pages tagged browseridFeeding the Cloudhttps://feeding.cloud.geek.nz/tags/browserid/Feeding the Cloudikiwiki2021-06-11T20:43:57ZPersona Guiding Principleshttps://feeding.cloud.geek.nz/posts/persona-guiding-principles/
<a href="https://creativecommons.org/licenses/by-sa/4.0/">Creative Commons Attribution-ShareAlike 4.0 International License</a>
2021-06-11T20:43:57Z2016-11-29T19:42:00Z
<p>Given the
<a href="https://wiki.mozilla.org/Identity/Persona_Shutdown_Guidelines_for_Reliers">impending shutdown of Persona</a>
and the lack of a clear alternative to it, I decided to write about some of
the principles that guided its design and development in the hope that it
may influence future efforts in some way.</p>
<h1 id="Permission-less_system">Permission-less system</h1>
<p>There was no need for reliers (sites relying on Persona to log their users
in) to ask for permission before using Persona. Just like a site doesn't
need to ask for permission before creating a link to another site, reliers
didn't need to apply for an API key before they got started and authenticated
their users using Persona.</p>
<p>Similarly, identity providers (the services vouching for their users
identity) didn't have to be whitelisted by reliers in order to be useful to
their users.</p>
<h1 id="Federation_at_the_domain_level">Federation at the domain level</h1>
<p>Just like email, Persona was federated at the domain name level and put
domain owners in control. Just like they can choose who gets to manage
emails for their domain, they could:</p>
<ul>
<li>run their own identity provider, or</li>
<li>delegate to their favourite provider.</li>
</ul>
<p>Site owners were also in <strong>control of the mechanism and policies</strong> involved
in authenticating their users. For example, a security-sensitive corporation
could decide to require 2-factor authentication for everyone or put a very
short expiry on the certificates they issued.</p>
<p>Alternatively, a low-security domain could get away with a much simpler
login mechanism (including a "0-factor" mechanism in the case of
<a href="http://mockmyid.com">http://mockmyid.com</a>!).</p>
<h1 id="Privacy_from_your_identity_provider">Privacy from your identity provider</h1>
<p>While identity providers were the ones vouching for their users' identity,
they didn't need to know the websites that their users are visiting. This is
a potential source of control or censorship and the design of Persona was
able to eliminate this.</p>
<p>The downside of this design of course is that it becomes impossible for an
identity provider to provide their users with a list of all of the sites
where they successfully logged in for audit purposes, something that
centralized systems can provide easily.</p>
<h1 id="The_browser_as_a_trusted_agent">The browser as a trusted agent</h1>
<p>The browser, whether it had native support for the
<a href="https://developer.mozilla.org/Persona/Protocol_Overview">BrowserID protocol</a>
or not, was the agent that the user needed to trust. It connected
reliers (sites using Persona for logins) and identity providers together and
got to see all aspects of the login process.</p>
<p>It also held your private keys and therefore was the only party that could
impersonate you. This is of course a power which it already held by virtue
of its role as the web browser.</p>
<p>Additionally, since it was the one generating and holding the private keys,
your browser could also choose how long these keys are valid and may choose to
vary that amount of time depending on factors like a shared computer
environment or Private Browsing mode.</p>
<p>Other clients/agents would likely be necessary as well, especially
when it comes to interacting with mobile applications or native desktop
applications. Each client would have its own key, but they would all be
signed by the identity provider and therefore valid.</p>
<h1 id="Bootstrapping_a_complex_system_requires_fallbacks">Bootstrapping a complex system requires fallbacks</h1>
<p>Persona was a complex system which involved a number of different actors. In
order to slowly roll this out without waiting on every actor to implement
the BrowserID protocol (something that would have taken an infinite amount of
time), fallbacks were deemed necessary:</p>
<ul>
<li>client-side JavaScript implementation for browsers without built-in support</li>
<li>centralized fallback identity provider for domains without native support
or a working delegation</li>
<li>centralized verifier until local verification is done within
authentication libraries</li>
</ul>
<p>In addition, to lessen the burden on the centralized identity provider
fallback, Persona experimented with a number of bridges to provide
quasi-native support for a few large email providers.</p>
<h1 id="Support_for_multiple_identities">Support for multiple identities</h1>
<p>User research has shown that many users choose to present a different
identity to different websites. An identity system that would restrict them
to a single identity wouldn't work.</p>
<p>Persona handled this naturally by linking identities to email addresses.
Users who wanted to present a different identity to a website could simply use
a different email address. For example, a work address and a personal
address.</p>
<h1 id="No_lock-in">No lock-in</h1>
<p>Persona was an identity system which didn't stand between a site and its
users. It exposed email address to sites and allowed them to control the
relationship with their users.</p>
<p>Sites wanting to move away from Persona can use the email addresses they
have to both:</p>
<ul>
<li>notify users of the new login system, and</li>
<li>allow users to reset (or set) their password via an email flow.</li>
</ul>
<p>Websites should not have to depend on the operator of an identity system in
order to be able to talk to their users.</p>
<h1 id="Short-lived_certificates_instead_of_revocation">Short-lived certificates instead of revocation</h1>
<p>Instead of relying on the correct use of revocation systems, Persona used
short-lived certificates in an effort to simplify this critical part of any
cryptographic system.</p>
<p>It offered three ways to limit the lifetime of crypto keys:</p>
<ul>
<li>assertion expiry (set by the client)</li>
<li>key expiry (set by the client)</li>
<li>certificate expiry (set by the identify provider)</li>
</ul>
<p>The main drawback of such a pure expiration-based system is the increased
window of time between a password change (or a similar signal that the user
would like to revoke access) and the actual termination of all sessions. A
short expirty can mitigate this problem, but it cannot be eliminated
entirely unlike in a centralized identity system.</p>
IndieAuth as a Persona Identity Providerhttps://feeding.cloud.geek.nz/posts/indieauth-as-a-persona-identity-provider/
<a href="https://creativecommons.org/licenses/by-sa/4.0/">Creative Commons Attribution-ShareAlike 4.0 International License</a>
2021-06-11T20:43:57Z2013-06-30T00:30:00Z
<p>Two weeks ago, I attended <a href="http://indiewebcamp.com">Indie Web Camp</a> and had lots of interesting
<a href="https://login.persona.org">Persona</a> conversations. As part of an
<a href="http://indiewebcamp.com/2013/Auth_Jam_Session">Auth Jam session</a>, a few of us explored the idea of adding
non-<a href="https://en.wikipedia.org/wiki/OAuth">OAuth</a>-based authentication mechanisms to
<a href="https://indieauth.com">IndieAuth</a>.</p>
<p>While <a href="http://aaronparecki.com">Aaron</a> and <a href="http://ozten.com">Austin</a> got
<a href="https://github.com/aaronpk/IndieAuth/commit/1e5581b2a91b4d455be1310f833026a8c343ec71">SMS</a> and <a href="https://github.com/aaronpk/IndieAuth/commit/2dd7d58e26b65af53ea0a3c8659e1779dcb7be41">Persona</a> working as IndieAuth providers during one of the Sunday hack
sessions, I wanted to explore the idea of IndieAuth (an implementation of
<a href="http://microformats.org/wiki/RelMeAuth">RelMeAuth</a>) as a
<a href="https://developer.mozilla.org/en-US/docs/Mozilla/Persona/Implementing_a_Persona_IdP">Persona identity provider</a>.</p>
<h1 id="Using_IndieAuth_on_any_Persona-enabled_site">Using IndieAuth on any Persona-enabled site</h1>
<p>The goal of this effort was to allow Indie Web developers to use their
preferred email address with Persona and have IndieAuth authenticate them
using any supported IndieAuth provider (e.g. <a href="https://dev.twitter.com/docs/auth/implementing-sign-twitter">Twitter</a> or <a href="http://developer.github.com/guides/basics-of-authentication/">Github</a>).</p>
<p>This work will help bridge the gap between these two projects and allow
IndieAuth developers to log into more website using their
<a href="http://indiewebcamp.com/How_to_set_up_IndieAuth_on_your_own_domain">hard-earned credentials</a>.</p>
<h1 id="User_setup">User setup</h1>
<p>In order to take advantage of this, users have to:</p>
<ol>
<li>Serve a <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Persona/.well-known-browserid">support document</a> on their <em>email</em> domain declaring
<code>indieauth.com</code> as the authority for their domain.</li>
<li>Include a <a href="http://microformats.org/wiki/rel-me"><code>rel="me"</code></a> link for their preferred
email address on their <em>personal</em> domain.</li>
<li>Advertise their <em>personal</em> domain on their <em>email</em> domain via
<a href="https://tools.ietf.org/html/draft-ietf-appsawg-webfinger">WebFinger</a>.</li>
</ol>
<p>For example, Aaron would use the following support document at
<code>https://parecki.com/.well-known/browserid</code>:</p>
<pre><code>{
"authority": "indieauth.com"
}
</code></pre>
<p>Then he would ensure that there is a <code>rel="me"</code> link on <code>aaronparecki.com</code>
pointing to his preferred email address, <code>aaron@parecki.com</code>:</p>
<pre><code><a href="mailto:aaron@parecki.com" rel="me">Email me</a>
</code></pre>
<p>Finally, because his <em>email</em> domain (<code>parecki.com</code>) is different from his
<em>personal</em> domain (<code>aaronparecki.com</code>), they would need to be linked
together via WebFinger. Thankfully, he found a clever way to do this with
<a href="https://gist.github.com/aaronpk/5846789">a simple Apache rewrite rule and some static files</a>:</p>
<pre><code><Directory /var/www/profile>
Header set Access-Control-Allow-Origin: "*"
</Directory>
RewriteEngine on
RewriteMap unescape int:unescape
RewriteCond ${unescape:%{QUERY_STRING}} resource=acct:(.+)
RewriteRule ^/.well-known/webfinger /profile/${unescape:%1}.json? [last]
</code></pre>
<p>with the following in <code>/var/www/profile/francois@fmarier.org.json</code>:</p>
<pre><code>{
"subject": "acct:francois@fmarier.org",
"links": [
{
"rel": "http://webfinger.net/rel/avatar",
"href": "http://fmarier.org/img/francois_marier.jpg"
},
{
"rel": "http://webfinger.net/rel/profile-page",
"href": "http://fmarier.org/"
},
{
"rel": "me",
"href": "http://fmarier.org/"
}
]
}
</code></pre>
<p>You can test that your setup is working using <a href="https://webfinger.net/">https://webfinger.net/</a>.</p>
<h1 id="Implementation">Implementation</h1>
<p>Aaron and I worked out the the details of how this will work and wrote a
<a href="https://github.com/fmarier/indieauth-personaidp-spec">walkthrough</a> to illustrate it.</p>
<p>The gist of it is that Persona will ask IndieAuth to certify an email
address and IndieAuth will convert that email address to a personal domain
(using WebFinger) before authenticating the user with one of the IndieAuth
providers enabled on that domain.</p>
Prefetching resources to prime the browser cache for the next pagehttps://feeding.cloud.geek.nz/posts/prefetching-resources-to-prime-browser/
<a href="https://creativecommons.org/licenses/by-sa/4.0/">Creative Commons Attribution-ShareAlike 4.0 International License</a>
2021-06-11T20:43:57Z2012-11-26T23:52:00Z
<p>One of the great ways to reduce the perceived load time of pages on your site is to prefetch resources that will be needed while users are busy reading or interacting with the current page.</p>
<p>There are a few ways to ensure that the browser will already have a page in its cache when users visit them. In this particular case, I wanted improve the load time of the <a href="https://login.persona.org/">Persona</a> dialog while users are busy looking at the third-party site they want to login into.</p>
<h3 id="Hidden_images_and_iframes">Hidden images and iframes</h3>
<p>The classic way that many image galleries used to prefetch the "next" image in a series was to include it on the current page as a tiny 1x1 pixel image.</p>
<p>Ignoring the fact that this is now usually blocked because it looks like a <a href="https://en.wikipedia.org/wiki/Web_bug">web bug</a> (2x2 is the way to go now), the problem with this technique is that the browser now has to pull two images at once, which, for slower connections, means that the load time for the initial page will be longer.</p>
<p>A variant of this technique, with the same problem, is to load the next page in a hidden <a href="https://developer.mozilla.org/en-US/docs/HTML/Element/iframe">iframe</a>:</p>
<pre><code><iframe style="display: none" src="/prefetch.html"></iframe>
</code></pre>
<h3 id="XHR_fetch_after_the_page_load">XHR fetch after the page load</h3>
<p>A more modern version of the hidden image technique is to use an <a href="https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest">XHR</a> request to download the resources to the cache once the page has finished loading.</p>
<p>It looks like this:</p>
<pre><code><script>
window.onload = function () {
var xhr = new XMLHttpRequest();
xhr.open('GET', '/css/styles.css', false);
xhr.send(null);
};
</script>
</code></pre>
<p>The main downside of this solution is that you can only prefetch resources on the same server because of browsers' <a href="https://developer.mozilla.org/en-US/docs/Same_origin_policy_for_JavaScript">same-origin restrictions</a> (unless you can use <a href="https://developer.mozilla.org/en-US/docs/HTTP_access_control">HTTP access controls (CORS)</a>). In the case of Persona though, I wanted third-party sites to trigger the prefetching of resources available from a different host: <code>login.persona.org</code>.</p>
<h3 id="HTML5.26.2339.3Bs_prefetch_links">HTML5's prefetch links</h3>
<p>HTML5 has defined a <a href="http://www.whatwg.org/specs/web-apps/current-work/#link-type-prefetch">prefetch relation type</a> for links and similar tags. All you need to do to use it is to put something like this in your page head:</p>
<pre><code><link rel="prefetch" href="/img/background.jpg">
<link rel="prefetch" href="/css/styles.css">
<link rel="prefetch" href="/js/navigation.js">
</code></pre>
<p>These <a href="https://developer.mozilla.org/en-US/docs/Link_prefetching_FAQ">prefetching hints</a> work on HTTPS pages and across domains, but only get loaded when the browser is idle and believes that the network can handle it. In other words, with the exception of a few extra bytes added to the HTML, these hints will not affect the loading time of the page they are on.</p>
<p>Prefetching <strong>will not parse HTML or execute Javascript</strong> though (i.e. it's not recursive). So if you want to put a page and all of its resources into the cache ahead of time, you'll need to have link tags for each child resource in the page head.</p>
<p>While this <a href="https://code.google.com/p/chromium/issues/detail?id=13505">has been implemented in Chrome</a>, it got <a href="https://code.google.com/p/chromium/issues/detail?id=107897">disabled</a> when they added support for the non-standard <a href="https://developers.google.com/chrome/whitepapers/prerender">prerender</a> relation type. So right now, you may need to use both. Note that either of these relations include DNS resolution, which makes <a href="https://developer.mozilla.org/en-US/docs/Controlling_DNS_prefetching">dns-prefetch</a> relations unnecessary.</p>
<h3 id="Dynamic_list_of_resources_to_prefetch">Dynamic list of resources to prefetch</h3>
<p>If you're trying to use prefetch links to get other sites to pre-cache your resources, you're essentially limited to a static list of resources that doesn't change. Unfortunately, that's not how modern sites work. Many sites now host each version of a static file on a unique (e.g. versioned or timestamped) URL with very long <a href="http://developer.yahoo.com/performance/rules.html#expires">expire headers</a>.</p>
<p>Ideally, we would give the browser a list of resources to prefetch. While that feature doesn't exist yet, we can approximate it by <strong>loading a hidden iframe containing the necessary prefetch links</strong>. That way, third-party sites can hardcode the iframe on their pages and you can update the list of resources to prefetch anytime you want.</p>
<p>The great thing about the way Persona works is that we already use an iframe as a <a href="https://github.com/mozilla/jschannel">commnication channel</a> between the site and the code on <code>login.persona.org</code>. So all I had to do in the end was to <a href="https://github.com/fmarier/browserid/commit/fec98808fec87abf86d81d7448a5a59e51b20f8f">add prefetch links to that existing iframe</a>. The result of this is that every site using the <a href="http://identity.mozilla.com/post/28513408358/a-new-api-for-persona">Observer API</a> gets the benefit of prefetching without having to change anything!</p>
<h3 id="Testing">Testing</h3>
<p>If you want to make sure that your prefetching works:</p>
<ol>
<li>Clear your browser cache.</li>
<li>Load the page that has prefetch links.</li>
<li>Take a look at the contents of your cache (<code>about:cache</code> in Firefox and <code>chrome://cache/</code> in Chrome) and look for the resources that should be prefetched.</li>
</ol>
Migrating Libravatar to the Persona Observer APIhttps://feeding.cloud.geek.nz/posts/migrating-libravatar-to-persona/
<a href="https://creativecommons.org/licenses/by-sa/4.0/">Creative Commons Attribution-ShareAlike 4.0 International License</a>
2021-06-11T20:43:57Z2012-08-01T10:40:00Z
<p><a href="https://www.libravatar.org">Libravatar</a> recently upgraded its support for the <a href="https://login.persona.org">Persona</a> authentication system (formerly BrowserID).</p>
<p>Here are some notes on what was involved in migrating to the <a href="http://identity.mozilla.com/post/28513408358/a-new-api-for-persona">Observer API</a> for those who want to do the same on their sites.</p>
<h3 id="Moving_away_from_hidden_forms">Moving away from hidden forms</h3>
<p>Libravatar used to <code>POST</code> the user's assertion to the server-side verification code through a hidden HTML form, just like the <a href="https://github.com/mozilla/browserid-cookbook/blob/6b5292f9cdf4f25cb37dca5dcd91dcdaa3efaee6/python/python.cgi#L26">example Python CGI</a> from the <a href="https://github.com/mozilla/browserid-cookbook">BrowserID cookbook</a>.</p>
<p>This was a reasonable solution when the Persona code was only needed on a handful of pages, but the new API recommends loading the code on all pages where users can be logged in. Therefore, instead of copying this hidden form into the base template and including it on every page, I decided to <a href="https://git.nzoss.org.nz/libravatar/libravatar/commit/8fc6cab6186052dbdb1dee379141114f6b272233">switch to a jQuery.post()-based solution</a> prior to making any other changes.</p>
<p>As a side-effect of interacting with the backend in an <a href="https://en.wikipedia.org/wiki/Ajax_%28programming%29">AJAX</a> call, the error pages were converted to <a href="https://en.wikipedia.org/wiki/JSON">JSON</a> structures and are now displayed in a popup alert.</p>
<h3 id="From_.get.28.29_to_.watch.28.29_and_.request.28.29">From .get() to .watch() and .request()</h3>
<p>By far the biggest change that the new API requires is the move from <a href="https://developer.mozilla.org/en-US/docs/DOM/navigator.id.get">navigator.id.get()</a> to <a href="https://developer.mozilla.org/en-US/docs/DOM/navigator.id.watch">navigator.id.watch()</a> and <a href="https://developer.mozilla.org/en-US/docs/DOM/navigator.id.request">navigator.id.requets()</a>. Instead of asking for an assertion to verify, two callbacks are registered through <code>watch()</code> and identification is triggered through <code>request()</code> (which triggers the <code>onlogin</code> callback).</p>
<p>In the case of Libravatar, this involved:</p>
<ul>
<li>including the <a href="https://login.persona.org/include.js">Persona Javascript shim</a> on every page</li>
<li>moving the assertion verification code from the <code>get()</code> callback to the new <code>onlogin</code> callback</li>
<li>adding a redirection to the <a href="https://www.libravatar.org/account/logout">existing logout page</a> from the new <code>onlogout</code> callback</li>
<li>sharing part of the session state (i.e. which user is currently logged in, if any) with Persona through the <code>loggedInEmail</code> option to <code>watch()</code></li>
</ul>
<p>One thing to note is that while <a href="https://github.com/mozilla/browserid/pull/1806"><code>loggedInEmail</code> is going to be renamed to <code>loggedInUser</code></a>, this change hasn't hit the production version of Persona yet and so I <a href="https://git.nzoss.org.nz/libravatar/libravatar/commit/d39673bda7acfa615b52c9eaba98b565c14bcbf3">reverted to the old name</a> after noticing that <a href="https://github.com/mozilla/browserid/issues/2145"><code>onlogin</code> was unnecessarily called on every page load</a> (a fairly expensive operation given the need to transmit and verify the assertion server-side).</p>
<h3 id="Simplifying_Content_Security_Policy_headers">Simplifying Content Security Policy headers</h3>
<p>The <a href="https://feeding.cloud.geek.nz/2011/11/using-browserid-and-content-security.html">CSP headers</a> that Libravatar used to set on the pages that made use of the Persona Javascript shim now need to be set on every page, which is actually a nice <a href="https://git.nzoss.org.nz/libravatar/libravatar/commit/ae4c4db7859193eed6c85d820d95a1134599f21c">simplication of our Apache config</a>.</p>
<p>Note that if your CSP headers still refer to <code>browserid.org</code>, you must <a href="https://mail.mozilla.org/pipermail/persona-notices/2012/000001.html">change them to <code>login.persona.org</code></a>.</p>
<h3 id="Letting_Persona_know_about_changes_in_login_state">Letting Persona know about changes in login state</h3>
<p>One important change with respect to the old API is that Persona now keeps track of the login state for your site. If Persona finds a discrepancy between its idea of what your state should be and what you are advertising, it will trigger the appropriate callback (<code>onlogin</code> or <code>onlogout</code>) and attempt to resolve the conflict.</p>
<p>This is a very important feature since it will enable features like global logout and persistent cross-device logins, but it does mean that you have to notify Persona whenever your login state changes. If you forget to do this, your state will be automatically changed to match what Persona expects to see.</p>
<p>In Libravatar, this means that when users delete their account, we need to kill their session and <a href="https://git.nzoss.org.nz/libravatar/libravatar/commit/96513befe1668f0759968bd8417eb8c7e8fbde09">tell Persona about it</a> (through <a href="https://developer.mozilla.org/en/DOM/navigator.id.logout">navigator.id.logout()</a>). Otherwise, Persona will log them in again, which will of course cause a new account to be provisioned.</p>
<h3 id="Working_around_the_internal_login_state">Working around the internal login state</h3>
<p>The most complicated part of this migration to the new API was around our "add email" functionality, which lets users add extra emails to their existing Libravatar account.</p>
<p>With the old <code>get()</code> API, adding emails was as easy as requesting additional assertions and verifying them. Under the Observer API, requesting an assertion also changes the internal state that Persona keeps for that website. In practice, it means that after adding a new email in Libravatar, we need to <a href="https://git.nzoss.org.nz/libravatar/libravatar/commit/8b4dff7bbd491366fd75fa1fd7309ff1992e6e4e">update the "logged in" identifier</a> to match the new one. Failure to do this will prompt Persona to invoke the <code>onlogout</code> callback with a different email, which will cause the email to get added to a new Libravatar account instead.</p>
<p>There are also two corner cases where Libravatar needs to fallback to its manual authentication backend and tell Persona that nobody is logged in:</p>
<ul>
<li>when users <a href="https://git.nzoss.org.nz/libravatar/libravatar/commit/0c597856c35c4138613ace28770bf8a1b27756c4">remove from their account</a> the email address that their Persona session is tied to</li>
<li>when users <a href="https://git.nzoss.org.nz/libravatar/libravatar/commit/f2b8772a0ac7766e7f4443c4c999e12914a2e940">unsuccessfully attempt to add</a> an email that's already claimed by another account</li>
</ul>
<p>In any case, despite these hacks, I got it all working in the end which is why I'm hopeful that <a href="https://github.com/mozilla/browserid/issues/2152">we'll find a way to support this use case</a>.</p>
<h3 id="Taking_advantage_of_the_new_features">Taking advantage of the new features</h3>
<p>The most visible feature that the new API brings (as <a href="https://git.nzoss.org.nz/libravatar/libravatar/commit/bccf97a3709dec79d6ac6d7848c9f8851f31f19f">options to <code>request()</code></a>) is the <a href="http://identity.mozilla.com/post/27122712140/new-feature-adding-your-websites-name-and-logo-to-the">ability to add your name and logo</a> to the Persona popup window:</p>
<p><img alt="" src="https://feeding.cloud.geek.nz/posts/migrating-libravatar-to-persona/persona_branded_popup.png" /></p>
<p>The second feature <a href="https://git.nzoss.org.nz/libravatar/libravatar/commit/984e51875b215156b4b85d465bfd0b17ef3c9628">I tried to enable</a> on Libravatar is the new <a href="http://identity.mozilla.com/post/27914354400/improvements-to-the-first-time-sign-up-flow">redirectTo</a> <code>request()</code> option. Unfortunately, I had to <a href="https://git.nzoss.org.nz/libravatar/libravatar/commit/b6d5919f9aaa31f8ba40deecec64aeafb5189632">revert this change</a> since in our case, going straight to the profile page causes the <a href="https://docs.djangoproject.com/en/1.2/topics/auth/#the-login-required-decorator">@login_required</a> Django decorator to run before the <code>onlogin</code> callback has a chance to set the session cookie.</p>
<p>In any case, redirecting to the login page already worked and so Libravatar probably doesn't need to make use of this Persona feature.</p>
<h3 id="Conclusion">Conclusion</h3>
<p>This migration was harder than I was expecting, but I'm confident that it will become easier in the next few weeks as the <a href="https://github.com/mozilla/browserid">implementation</a> is polished and <a href="https://developer.mozilla.org/en-US/docs/browserid">documentation</a> refreshed. I'm very excited about the Observer API because of the new security features and native integration it will enable.</p>
<p>If you use Persona on your site, make sure you sign up to the new <a href="https://mail.mozilla.org/listinfo/persona-notices">service announcement</a> list.</p>
Using BrowserID and Content Security Policy togetherhttps://feeding.cloud.geek.nz/posts/using-browserid-and-content-security/
<a href="https://creativecommons.org/licenses/by-sa/4.0/">Creative Commons Attribution-ShareAlike 4.0 International License</a>
2021-06-11T20:43:57Z2011-11-01T09:40:00Z
<p>While looking into why <a href="https://browserid.org/">BrowserID</a> logins on <a href="https://www.libravatar.org/">Libravatar</a> didn't work on <a href="http://firefox.com/">Firefox</a>, I remembered that I had recently added <a href="https://developer.mozilla.org/en/Security/CSP">Content Security Policy</a> headers. Here's what I had to do to make BrowserID work on a CSP-enabled site.</p>
<h3 id="Create_a_hidden_form_and_a_login_link">Create a hidden form and a login link</h3>
<p>This is what the login button looked like before CSP:</p>
<pre><code><form id="browserid-form" action="/account/login_browserid" method="post">
<input id="browserid-assertion" type="hidden" name="assertion" value="">
<input style="display: none" type="submit">
</form>
<a href="javascript:try_browserid()">Login with BrowserID</a>
<script type="text/javascript">
function try_browserid() {
navigator.id.getVerifiedEmail(function(assertion) {
if (assertion) {
document.getElementById('browserid-assertion').setAttribute('value', assertion);
document.getElementById('browserid-form').submit();
}
});
}
</script>
</code></pre>
<p>The hidden form is there because the assertion needs to be sent to the application via <code>POST</code> to avoid leaking it out, but otherwise the code is pretty straightforward.</p>
<p>Now of course, with CSP turned ON, there are two problems:</p>
<ul>
<li>links <a href="https://developer.mozilla.org/en/Security/CSP/Default_CSP_restrictions#javascript:.C2.A0URIs">cannot use</a> <code>javascript:</code> URIs</li>
<li>inline Javascript is <a href="https://developer.mozilla.org/en/Security/CSP/Default_CSP_restrictions#Internal_.3Cscript.3E_nodes">forbidden</a></li>
</ul>
<p>So we can start by converting the login link to:</p>
<pre><code><a id="browserid-link" href="#">Login with BrowserID</a>
<script src="browserid_stuff.js" type="text/javascript">
</code></pre>
<p>then moving the <code>try_browserid()</code> function to a separate file to be served from the same domain and finally hooking it up to the <code>try_browserid()</code> function using Javascript (in that same <code>browserid_stuff.js</code> file):</p>
<pre><code>var link = document.getElementById('browserid-link');
link.onclick = try_browserid;
link.addEventListener('click', try_browserid, false);
</code></pre>
<h3 id="Exposing_the_right_X-Content-Security-Policy_header">Exposing the right X-Content-Security-Policy header</h3>
<p>In order to load the Javascript code from <code>browserid.org</code>, we need the following as part of the policy:</p>
<pre><code>script-src https://browserid.org
</code></pre>
<p>but that's not enough since the BrowserID login form seems to use some sort of <code><iframe></code> trick and so we need to add this extra permission as well:</p>
<pre><code>frame-src https://browserid.org
</code></pre>
<p>Here is the final policy I ended up setting (using Apache <a href="http://httpd.apache.org/docs/2.2/mod/mod_headers.html">mod_headers</a>) for the Libravatar login page:</p>
<pre><code><Location /account/login>
Header set X-Content-Security-Policy: "default-src 'self'; frame-src 'self' https://browserid.org ; script-src 'self' https://browserid.org"
</Location>
</code></pre>