mirror of
https://github.com/almet/notmyidea.git
synced 2025-04-28 11:32:39 +02:00
280 lines
No EOL
18 KiB
HTML
280 lines
No EOL
18 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<title>
|
|
Implementing <span class="caps">CORS</span> in Cornice - 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">Implementing <span class="caps">CORS</span> in Cornice</h1>
|
|
<time datetime="2013-02-04T00:00:00+01:00">04 février 2013</time>
|
|
</header>
|
|
<article>
|
|
|
|
<div class="note">
|
|
|
|
<div class="admonition-title">
|
|
|
|
Note
|
|
|
|
</div>
|
|
|
|
I’m cross-posting [on the mozilla services
|
|
weblog](https://blog.mozilla.org/services/). Since this is the first
|
|
time we’re doing that, I though it could be useful to point you there.
|
|
Check it out and expect more technical articles there in the future.
|
|
|
|
</div>
|
|
|
|
<p>For security reasons, it’s not possible to do cross-domain requests. In
|
|
other words, if you have a page served from the domain lolnet.org, it
|
|
will not be possible for it to get data from notmyidea.org.</p>
|
|
<p>Well, it’s possible, using tricks and techniques like
|
|
<a href="http://en.wikipedia.org/wiki/JSONP"><span class="caps">JSONP</span></a>, but that doesn’t work all
|
|
the time (see <a href="#how-this-is-different-from-jsonp">the section below</a>). I
|
|
remember myself doing some simple proxies on my domain server to be able
|
|
to query other’s <span class="caps">API</span>.</p>
|
|
<p>Thankfully, there is a nicer way to do this, namely, “Cross Origin
|
|
Resource-Sharing”, or <a href="http://www.w3.org/TR/cors/"><span class="caps">CORS</span></a>.</p>
|
|
<h2 id="you-want-an-icecream-go-ask-your-dad-first">You want an icecream? Go ask your dad first.</h2>
|
|
<p>If you want to use <span class="caps">CORS</span>, you need the <span class="caps">API</span> you’re querying to support it;
|
|
on the server side.</p>
|
|
<p>The <span class="caps">HTTP</span> server need to answer to the <span class="caps">OPTIONS</span> verb, and with the
|
|
appropriate response headers.</p>
|
|
<p><span class="caps">OPTIONS</span> is sent as what the authors of the spec call a “preflight
|
|
request”; just before doing a request to the <span class="caps">API</span>, the <em>User-Agent</em> (the
|
|
browser most of the time) asks the permission to the resource, with an
|
|
<span class="caps">OPTIONS</span> call.</p>
|
|
<p>The server answers, and tell what is available and what isn’t:</p>
|
|
<p><img alt="The CORS flow (from the HTML5 CORS tutorial)" src="/images/cors_flow.png"></p>
|
|
<ul>
|
|
<li>
|
|
<p>1a. The User-Agent, rather than doing the call directly, asks the
|
|
server, the <span class="caps">API</span>, the permission to do the request. It does so with
|
|
the following headers:</p>
|
|
<ul>
|
|
<li><strong>Access-Control-Request-Headers</strong>, contains the headers the
|
|
User-Agent want to access.</li>
|
|
<li><strong>Access-Control-Request-Method</strong> contains the method the
|
|
User-Agent want to access.</li>
|
|
</ul>
|
|
</li>
|
|
<li>
|
|
<p>1b. The <span class="caps">API</span> answers what is authorized:</p>
|
|
<ul>
|
|
<li><strong>Access-Control-Allow-Origin</strong> the origin that’s accepted. Can
|
|
be * or the domain name.</li>
|
|
<li><strong>Access-Control-Allow-Methods</strong> a <em>list</em> of allowed methods.
|
|
This can be cached. Note than the request asks permission for
|
|
one method and the server should return a list of accepted methods.</li>
|
|
<li><strong>Access-Allow-Headers</strong> a list of allowed headers, for all of
|
|
the methods, since this can be cached as well.</li>
|
|
</ul>
|
|
</li>
|
|
<li>
|
|
<ol>
|
|
<li>The User-Agent can do the “normal” request.</li>
|
|
</ol>
|
|
</li>
|
|
</ul>
|
|
<p>So, if you want to access the /icecream resource, and do a <span class="caps">PUT</span> there,
|
|
you’ll have the following flow:</p>
|
|
<div class="highlight"><pre><span></span><code>> OPTIONS /icecream
|
|
> Access-Control-Request-Methods = PUT
|
|
> Origin: notmyidea.org
|
|
< Access-Control-Allow-Origin = notmyidea.org
|
|
< Access-Control-Allow-Methods = PUT,GET,DELETE
|
|
200 OK
|
|
</code></pre></div>
|
|
|
|
<p>You can see that we have an Origin Header in the request, as well as a
|
|
Access-Control-Request-Methods. We’re here asking if we have the right,
|
|
as notmyidea.org, to do a <span class="caps">PUT</span> request on /icecream.</p>
|
|
<p>And the server tells us that we can do that, as well as <span class="caps">GET</span> and <span class="caps">DELETE</span>.</p>
|
|
<p>I’ll not cover all the details of the <span class="caps">CORS</span> specification here, but bear
|
|
in mind than with <span class="caps">CORS</span>, you can control what are the authorized methods,
|
|
headers, origins, and if the client is allowed to send authentication
|
|
information or not.</p>
|
|
<h2 id="a-word-about-security">A word about security</h2>
|
|
<p><span class="caps">CORS</span> is not an answer for every cross-domain call you want to do,
|
|
because you need to control the service you want to call. For instance,
|
|
if you want to build a feed reader and access the feeds on different
|
|
domains, you can be pretty much sure that the servers will not implement
|
|
<span class="caps">CORS</span>, so you’ll need to write a proxy yourself, to provide this.</p>
|
|
<p>Secondly, if misunderstood, <span class="caps">CORS</span> can be insecure, and cause problems.
|
|
Because the rules apply when a client wants to do a request to a server,
|
|
you need to be extra careful about who you’re authorizing.</p>
|
|
<p>An incorrectly secured <span class="caps">CORS</span> server can be accessed by a malicious client
|
|
very easily, bypassing network security. For instance, if you host a
|
|
server on an intranet that is only available from behind a <span class="caps">VPN</span> but
|
|
accepts every cross-origin call. A bad guy can inject javascript into
|
|
the browser of a user who has access to your protected server and make
|
|
calls to your service, which is probably not what you want.</p>
|
|
<h2 id="how-this-is-different-from-jsonp">How this is different from <span class="caps">JSONP</span>?</h2>
|
|
<p>You may know the <a href="http://en.wikipedia.org/wiki/JSONP"><span class="caps">JSONP</span></a> protocol.
|
|
<span class="caps">JSONP</span> allows cross origin, but for a particular use case, and does have
|
|
some drawbacks (for instance, it’s not possible to do DELETEs or PUTs
|
|
with <span class="caps">JSONP</span>).</p>
|
|
<p><span class="caps">JSONP</span> exploits the fact that it is possible to get information from
|
|
another domain when you are asking for javascript code, using the
|
|
\<script> element.</p>
|
|
<blockquote>
|
|
<p>Exploiting the open policy for \<script> elements, some pages use
|
|
them to retrieve JavaScript code that operates on dynamically
|
|
generated <span class="caps">JSON</span>-formatted data from other origins. This usage pattern
|
|
is known as <span class="caps">JSONP</span>. Requests for <span class="caps">JSONP</span> retrieve not <span class="caps">JSON</span>, but arbitrary
|
|
JavaScript code. They are evaluated by the JavaScript interpreter, not
|
|
parsed by a <span class="caps">JSON</span> parser.</p>
|
|
</blockquote>
|
|
<h2 id="using-cors-in-cornice">Using <span class="caps">CORS</span> in Cornice</h2>
|
|
<p>Okay, things are hopefully clearer about <span class="caps">CORS</span>, let’s see how we
|
|
implemented it on the server-side.</p>
|
|
<p>Cornice is a toolkit that lets you define resources in python and takes
|
|
care of the heavy lifting for you, so I wanted it to take care of the
|
|
<span class="caps">CORS</span> support as well.</p>
|
|
<p>In Cornice, you define a service like this:</p>
|
|
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">cornice</span> <span class="kn">import</span> <span class="n">Service</span>
|
|
|
|
<span class="n">foobar</span> <span class="o">=</span> <span class="n">Service</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s2">"foobar"</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="s2">"/foobar"</span><span class="p">)</span>
|
|
|
|
<span class="c1"># and then you do something with it</span>
|
|
<span class="nd">@foobar</span><span class="o">.</span><span class="n">get</span><span class="p">()</span>
|
|
<span class="k">def</span> <span class="nf">get_foobar</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
|
|
<span class="c1"># do something with the request.</span>
|
|
</code></pre></div>
|
|
|
|
<p>To add <span class="caps">CORS</span> support to this resource, you can go this way, with the
|
|
cors_origins parameter:</p>
|
|
<div class="highlight"><pre><span></span><code><span class="n">foobar</span> <span class="o">=</span> <span class="n">Service</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s1">'foobar'</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="s1">'/foobar'</span><span class="p">,</span> <span class="n">cors_origins</span><span class="o">=</span><span class="p">(</span><span class="s1">'*'</span><span class="p">,))</span>
|
|
</code></pre></div>
|
|
|
|
<p>Ta-da! You have enabled <span class="caps">CORS</span> for your service. <strong>Be aware that you’re
|
|
authorizing anyone to query your server, that may not be what you want.</strong></p>
|
|
<p>Of course, you can specify a list of origins you trust, and you don’t
|
|
need to stick with *, which means “authorize everyone”.</p>
|
|
<h3 id="headers">Headers</h3>
|
|
<p>You can define the headers you want to expose for the service:</p>
|
|
<div class="highlight"><pre><span></span><code><span class="n">foobar</span> <span class="o">=</span> <span class="n">Service</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s1">'foobar'</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="s1">'/foobar'</span><span class="p">,</span> <span class="n">cors_origins</span><span class="o">=</span><span class="p">(</span><span class="s1">'*'</span><span class="p">,))</span>
|
|
|
|
<span class="nd">@foobar</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">cors_headers</span><span class="o">=</span><span class="p">(</span><span class="s1">'X-My-Header'</span><span class="p">,</span> <span class="s1">'Content-Type'</span><span class="p">))</span>
|
|
<span class="k">def</span> <span class="nf">get_foobars_please</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
|
|
<span class="k">return</span> <span class="s2">"some foobar for you"</span>
|
|
</code></pre></div>
|
|
|
|
<p>I’ve done some testing and it wasn’t working on Chrome because I wasn’t
|
|
handling the headers the right way (The missing one was Content-Type,
|
|
that Chrome was asking for). With my first version of the
|
|
implementation, I needed the service implementers to explicitely list
|
|
all the headers that should be exposed. While this improves security, it
|
|
can be frustrating while developing.</p>
|
|
<p>So I introduced an expose_all_headers flag, which is set to True by
|
|
default, if the service supports <span class="caps">CORS</span>.</p>
|
|
<h3 id="cookies-credentials">Cookies / Credentials</h3>
|
|
<p>By default, the requests you do to your <span class="caps">API</span> endpoint don’t include the
|
|
credential information for security reasons. If you really want to do
|
|
that, you need to enable it using the cors_credentials parameter. You
|
|
can activate this one on a per-service basis or on a per-method basis.</p>
|
|
<h3 id="caching">Caching</h3>
|
|
<p>When you do a preflight request, the information returned by the server
|
|
can be cached by the User-Agent so that it’s not redone before each
|
|
actual call.</p>
|
|
<p>The caching period is defined by the server, using the
|
|
Access-Control-Max-Age header. You can configure this timing using the
|
|
cors_max_age parameter.</p>
|
|
<h3 id="simplifying-the-api">Simplifying the <span class="caps">API</span></h3>
|
|
<p>We have cors_headers, cors_enabled, cors_origins, cors_credentials,
|
|
cors_max_age, cors_expose_all_headers … a fair number of
|
|
parameters. If you want to have a specific <span class="caps">CORS</span>-policy for your
|
|
services, that can be a bit tedious to pass these to your services all
|
|
the time.</p>
|
|
<p>I introduced another way to pass the <span class="caps">CORS</span> policy, so you can do
|
|
something like that:</p>
|
|
<div class="highlight"><pre><span></span><code><span class="n">policy</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">(</span><span class="n">enabled</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
|
|
<span class="n">headers</span><span class="o">=</span><span class="p">(</span><span class="s1">'X-My-Header'</span><span class="p">,</span> <span class="s1">'Content-Type'</span><span class="p">),</span>
|
|
<span class="n">origins</span><span class="o">=</span><span class="p">(</span><span class="s1">'*.notmyidea.org'</span><span class="p">),</span>
|
|
<span class="n">credentials</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
|
|
<span class="n">max_age</span><span class="o">=</span><span class="mi">42</span><span class="p">)</span>
|
|
|
|
<span class="n">foobar</span> <span class="o">=</span> <span class="n">Service</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s1">'foobar'</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="s1">'/foobar'</span><span class="p">,</span> <span class="n">cors_policy</span><span class="o">=</span><span class="n">policy</span><span class="p">)</span>
|
|
</code></pre></div>
|
|
|
|
<h2 id="comparison-with-other-implementations">Comparison with other implementations</h2>
|
|
<p>I was curious to have a look at other implementations of <span class="caps">CORS</span>, in django
|
|
for instance, and I found <a href="https://gist.github.com/426829.js">a gist about
|
|
it</a>.</p>
|
|
<p>Basically, this adds a middleware that adds the “rights” headers to the
|
|
answer, depending on the request.</p>
|
|
<p>While this approach works, it’s not implementing the specification
|
|
completely. You need to add support for all the resources at once.</p>
|
|
<p>We can think about a nice way to implement this specifying a definition
|
|
of what’s supposed to be exposed via <span class="caps">CORS</span> and what shouldn’t directly in
|
|
your settings. In my opinion, <span class="caps">CORS</span> support should be handled at the
|
|
service definition level, except for the list of authorized hosts.
|
|
Otherwise, you don’t know exactly what’s going on when you look at the
|
|
definition of the service.</p>
|
|
<h2 id="resources">Resources</h2>
|
|
<p>There are a number of good resources that can be useful to you if you
|
|
want to either understand how <span class="caps">CORS</span> works, or if you want to implement it yourself.</p>
|
|
<ul>
|
|
<li><a href="http://enable-cors.org/">http://enable-cors.org/</a> is useful to get started when you don’t
|
|
know anything about <span class="caps">CORS</span>.</li>
|
|
<li>There is a <span class="caps">W3C</span> wiki page containing information that may be useful
|
|
about clients, common pitfalls etc:
|
|
<a href="http://www.w3.org/wiki/CORS_Enabled">http://www.w3.org/wiki/CORS_Enabled</a></li>
|
|
<li><em><span class="caps">HTML5</span> rocks</em> has a tutorial explaining how to implement <span class="caps">CORS</span>, with
|
|
<a href="http://www.html5rocks.com/en/tutorials/cors/#toc-adding-cors-support-to-the-server">a nice section about the
|
|
server-side</a>.</li>
|
|
<li>Be sure to have a look at the <a href="http://caniuse.com/#search=cors">clients support-matrix for this
|
|
feature</a>.</li>
|
|
<li>About security, <a href="https://code.google.com/p/html5security/wiki/CrossOriginRequestSecurity">check out this page</a></li>
|
|
<li>If you want to have a look at the implementation code, check <a href="https://github.com/mozilla-services/cornice/pull/98/files">on github</a></li>
|
|
</ul>
|
|
<p>Of course, the <span class="caps">W3C</span> specification is the best resource to rely on. This
|
|
specification isn’t hard to read, so you may want to go through it.
|
|
Especially the <a href="http://www.w3.org/TR/cors/#resource-processing-model">“resource processing model” section</a></p>
|
|
</article>
|
|
<footer>
|
|
<a id="feed" href="/feeds/all.atom.xml">
|
|
<img alt="RSS Logo" src="/theme/rss.svg" />
|
|
</a>
|
|
</footer>
|
|
</div>
|
|
</body>
|
|
</html> |