blog.notmyidea.org/whats-hawk-and-how-to-use-it.html
2019-07-02 22:54:50 +00:00

300 lines
No EOL
18 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1">
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico" />
<title>What's Hawk and how to use it? - Carnets Web</title>
<meta charset="utf-8" />
<link href="https://blog.notmyidea.org/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="Carnets Web Full Atom Feed" />
<link rel="stylesheet" href="https://blog.notmyidea.org/theme/css/poole.css"/>
<link rel="stylesheet" href="https://blog.notmyidea.org/theme/css/syntax.css"/>
<link rel="stylesheet" href="https://blog.notmyidea.org/theme/css/lanyon.css"/>
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=PT+Serif:400,400italic,700%7CPT+Sans:400">
<link rel="stylesheet" href="https://blog.notmyidea.org/theme/css/styles.css"/>
<style>
h1 {
font-family: "Avant Garde", Avantgarde, "Century Gothic", CenturyGothic, "AppleGothic", sans-serif;
padding: 80px 50px;
text-align: center;
text-transform: uppercase;
text-rendering: optimizeLegibility;
color: #202020;
letter-spacing: .1em;
text-shadow:
-1px -1px 1px #111,
2px 2px 1px #eaeaea;
}
#main {
text-align: justify;
text-justify: inter-word;
}
#main h1 {
padding: 10px;
}
.post-headline {
padding: 15px;
}
</style>
</head>
<body>
<!-- Target for toggling the sidebar `.sidebar-checkbox` is for regular
styles, `#sidebar-checkbox` for behavior. -->
<input type="checkbox" class="sidebar-checkbox" id="sidebar-checkbox">
<!-- Toggleable sidebar -->
<div class="sidebar" id="sidebar">
<div class="sidebar-item">
<div class="profile">
<img src="https://blog.notmyidea.org/theme/img/profile.png"/>
</div>
</div>
<nav class="sidebar-nav">
<a class="sidebar-nav-item" href="/">Articles</a>
<a class="sidebar-nav-item" href="https://www.vieuxsinge.com">Brasserie du Vieux Singe</a>
<a class="sidebar-nav-item" href="http://blog.notmyidea.org/pages/about.html">A propos</a>
<a class="sidebar-nav-item" href="https://twitter.com/ametaireau">Messages courts</a>
<a class="sidebar-nav-item" href="https://github.com/almet">Code</a>
</nav>
</div> <div class="wrap">
<div class="masthead">
<div class="container">
<h3 class="masthead-title">
<a href="https://blog.notmyidea.org/" title="Home">Carnets Web</a>
</h3>
</div>
</div>
<div class="container content">
<div id="main" class="posts">
<h1 class="post-title">What's Hawk and how to use it?</h1>
<span class="post-date">31 juillet 2014</span>
<img id="illustration" src="" />
<div class="post article">
<h1>🌟</h1>
<p>At Mozilla, we recently had to implement <a class="reference external" 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 class="reference external" 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 class="reference external" 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>
<div class="section" id="exchange-of-the-hawk-id-and-hawk-key">
<h2>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 class="reference external" href="http://accounts.firefox.com">Firefox Accounts</a> put together a scheme to do that, which acts
like the following:</p>
<div class="admonition note">
<p class="first admonition-title">Note</p>
<p>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.</p>
<p class="last">If you are not interested into these details, you can directly jump to the
next section to see how to use the libraries.</p>
</div>
<p>When your server application needs to send you the credentials, it will return
it inside a specific <cite>Hawk-Session-Token</cite> 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 class="reference external" href="http://en.wikipedia.org/wiki/HKDF">HKDF derivation</a> on the
given session token. You'll need to use the following parameters:</p>
<pre class="literal-block">
key_material = HKDF(hawk_session, &quot;&quot;, 'identity.mozilla.com/picl/v1/sessionToken', 32*2)
</pre>
<div class="admonition note">
<p class="first admonition-title">Note</p>
<p class="last">The <tt class="docutils literal">identity.mozilla.com/picl/v1/sessionToken</tt> is a reference to this way of
deriving the credentials, not an actual URL.</p>
</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><span class="nx">credentials</span> <span class="o">=</span> <span class="p">{</span>
<span class="s1">&#39;id&#39;</span><span class="o">:</span> <span class="nx">keyMaterial</span><span class="p">[</span><span class="mi">0</span><span class="o">:</span><span class="mi">32</span><span class="p">],</span>
<span class="s1">&#39;key&#39;</span><span class="o">:</span> <span class="nx">keyMaterial</span><span class="p">[</span><span class="mi">32</span><span class="o">:</span><span class="mi">64</span><span class="p">],</span>
<span class="s1">&#39;algorithm&#39;</span><span class="o">:</span> <span class="s1">&#39;sha256&#39;</span>
<span class="p">}</span>
</pre></div>
</div>
<div class="section" id="httpie">
<h2>Httpie</h2>
<p>To showcase APIs in the documentation, I like to use <a class="reference external" href="https://github.com/jakubroztocil/httpie">httpie</a>, a curl-replacement with a nicer
API, built around <a class="reference external" 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 class="reference external" href="https://github.com/mozilla-services/requests-hawk">I wrote
a wrapper</a> around <a class="reference external" 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>
<pre class="literal-block">
$ pip install requests-hawk httpie
$ http GET localhost:5000/registration --auth-type=hawk --auth='id:key'
</pre>
<p>In addition, it will help you to craft requests using the requests library:</p>
<div class="highlight"><pre><span></span><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>
</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><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>
</pre></div>
</div>
<div class="section" id="integrate-with-python-pyramid-apps">
<h2>Integrate with python pyramid apps</h2>
<p>If you're writing pyramid applications, you'll be happy to learn that <a class="reference external" 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><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>
</pre></div>
<p>The <cite>get_hawk_id</cite> function is a function that takes a request and
a tokenid and returns a tuple of <cite>(token_id, token_key)</cite>.</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 <cite>decode_hawk_id</cite> function) decodes the
key from the token itself, using a master secret on the server (so you don't
need to store anything).</p>
</div>
<div class="section" id="integrate-with-node-js-express-apps">
<h2>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 class="reference external" 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><span class="kd">var</span> <span class="nx">express</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s2">&quot;express&quot;</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">hawk</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s2">&quot;express-hawkauth&quot;</span><span class="p">);</span>
<span class="nx">app</span> <span class="o">=</span> <span class="nx">express</span><span class="p">();</span>
<span class="kd">var</span> <span class="nx">hawkMiddleware</span> <span class="o">=</span> <span class="nx">hawk</span><span class="p">.</span><span class="nx">getMiddleware</span><span class="p">({</span>
<span class="nx">hawkOptions</span><span class="o">:</span> <span class="p">{},</span>
<span class="nx">getSession</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">tokenId</span><span class="p">,</span> <span class="nx">cb</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// A function which pass to the cb the key and algorithm for the</span>
<span class="c1">// given token id. First argument of the callback is a potential</span>
<span class="c1">// error.</span>
<span class="nx">cb</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="p">{</span><span class="nx">key</span><span class="o">:</span> <span class="s2">&quot;key&quot;</span><span class="p">,</span> <span class="nx">algorithm</span><span class="o">:</span> <span class="s2">&quot;sha256&quot;</span><span class="p">});</span>
<span class="p">},</span>
<span class="nx">createSession</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">id</span><span class="p">,</span> <span class="nx">key</span><span class="p">,</span> <span class="nx">cb</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// A function which stores a session for the given id and key.</span>
<span class="c1">// Argument returned is a potential error.</span>
<span class="nx">cb</span><span class="p">(</span><span class="kc">null</span><span class="p">);</span>
<span class="p">},</span>
<span class="nx">setUser</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">,</span> <span class="nx">tokenId</span><span class="p">,</span> <span class="nx">cb</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// A function that uses req and res, the hawkId when they&#39;re known so</span>
<span class="c1">// that it can tweak it. For instance, you can store the tokenId as the</span>
<span class="c1">// user.</span>
<span class="nx">req</span><span class="p">.</span><span class="nx">user</span> <span class="o">=</span> <span class="nx">tokenId</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s2">&quot;/hawk-enabled-endpoint&quot;</span><span class="p">,</span> <span class="nx">hawkMiddleware</span><span class="p">);</span>
</pre></div>
<p>If you pass the <cite>createSession</cite> parameter, all non-authenticated requests will
create a new hawk session and return it with the response, in the
<cite>Hawk-Session-Token</cite> 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 <cite>createSession</cite> parameter
defined.</p>
</div>
<div class="section" id="some-reference-implementations">
<h2>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 class="simple">
<li>The Mozilla Loop server <a class="reference external" 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 class="reference external" 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 class="reference external" href="http://hawkrest.readthedocs.org/en/latest/">hawkrest, for the
django rest framework</a></li>
</ul>
</div>
Vous pouvez également <a onclick="(function(){
let here = document.location;
document.location = `http://pdf.fivefilters.org/simple-print/url.php?size=A4#${here}`;
return false;
})();return false;">télécharger cet article en pdf</a>.
</div>
</div>
</div>
<label for="sidebar-checkbox" class="sidebar-toggle"></label>
<script>
(function(document) {
var i = 0;
// snip empty header rows since markdown can't
var rows = document.querySelectorAll('tr');
for(i=0; i<rows.length; i++) {
var ths = rows[i].querySelectorAll('th');
var rowlen = rows[i].children.length;
if (ths.length > 0 && ths.length === rowlen) {
rows[i].remove();
}
}
})(document);
</script>
<script>
/* Lanyon & Poole are Copyright (c) 2014 Mark Otto. Adapted to Pelican 20141223 and extended a bit by @thomaswilley */
(function(document) {
var toggle = document.querySelector('.sidebar-toggle');
var sidebar = document.querySelector('#sidebar');
var checkbox = document.querySelector('#sidebar-checkbox');
document.addEventListener('click', function(e) {
var target = e.target;
if(!checkbox.checked ||
sidebar.contains(target) ||
(target === checkbox || target === toggle)) return;
checkbox.checked = false;
}, false);
})(document);
</script>
<!-- Piwik -->
<script type="text/javascript">
var _paq = _paq || [];
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="//tracker.notmyidea.org/";
_paq.push(['setTrackerUrl', u+'piwik.php']);
_paq.push(['setSiteId', 3]);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
})();
</script>
<noscript><p><img src="//tracker.notmyidea.org/piwik.php?idsite=3" style="border:0;" alt="" /></p></noscript>
<!-- End Piwik Code -->
</div>
</body>
</html>