blog.notmyidea.org/whats-hawk-and-how-to-use-it.html

241 lines
No EOL
20 KiB
HTML

<!DOCTYPE html>
<html lang="fr">
<head>
<title>
What&#8217;s Hawk and how to use&nbsp;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?v2"
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>
<div id="content">
<section id="links">
<ul>
<li>
<a class="main" href="/">Alexis Métaireau</a>
</li>
<li>
<a class=""
href="https://blog.notmyidea.org/journal/index.html">Journal</a>
</li>
<li>
<a class="selected"
href="https://blog.notmyidea.org/code/">Code, etc.</a>
</li>
<li>
<a class=""
href="https://blog.notmyidea.org/weeknotes/">Notes hebdo</a>
</li>
<li>
<a class=""
href="https://blog.notmyidea.org/lectures/">Lectures</a>
</li>
<li>
<a class=""
href="https://blog.notmyidea.org/projets.html">Projets</a>
</li>
</ul>
</section>
<header>
<h1 class="post-title">What&#8217;s Hawk and how to use&nbsp;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&nbsp;apps.</p>
<p>But maybe you don&#8217;t know&nbsp;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&nbsp;approach.</p>
<p>It is an authentication scheme for <span class="caps">HTTP</span>, built around <a href="https://en.wikipedia.org/wiki/Hmac"><span class="caps">HMAC</span>
digests</a> of requests and&nbsp;responses.</p>
<p>Every authenticated client request has an Authorization header
containing a <span class="caps">MAC</span> (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&nbsp;server.</p>
<h2 id="exchange-of-the-hawk-id-and-hawk-key">Exchange of the hawk id and hawk&nbsp;key</h2>
<p>To sign the requests, a client needs to retrieve a token id and a token
key from the&nbsp;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&nbsp;following:</p>
<div class="note">
<div class="admonition-title">
Note
</div>
All this derivation crazyness might seem a bit complicated, but don&#8217;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&nbsp;requests.</p>
<p>In order to get the hawk credentials, you&#8217;ll need&nbsp;to:</p>
<p>First, do an <a href="http://en.wikipedia.org/wiki/HKDF"><span class="caps">HKDF</span> derivation</a> on the
given session token. You&#8217;ll need to use the following&nbsp;parameters:</p>
<div class="highlight"><pre><span></span><code>key_material = HKDF(hawk_session, &quot;&quot;, &#39;identity.mozilla.com/picl/v1/sessionToken&#39;, 32*2)
</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 <span class="caps">URL</span>.
</div>
<p>Then, the key material you&#8217;ll get out of the <span class="caps">HKDF</span> need to be separated
into two parts, the first 32 hex caracters are the hawk id, and the next
32 ones are the hawk&nbsp;key.</p>
<p>Credentials:</p>
<div class="highlight"><pre><span></span><code>javascript
credentials = {
&#39;id&#39;: keyMaterial[0:32],
&#39;key&#39;: keyMaterial[32:64],
&#39;algorithm&#39;: &#39;sha256&#39;
}
</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 <span class="caps">API</span>, 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&nbsp;lib.</p>
<p>Doing hawk requests in your terminal is now as simple&nbsp;as:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>pip<span class="w"> </span>install<span class="w"> </span>requests-hawk<span class="w"> </span>httpie
$<span class="w"> </span>http<span class="w"> </span>GET<span class="w"> </span>localhost:5000/registration<span class="w"> </span>--auth-type<span class="o">=</span>hawk<span class="w"> </span>--auth<span class="o">=</span><span class="s1">&#39;id:key&#39;</span>
</code></pre></div>
<p>In addition, it will help you to craft requests using the requests&nbsp;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">&#39;id&#39;</span><span class="p">:</span> <span class="nb">id</span><span class="p">,</span> <span class="s1">&#39;key&#39;</span><span class="p">:</span> <span class="n">key</span><span class="p">,</span> <span class="s1">&#39;algorithm&#39;</span><span class="p">:</span> <span class="s1">&#39;sha256&#39;</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">&quot;/url&quot;</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&#8217;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&nbsp;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">&#39;hawk-session-token&#39;</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">&quot;/url&quot;</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&nbsp;apps</h2>
<p>If you&#8217;re writing pyramid applications, you&#8217;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&#8217;m chocked how
simple it is to use&nbsp;it.</p>
<p>Here is a demo of how we implemented it for&nbsp;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,&nbsp;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&#8217;t pass a decode_hawk_id
function) decodes the key from the token itself, using a master secret
on the server (so you don&#8217;t need to store&nbsp;anything).</p>
<h2 id="integrate-with-nodejs-express-apps">Integrate with node.js Express&nbsp;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&#8217;ll need to use it as a&nbsp;middleware:</p>
<div class="highlight"><pre><span></span><code><span class="n">javascript</span>
<span class="k">var</span><span class="w"> </span><span class="n">express</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">require</span><span class="p">(</span><span class="s2">&quot;express&quot;</span><span class="p">);</span>
<span class="k">var</span><span class="w"> </span><span class="n">hawk</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">require</span><span class="p">(</span><span class="s2">&quot;express-hawkauth&quot;</span><span class="p">);</span>
<span class="n">app</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">express</span><span class="p">();</span>
<span class="k">var</span><span class="w"> </span><span class="n">hawkMiddleware</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">hawk</span><span class="o">.</span><span class="n">getMiddleware</span><span class="p">({</span>
<span class="w"> </span><span class="n">hawkOptions</span><span class="p">:</span><span class="w"> </span><span class="p">{},</span>
<span class="w"> </span><span class="n">getSession</span><span class="p">:</span><span class="w"> </span><span class="n">function</span><span class="p">(</span><span class="n">tokenId</span><span class="p">,</span><span class="w"> </span><span class="n">cb</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="n">A</span><span class="w"> </span><span class="n">function</span><span class="w"> </span><span class="n">which</span><span class="w"> </span><span class="k">pass</span><span class="w"> </span><span class="n">to</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">cb</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">key</span><span class="w"> </span><span class="ow">and</span><span class="w"> </span><span class="n">algorithm</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">the</span>
<span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="n">given</span><span class="w"> </span><span class="n">token</span><span class="w"> </span><span class="n">id</span><span class="o">.</span><span class="w"> </span><span class="n">First</span><span class="w"> </span><span class="n">argument</span><span class="w"> </span><span class="n">of</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">callback</span><span class="w"> </span><span class="k">is</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="n">potential</span>
<span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="n">error</span><span class="o">.</span>
<span class="w"> </span><span class="n">cb</span><span class="p">(</span><span class="nb nb-Type">null</span><span class="p">,</span><span class="w"> </span><span class="p">{</span><span class="n">key</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;key&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">algorithm</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;sha256&quot;</span><span class="p">});</span>
<span class="w"> </span><span class="p">},</span>
<span class="w"> </span><span class="n">createSession</span><span class="p">:</span><span class="w"> </span><span class="n">function</span><span class="p">(</span><span class="n">id</span><span class="p">,</span><span class="w"> </span><span class="n">key</span><span class="p">,</span><span class="w"> </span><span class="n">cb</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="n">A</span><span class="w"> </span><span class="n">function</span><span class="w"> </span><span class="n">which</span><span class="w"> </span><span class="n">stores</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="n">session</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">given</span><span class="w"> </span><span class="n">id</span><span class="w"> </span><span class="ow">and</span><span class="w"> </span><span class="n">key</span><span class="o">.</span>
<span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="n">Argument</span><span class="w"> </span><span class="n">returned</span><span class="w"> </span><span class="k">is</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="n">potential</span><span class="w"> </span><span class="n">error</span><span class="o">.</span>
<span class="w"> </span><span class="n">cb</span><span class="p">(</span><span class="nb nb-Type">null</span><span class="p">);</span>
<span class="w"> </span><span class="p">},</span>
<span class="w"> </span><span class="n">setUser</span><span class="p">:</span><span class="w"> </span><span class="n">function</span><span class="p">(</span><span class="n">req</span><span class="p">,</span><span class="w"> </span><span class="n">res</span><span class="p">,</span><span class="w"> </span><span class="n">tokenId</span><span class="p">,</span><span class="w"> </span><span class="n">cb</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="n">A</span><span class="w"> </span><span class="n">function</span><span class="w"> </span><span class="n">that</span><span class="w"> </span><span class="n">uses</span><span class="w"> </span><span class="n">req</span><span class="w"> </span><span class="ow">and</span><span class="w"> </span><span class="n">res</span><span class="p">,</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">hawkId</span><span class="w"> </span><span class="n">when</span><span class="w"> </span><span class="n">they</span><span class="s1">&#39;re known so</span>
<span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="n">that</span><span class="w"> </span><span class="n">it</span><span class="w"> </span><span class="n">can</span><span class="w"> </span><span class="n">tweak</span><span class="w"> </span><span class="n">it</span><span class="o">.</span><span class="w"> </span><span class="n">For</span><span class="w"> </span><span class="n">instance</span><span class="p">,</span><span class="w"> </span><span class="n">you</span><span class="w"> </span><span class="n">can</span><span class="w"> </span><span class="n">store</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">tokenId</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="n">the</span>
<span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="n">user</span><span class="o">.</span>
<span class="w"> </span><span class="n">req</span><span class="o">.</span><span class="n">user</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">tokenId</span><span class="p">;</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">});</span>
<span class="n">app</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;/hawk-enabled-endpoint&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">hawkMiddleware</span><span class="p">);</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&nbsp;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&#8217;t have any
createSession parameter&nbsp;defined.</p>
<h2 id="some-reference-implementations">Some reference&nbsp;implementations</h2>
<p>As a reference, here is how we&#8217;re using the libraries I&#8217;m talking about,
in case that helps you to integrate with your&nbsp;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&#8217;re
logged in with a valid BrowserID
assertion</a>;
request, to keep a session between client and&nbsp;server;</li>
<li><a href="https://github.com/spiral-project/daybed/commit/f178b4e43015fa077430798dcd3d0886c7611caf">I recently added hawk support on the Daybed
project</a>
(that&#8217;s a pyramid / cornice)&nbsp;app.</li>
<li>It&#8217;s also interesting to note that Kumar put together <a href="http://hawkrest.readthedocs.org/en/latest/">hawkrest, for
the django rest&nbsp;framework</a></li>
</ul>
</article>
<footer>
<a id="feed" href="/feeds/all.atom.xml">
<img alt="RSS Logo" src="/theme/rss.svg" />
</a>
</footer>
</div>
</body>
</html>