mirror of
https://github.com/almet/notmyidea.git
synced 2025-04-29 03:52:38 +02:00
219 lines
No EOL
15 KiB
HTML
219 lines
No EOL
15 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<title>What's Hawk and how to use it? - Alexis Métaireau</title>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<link rel="stylesheet" href="https://blog.notmyidea.org/theme/css/main.css" type="text/css" />
|
|
<link href="https://blog.notmyidea.org/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="Alexis Métaireau ATOM Feed" />
|
|
</head>
|
|
<body>
|
|
<section id="links">
|
|
<li>
|
|
<a class="" href="https://blog.notmyidea.org/" id="site-title">Blog</a>
|
|
</li>
|
|
<li><a class="" href="https://blog.notmyidea.org/pages/projets.html">Projets</a></li>
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<header>
|
|
<h1 class="post-title">What's Hawk and how to use it?</h1>
|
|
<time datetime="2014-07-31T00:00:00+02:00">31 juillet 2014</time>
|
|
|
|
|
|
</header>
|
|
<article>
|
|
|
|
<p>At Mozilla, we recently had to implement <a href="https://github.com/hueniverse/hawk">the Hawk authentication
|
|
scheme</a> for a number of projects,
|
|
and we came up creating two libraries to ease integration into pyramid
|
|
and node.js apps.</p>
|
|
<p>But maybe you don't know Hawk.</p>
|
|
<p>Hawk is a relatively new technology, crafted by one of the original
|
|
<a href="https://en.wikipedia.org/wiki/OAuth">OAuth</a> specification authors, that
|
|
intends to replace the 2-legged OAuth authentication scheme using a
|
|
simpler approach.</p>
|
|
<p>It is an authentication scheme for HTTP, built around <a href="https://en.wikipedia.org/wiki/Hmac">HMAC
|
|
digests</a> of requests and responses.</p>
|
|
<p>Every authenticated client request has an Authorization header
|
|
containing a MAC (Message Authentication Code) and some additional
|
|
metadata, then 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.</p>
|
|
<h2 id="exchange-of-the-hawk-id-and-hawk-key">Exchange of the hawk id and hawk key</h2>
|
|
<p>To sign the requests, a client needs to retrieve a token id and a token
|
|
key from the server.</p>
|
|
<p>Hawk itself does not define how these credentials should be exchanged
|
|
between the server and the client. The excellent team behind <a href="http://accounts.firefox.com">Firefox
|
|
Accounts</a> put together a scheme to do that,
|
|
which acts like the following:</p>
|
|
<div class="note">
|
|
|
|
<div class="admonition-title">
|
|
|
|
Note
|
|
|
|
</div>
|
|
|
|
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.
|
|
|
|
</div>
|
|
|
|
<p>When your server application needs to send you the credentials, it will
|
|
return it inside a specific Hawk-Session-Token header. This token can be
|
|
derived to split this string in two values (hawk id and hawk key) that
|
|
you will use to sign your next requests.</p>
|
|
<p>In order to get the hawk credentials, you'll need to:</p>
|
|
<p>First, do an <a href="http://en.wikipedia.org/wiki/HKDF">HKDF derivation</a> on the
|
|
given session token. You'll need to use the following
|
|
parameters:</p>
|
|
<div class="highlight"><pre><span></span><code><span class="err">key_material = HKDF(hawk_session, "", 'identity.mozilla.com/picl/v1/sessionToken', 32*2)</span>
|
|
</code></pre></div>
|
|
|
|
<div class="note">
|
|
|
|
<div class="admonition-title">
|
|
|
|
Note
|
|
|
|
</div>
|
|
|
|
The `identity.mozilla.com/picl/v1/sessionToken` is a reference to this
|
|
way of deriving the credentials, not an actual URL.
|
|
|
|
</div>
|
|
|
|
<p>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.</p>
|
|
<p>Credentials:</p>
|
|
<div class="highlight"><pre><span></span><code><span class="err">javascript</span>
|
|
<span class="err">credentials = {</span>
|
|
<span class="err"> 'id': keyMaterial[0:32],</span>
|
|
<span class="err"> 'key': keyMaterial[32:64],</span>
|
|
<span class="err"> 'algorithm': 'sha256'</span>
|
|
<span class="err">}</span>
|
|
</code></pre></div>
|
|
|
|
<h2 id="httpie">Httpie</h2>
|
|
<p>To showcase APIs in the documentation, I like to use
|
|
<a href="https://github.com/jakubroztocil/httpie">httpie</a>, a curl-replacement
|
|
with a nicer API, built around <a href="http://python-requests.org">the python requests
|
|
library</a>.</p>
|
|
<p>Luckily, HTTPie allows you to plug different authentication schemes for
|
|
it, so <a href="https://github.com/mozilla-services/requests-hawk">I wrote a
|
|
wrapper</a> around
|
|
<a href="https://github.com/kumar303/mohawk">mohawk</a> to add hawk support to the
|
|
requests lib.</p>
|
|
<p>Doing hawk requests in your terminal is now as simple as:</p>
|
|
<div class="highlight"><pre><span></span><code>$ pip install requests-hawk httpie
|
|
$ http GET localhost:5000/registration --auth-type<span class="o">=</span>hawk --auth<span class="o">=</span><span class="s1">'id:key'</span>
|
|
</code></pre></div>
|
|
|
|
<p>In addition, it will help you to craft requests using the requests
|
|
library:</p>
|
|
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">requests</span>
|
|
<span class="kn">from</span> <span class="nn">requests_hawk</span> <span class="kn">import</span> <span class="n">HawkAuth</span>
|
|
|
|
<span class="n">hawk_auth</span> <span class="o">=</span> <span class="n">HawkAuth</span><span class="p">(</span>
|
|
<span class="n">credentials</span><span class="o">=</span><span class="p">{</span><span class="s1">'id'</span><span class="p">:</span> <span class="nb">id</span><span class="p">,</span> <span class="s1">'key'</span><span class="p">:</span> <span class="n">key</span><span class="p">,</span> <span class="s1">'algorithm'</span><span class="p">:</span> <span class="s1">'sha256'</span><span class="p">})</span>
|
|
|
|
<span class="n">requests</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s2">"/url"</span><span class="p">,</span> <span class="n">auth</span><span class="o">=</span><span class="n">hawk_auth</span><span class="p">)</span>
|
|
</code></pre></div>
|
|
|
|
<p>Alternatively, if you don't have the token id and key, you can pass the
|
|
hawk session token I talked about earlier and the lib will take care of
|
|
the derivation for you:</p>
|
|
<div class="highlight"><pre><span></span><code><span class="n">hawk_auth</span> <span class="o">=</span> <span class="n">HawkAuth</span><span class="p">(</span>
|
|
<span class="n">hawk_session</span><span class="o">=</span><span class="n">resp</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s1">'hawk-session-token'</span><span class="p">],</span>
|
|
<span class="n">server_url</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">server_url</span>
|
|
<span class="p">)</span>
|
|
<span class="n">requests</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s2">"/url"</span><span class="p">,</span> <span class="n">auth</span><span class="o">=</span><span class="n">hawk_auth</span><span class="p">)</span>
|
|
</code></pre></div>
|
|
|
|
<h2 id="integrate-with-python-pyramid-apps">Integrate with python pyramid apps</h2>
|
|
<p>If you're writing pyramid applications, you'll be happy to learn that
|
|
<a href="https://www.rfk.id.au/blog/">Ryan Kelly</a> put together a library that
|
|
makes Hawk work as an Authentication provider for them. I'm chocked how
|
|
simple it is to use it.</p>
|
|
<p>Here is a demo of how we implemented it for Daybed:</p>
|
|
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">pyramid_hawkauth</span> <span class="kn">import</span> <span class="n">HawkAuthenticationPolicy</span>
|
|
|
|
<span class="n">policy</span> <span class="o">=</span> <span class="n">HawkAuthenticationPolicy</span><span class="p">(</span><span class="n">decode_hawk_id</span><span class="o">=</span><span class="n">get_hawk_id</span><span class="p">)</span>
|
|
<span class="n">config</span><span class="o">.</span><span class="n">set_authentication_policy</span><span class="p">(</span><span class="n">authn_policy</span><span class="p">)</span>
|
|
</code></pre></div>
|
|
|
|
<p>The get_hawk_id function is a function that takes a request and a
|
|
tokenid and returns a tuple of (token_id, token_key).</p>
|
|
<p>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 key from the token itself, using a master secret
|
|
on the server (so you don't need to store anything).</p>
|
|
<h2 id="integrate-with-nodejs-express-apps">Integrate with node.js Express apps</h2>
|
|
<p>We had to implement Hawk authentication for two node.js projects and
|
|
finally came up factorizing everything in a library for express, named
|
|
<a href="https://github.com/mozilla-services/express-hawkauth">express-hawkauth</a>.</p>
|
|
<p>In order to plug it in your application, you'll need to use it as a
|
|
middleware:</p>
|
|
<div class="highlight"><pre><span></span><code><span class="nt">javascript</span>
|
|
<span class="nt">var</span> <span class="nt">express</span> <span class="o">=</span> <span class="nt">require</span><span class="o">(</span><span class="s2">"express"</span><span class="o">);</span>
|
|
<span class="nt">var</span> <span class="nt">hawk</span> <span class="o">=</span> <span class="nt">require</span><span class="o">(</span><span class="s2">"express-hawkauth"</span><span class="o">);</span>
|
|
<span class="nt">app</span> <span class="o">=</span> <span class="nt">express</span><span class="o">();</span>
|
|
|
|
<span class="nt">var</span> <span class="nt">hawkMiddleware</span> <span class="o">=</span> <span class="nt">hawk</span><span class="p">.</span><span class="nc">getMiddleware</span><span class="o">(</span><span class="p">{</span>
|
|
<span class="n">hawkOptions</span><span class="p">:</span> <span class="err">{</span><span class="p">}</span><span class="o">,</span>
|
|
<span class="nt">getSession</span><span class="o">:</span> <span class="nt">function</span><span class="o">(</span><span class="nt">tokenId</span><span class="o">,</span> <span class="nt">cb</span><span class="o">)</span> <span class="p">{</span>
|
|
<span class="err">//</span> <span class="err">A</span> <span class="err">function</span> <span class="err">which</span> <span class="err">pass</span> <span class="err">to</span> <span class="err">the</span> <span class="err">cb</span> <span class="err">the</span> <span class="err">key</span> <span class="err">and</span> <span class="err">algorithm</span> <span class="err">for</span> <span class="err">the</span>
|
|
<span class="err">//</span> <span class="err">given</span> <span class="err">token</span> <span class="err">id.</span> <span class="err">First</span> <span class="err">argument</span> <span class="err">of</span> <span class="err">the</span> <span class="err">callback</span> <span class="err">is</span> <span class="err">a</span> <span class="err">potential</span>
|
|
<span class="err">//</span> <span class="err">error.</span>
|
|
<span class="err">cb(null,</span> <span class="err">{</span><span class="n">key</span><span class="p">:</span> <span class="s2">"key"</span><span class="p">,</span> <span class="n">algorithm</span><span class="o">:</span> <span class="s2">"sha256"</span><span class="p">}</span><span class="o">);</span>
|
|
<span class="err">}</span><span class="o">,</span>
|
|
<span class="nt">createSession</span><span class="o">:</span> <span class="nt">function</span><span class="o">(</span><span class="nt">id</span><span class="o">,</span> <span class="nt">key</span><span class="o">,</span> <span class="nt">cb</span><span class="o">)</span> <span class="p">{</span>
|
|
<span class="err">//</span> <span class="err">A</span> <span class="err">function</span> <span class="err">which</span> <span class="err">stores</span> <span class="err">a</span> <span class="err">session</span> <span class="err">for</span> <span class="err">the</span> <span class="err">given</span> <span class="err">id</span> <span class="err">and</span> <span class="err">key.</span>
|
|
<span class="err">//</span> <span class="err">Argument</span> <span class="err">returned</span> <span class="err">is</span> <span class="err">a</span> <span class="err">potential</span> <span class="err">error.</span>
|
|
<span class="err">cb(null)</span><span class="p">;</span>
|
|
<span class="p">}</span><span class="o">,</span>
|
|
<span class="nt">setUser</span><span class="o">:</span> <span class="nt">function</span><span class="o">(</span><span class="nt">req</span><span class="o">,</span> <span class="nt">res</span><span class="o">,</span> <span class="nt">tokenId</span><span class="o">,</span> <span class="nt">cb</span><span class="o">)</span> <span class="p">{</span>
|
|
<span class="err">//</span> <span class="err">A</span> <span class="err">function</span> <span class="err">that</span> <span class="err">uses</span> <span class="err">req</span> <span class="err">and</span> <span class="err">res,</span> <span class="err">the</span> <span class="err">hawkId</span> <span class="err">when</span> <span class="err">they're</span> <span class="err">known</span> <span class="err">so</span>
|
|
<span class="err">//</span> <span class="err">that</span> <span class="err">it</span> <span class="err">can</span> <span class="err">tweak</span> <span class="err">it.</span> <span class="err">For</span> <span class="err">instance,</span> <span class="err">you</span> <span class="err">can</span> <span class="err">store</span> <span class="err">the</span> <span class="err">tokenId</span> <span class="err">as</span> <span class="err">the</span>
|
|
<span class="err">//</span> <span class="err">user.</span>
|
|
<span class="err">req.user</span> <span class="err">=</span> <span class="err">tokenId</span><span class="p">;</span>
|
|
<span class="p">}</span>
|
|
<span class="err">}</span><span class="o">);</span>
|
|
|
|
<span class="nt">app</span><span class="p">.</span><span class="nc">get</span><span class="o">(</span><span class="s2">"/hawk-enabled-endpoint"</span><span class="o">,</span> <span class="nt">hawkMiddleware</span><span class="o">);</span>
|
|
</code></pre></div>
|
|
|
|
<p>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.</p>
|
|
<p>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.</p>
|
|
<h2 id="some-reference-implementations">Some reference implementations</h2>
|
|
<p>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.</p>
|
|
<ul>
|
|
<li>The Mozilla Loop server <a href="https://github.com/mozilla-services/loop-server/blob/master/loop/index.js#L70-L133">uses hawk as authentication once you're
|
|
logged in with a valid BrowserID
|
|
assertion</a>;
|
|
request, to keep a session between client and server;</li>
|
|
<li><a href="https://github.com/spiral-project/daybed/commit/f178b4e43015fa077430798dcd3d0886c7611caf">I recently added hawk support on the Daybed
|
|
project</a>
|
|
(that's a pyramid / cornice) app.</li>
|
|
<li>It's also interesting to note that Kumar put together <a href="http://hawkrest.readthedocs.org/en/latest/">hawkrest, for
|
|
the django rest
|
|
framework</a></li>
|
|
</ul>
|
|
</article>
|
|
|
|
</body>
|
|
</html> |