From c6d590d5123a1c3a3e2681a683edc73b23067830 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20M=C3=A9taireau?= Date: Thu, 31 Jul 2014 10:11:48 +0200 Subject: [PATCH] Work on the hawk article --- Makefile | 3 + content/tech/hawk.rst | 184 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 187 insertions(+) create mode 100644 content/tech/hawk.rst diff --git a/Makefile b/Makefile index 32cbe68..3ee7ff5 100644 --- a/Makefile +++ b/Makefile @@ -5,3 +5,6 @@ build: upload: build rsync -e "ssh -p 22" -P -rvz --delete output/* alexis@172.19.2.119:/home/www/notmyidea.org/blog + +serve: build + cd output && python -m pelican.server 8000 diff --git a/content/tech/hawk.rst b/content/tech/hawk.rst new file mode 100644 index 0000000..fcd85c4 --- /dev/null +++ b/content/tech/hawk.rst @@ -0,0 +1,184 @@ +What's Hawk and how to use it? +############################## + +:date: 2014-07-31 + +We recently had to implement `the Hawk authentication scheme +`_ for a number of projects, and we came up +creating two libraries to ease integration into pyramid and node.js apps. + +But maybe you don't know Hawk. Hawk is a relatively new technology, crafted by +one of the original OAuth specification authors, that intends to replace the +2-legged OAuth authentication scheme using a simpler approach. + +It is an authentication scheme for HTTP, built around HMAC digests of +requests and responses. + +Every authenticated client request has an Authorization header containing a MAC +and some additional metadata, and each server response to authenticated +requests contains a Server-Authorization header that authenticates the +response, so the client is sure it comes from the right server. + + +Exchange of the hawk id and hawk secret +======================================= + +To sign the requests, a client needs to retrieve a token id and secret from the +server. + +Hawk itself does not define how the hawk id and secret should be exchanged +between the server and the client. The excellent team behind `Firefox Accounts +`_ put together a scheme to do that, which acts +like the following: + +.. note:: + + All this derivation crazyness might seem a bit complicated, but don't worry, + we put together some libraries that takes care of that for you automatically. + + If you are not interested into these details, you can directly jump to the + next section to see how to use the libraries. + +When your server application needs to send you the credentials, it will return +it inside a specific `Hawk-Session-Token` header. + +In order to get the hawk credentials to use on the client you will need to: + +First, do an `HKDF derivation `_ on the +given session token. You'll need to use the following parameters:: + + key_material = HKDF(hawk_session, "", 'identity.mozilla.com/picl/v1/sessionToken', 32*2) + +The "identity.mozilla.com/picl/v1/sessionToken" is a reference to this way of +deriving the credentials, not an actual URL. + +Then, the key material you'll get out of the HKDF need to be separated into two +parts, the first 32 hex caracters are the hawk id, and the next 32 ones are the +hawk key. + +Credentials:: + + credentials = { + 'id': keyMaterial[0:32], + 'key': keyMaterial[32:64], + 'algorithm': 'sha256' + } + +Httpie +====== + +To showcase APIs in the documentation, I like to use `httpie +`_, a curl-replacement with a nicer +API, built around `the python requests library `_. + +Luckily, HTTPie allows you to plug different authentication schemes for it, so `I wrote +a wrapper `_ around `mohawk +`_ to add hawk support to the requests lib. + +Doing hawk requests in your terminal is now as simple as:: + + $ pip install requests-hawk httpie + $ http GET localhost:5000/registration --auth-type=hawk --auth='id:key' + +In addition, it will help you to craft requests using the requests library:: + + import requests + from requests_hawk import HawkAuth + + hawk_auth = HawkAuth( + credentials={'id': id, 'key': key, 'algorithm': 'sha256'}) + + requests.post("/url", auth=hawk_auth) + +Alternatively, if you don't have the token id and secret, you can pass the hawk +session token I talked about earlier and the lib will take care of the +derivation for you:: + + hawk_auth = HawkAuth( + hawk_session=resp.headers['hawk-session-token'], + server_url=self.server_url + ) + requests.post("/url", auth=hawk_auth) + +Integrate with python pyramid apps +================================== + +If you're writing pyramid applications, you'll be happy to learn that `Ryan +Kelly `_ put together a library that makes Hawk +work as an Authentication provider for them. I'm chocked how simple it +is to use it. + +Here is a demo of how we implemented it for Daybed:: + + from pyramid_hawkauth import HawkAuthenticationPolicy + + policy = HawkAuthenticationPolicy(decode_hawk_id=get_hawk_id) + config.set_authentication_policy(authn_policy) + +The `get_hawk_id` function is a function that takes a request and +a tokenid and returns a tuple of `(token_id, token_secret)`. + +How you want to store the tokens and retrieve them is up to you. The default +implementation (e.g. if you don't pass a `decode_hawk_id` function) decodes the +secret from the token itself, using a master secret on the server (so you don't +need to store anything). + +Integrate with node.js Express apps +=================================== + +We had to implement Hawk authentication for two node.js projects and finally +came up factorizing everything in a library for express, named `express-hawkauth +`_. + +In order to plug it in your application, you'll need to use it as +a middleware:: + + var express = require("express"); + var hawk = require("express-hawkauth"); + app = express(); + + var hawkMiddleware = hawk.getMiddleware({ + hawkOptions: {}, + getSession: function(tokenId, cb) { + // A function which pass to the cb the key and algorithm for the + // given token id. First argument of the callback is a potential + // error. + cb(null, {key: "key", algorithm: "sha256"}); + }, + createSession: function(id, key, cb) { + // A function which stores a session for the given id and key. + // Argument returned is a potential error. + cb(null); + }, + setUser: function(req, res, tokenId, cb) { + // A function that uses req and res, the hawkId when they're known so + // that it can tweak it. For instance, you can store the tokenId as the + // user. + req.user = tokenId; + } + }); + + app.get('/hawk-enabled-endpoint', hawkMiddleware); + + +If you pass the `createSession` parameter, all non-authenticated requests will +create a new hawk session and return it with the response, in the +`Hawk-Session-Token` header. + +If you want to only check a valid hawk session exists (without creating a new +one), just create a middleware which doesn't have any `createSession` parameter +defined. + +Some reference implementations +============================== + +As a reference, here is how we're using the libraries I'm talking about, in +case that helps you to integrate with your projects. + +- The Mozilla Loop server uses hawk as authentication once you're logged in with + a valid BrowserID assertion; +- The Mozilla phone number verification server uses hawk after the first + request, to keep a session between client and server; +- `I recently added hawk support on the Daybed project + `_ + (that's a pyramid / cornice) app.