How to implement a BrowserID Primary

While working on my browserid-provider gem I had to figure out a few basic things to get this working, so I thought I’d share the essence here.

UPDATE: The primary source now is found at MDN.

The requirements for a BrowserID Primary Identity Provider are as follows.

  • A Primary authorize and certify users of it’s own domain by email address
  • The provider must respond to the following requests:
    1. GET /.well-known/browserid
    2. GET /provision
    3. GET /whoami
    4. GET /sign_in
    5. POST /certify

But wait, the only strict requirement is the first of those, since the rest can be configured to any other path of your preference.

GET /.well-known/browserid

You can observe the well-known/browserid at eyedee.me is is a JSON file with three keys:

  • public-key: An RSA public key
  • authentication: where to GET /sign_in
  • provisioning: where to GET /provision

I found some understanding of the RSA key here and the JWS here. The wiki page about the Primary Protocol gives more details about the requirements. Here’s how you can make one:

GET /provision

Next, your browser will send a request to your provisioning path for the HTML content that goes into the iframe mentioned on the Primary wiki page. I modified the static page from eyedee.me to my own dynamic page.

GET /whoami

This is a simple “application/json” document showing the username part of an email address:

{"user":null} or {"user":"mormor"}

GET /sign_in

This is where you present your authentication form, it will be loaded in the BrowserID window. You have to use the BrowserID authentication_api.js here, but watch out for getting this from the same site as the client gets the include.js and the /provision gets the provisioning_api.js. See more at the developer tips.

POST /certify

Last, The provisioning generates a key pair for the current user session on the client side, and sends a POST to the certify path. Have a look at the navigator.id.genKeyPair function in the provision document. The JSON data posted should look like this:

What we would like to do is:

  1. Verify that the browser session is active (a user is logged in)
  2. Sign the client certificate with our Primary certificate

BrowserID expects a JSON document like this one in response:

{"cert":"c429d3345a8d17eb3bf4a06a349d392e00d329744a5179380344e .... "}

Which I generate like this:

If you’re a Rubyist, go use my gem. Otherwise, go make a Primary.

By the way: I couldn’t have done this without the json-jwt.

Manually testing Rails REST API with cURL

I was reading REST-esting with cURL and found this very useful for manually testing my Rails JSON APIs. I am using Cucumber for my Behavious-Driven Development but I find that I often need to see how it feels to actually use the API with other tools than the Ruby ones.

I noticed that when running tests, the params Capybara generated in the controller was a Hash, while the cURL-generated params was a String. This was problematic when I was designing a JSON API, as I didn’t want to allow more than one way of posting JSON. Besides, I wanted to make as much use of the Rails magic as possible, so I added to the PostsController a condition for accepting the POST request:
[ruby] # POST /quantities
def create
begin
if params[“post”].is_a? Hash
@post = Post.create(params[:post])
respond_with(@post)
else
respond_with({:error => “Malformed JSON object.”}, :status => 406)
end
rescue
respond_with({:error => “Post Not Created.”}, :status => 400)
end
end[/ruby]

My User model looks like this:
[ruby]class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :token_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable

# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me, :username

# Create the authentication token for the user
before_save :ensure_authentication_token
end[/ruby]
This gives us the ability to accept requests with an authentication token. On the client side, just add a User’s token to the URL: [javascript]?auth_token=token_hash[/javascript] This is a Devise property. The User is :token_authenticatable and therefore have a an authentication_token in the database.

So here’s my Rails magic compatible cURL PUT:
[bash]$ curl -v -H “Accept: application/json” \n
-H “Content-Type: application/json” -X PUT \n
-d ‘{“post”:{“category_id”:1,”confirmed”:false}}’\n
http://localhost:3000/posts/1?auth_token=UeF1pJi5yxHjjgfrjpVf[/bash]
The reason this is compatible is simply the Content-Type HTTP header being specified.

This is how I perform a GET:
[bash]$ curl -v -H “Accept: application/json” \n
-H “Content-Type: application/json” -X GET \n
http://localhost:3000/posts/1?auth_token=UeF1pJi5yxHjjgfrjpVf[/bash]

Now, when I want to test different types of request, I can just replace the MIME types:

  • HTML is text/html
  • XML is application/xml or text/xml
  • JSON is application/json

This form of testing is much faster than running Cucumber for each time I want to see subtle changes. Besides, when I write tests for an API, and I want to prepare a POST test like this:

  Scenario: POST a post should be successful
    Given I send and accept JSON
    When I send a POST request to "/posts" with the following:
      """
        {"post":
          { "category_id":3,
            "date":"2012-02-14 05:00:00",
            "body":"Here's some text for ya!"
          }
        }
      """
    Then the response status should be "201"
    And the newest post should have the following:
      | category_id | body                     | date                |
      | 3           | Here's some text for ya! | 2012-02-14 05:00:00 |

    Given I send and accept XML
    When I send a POST request to "/posts" with the following:
      """
        
          2012-02-14T15:00:00
          1
          Here's some text for me!
        
      """
    Then the response status should be "201"
    And the newest paynote should have the following:
      | category_id | body                     | date                |
      | 1           | Here's some text for me! | 2012-02-14 15:00:00 |

To get the XML structure, I just use my cURL to GET /posts/1.xml or /posts/1.json, then I don’t have to write up the structure for the input myself. 🙂