This week, I added BrowserID login support to my personal, Clojure-based family web hub (code is available as a Git repository at https://matthias.benkard.de/code/benki.git
). I was quite astonished to see how easy deploying BrowserID is, especially as compared to OpenID or OAuth. In particular, the deployment does not depend on any BrowserID-specific libraries; any sufficiently featureful HTTP client library will do.
This article describes a moderately naïve implementation of BrowserID on top of an existing user session infrastructure. In particular, it does not deal with live-updating pages after login via JavaScript; instead, it simply reloads the current page when the user has completed the login procedure. (Adding live login capability should be easy enough by simply editing the client-side JavaScript login and logout handlers not to do a full page refresh.)
The client-side JavaScript code can be based on the official tutorial, like the following:
// -*- js-indent-level: 2 -*- jQuery(function($) { var loggedIn = function(res) { console.log(res); if (res.returnURI) { window.location.assign(res.returnURI); } else { window.location.reload(true); } }; var loggedOut = function(res) { }; var gotAssertion = function(assertion) { // got an assertion, now send it up to the server for verification if (assertion) { $.ajax({ type: 'POST', url: '/login/browserid/verify', data: { assertion: assertion }, success: function(res, status, xhr) { if (res === null) { loggedOut(); } else { loggedIn(res); } }, error: function(res, status, xhr) { alert("Whoops, I failed to authenticate you! " + res.responseText); } }); } else { loggedOut(); } } $('#browserid').click(function() { navigator.id.get(gotAssertion, {allowPersistent: true}); return false; }); // Query persistent login. var login = $('head').attr('data-logged-in'); if (login === "false") { navigator.id.get(gotAssertion, {silent: true}); } });
Put that code into a file somewhere and reference it from a script
tag after also loading https://browserid.org/include.js
and the jQuery library.
Now you need to program your server to reply to AJAX requests to /login/browserid/verify
. Let's say you're using Noir and storing users' email addresses in a database table. In addition, I am assuming that your login session management works by putting a :user
key into the session map. In this case, your server-side code might look like this (with https://example.com
replaced by your site's URI):
(ns eu.mulk.benki.auth (:use [hiccup core page-helpers] [noir core]) (:require [clojure.java.jdbc :as sql] [com.twinql.clojure.http :as http] [noir.response :as response] [noir.session :as session])) (defpage [:post "/login/browserid/verify"] {assertion :assertion} ;; NB. We delegate verification to browserid.org. ;; Can implement this ourselves sometime if we want. (let [reply (http/post "https://browserid.org/verify" :query {:assertion assertion :audience "https://example.com"} :as :json) result (:content reply) status (:status result) email (:email result)] (if (= (:status result) "okay") (sql/with-connection (sql/transaction (let [record (first (query "SELECT * FROM user_email_addresses WHERE email = ?" email)) user-id (and record (:user record))] (if user-id ;; I'm assuming that your login page stores the desired return ;; URI (i.e., the login page's referrer) using flash-put!. ;; If it doesn't, you might want to do something slightly different ;; here. (let [return-uri (session/flash-get)] (session/put! :user user-id) (response/json {:email email, :returnURI return-uri})) ;; If this is a public site, you might want to create a database ;; record for the new user here. We'll be content in denying ;; authorization instead. {:status 418, :headers {"Content-Type" "text/plain"}, :body "I couldn't find you in the database."})))) {:status 418, :headers {"Content-Type" "text/plain"}, :body "Your BrowserID request could not be validated."})))
And that's basically it. The only thing left is to put a sign-in button (<a href="#" id="browserid">Sign in</a>
) somewhere on all the pages that need sign-in capability.
Comments
Submit a comment
Note: This website uses a JavaScript-based spam prevention system. Please enable JavaScript in your browser to post comments. Comment format is plain text. Use blank lines to separate paragraphs.