mirror of
https://github.com/almet/notmyidea.git
synced 2025-04-28 11:32:39 +02:00
241 lines
No EOL
20 KiB
HTML
241 lines
No EOL
20 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<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?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’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 <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 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 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"><span class="caps">HKDF</span> derivation</a> on the
|
|
given session token. You’ll need to use the following parameters:</p>
|
|
<div class="highlight"><pre><span></span><code>key_material = HKDF(hawk_session, "", 'identity.mozilla.com/picl/v1/sessionToken', 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’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 key.</p>
|
|
<p>Credentials:</p>
|
|
<div class="highlight"><pre><span></span><code>javascript
|
|
credentials = {
|
|
'id': keyMaterial[0:32],
|
|
'key': keyMaterial[32:64],
|
|
'algorithm': 'sha256'
|
|
}
|
|
</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 lib.</p>
|
|
<p>Doing hawk requests in your terminal is now as simple 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">'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="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">"express"</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">"express-hawkauth"</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">"key"</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">"sha256"</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">'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">"/hawk-enabled-endpoint"</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 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>
|
|
<footer>
|
|
<a id="feed" href="/feeds/all.atom.xml">
|
|
<img alt="RSS Logo" src="/theme/rss.svg" />
|
|
</a>
|
|
</footer>
|
|
</div>
|
|
</body>
|
|
</html> |