Adding the Persona login system to your site takes just five steps:
- Include the Persona JavaScript library on your pages.
- Add “login” and “logout” buttons.
- Watch for login and logout actions.
- Verify the user’s credentials.
- Review best practices.
You should be able to get up and running in a single afternoon, but first things first: If you’re going to use Persona on your site, please take a moment and subscribe to the Persona notices mailing list. It’s extremely low traffic, only being used to announce changes or security issues which may adversely impact your site.
Step 1: Include the Persona library
Persona is designed to be browser-neutral and works well on all major desktop and mobile browsers. This is possible thanks to the cross-platform Persona JavaScript library. Once this library is loaded in your page, the Persona functions you need (watch()
, request()
, and logout()
) will be available in the global navigator.id
object.
To include the Persona JavaScript library, you can place this script
tag in the head of your page:
<script src="https://login.persona.org/include.js"></script>
You must include this on every page which uses navigator.id
functions. Because Persona is still in development, you should not self-host the include.js
file.
Step 2: Add login and logout buttons
Because Persona is designed as a DOM API, you must call functions when a user clicks a login or logout button on your site. To open the Persona dialog and prompt the user to log in, you should invoke navigator.id.request()
. For logout, invoke navigator.id.logout()
.
For example:
var signinLink = document.getElementById('signin'); if (signinLink) { signinLink.onclick = function() { navigator.id.request(); }; }; var signoutLink = document.getElementById('signout'); if (signoutLink) { signoutLink.onclick = function() { navigator.id.logout(); }; };
What should those buttons look like? Check out our Branding Resources page for premade images and CSS-based buttons!
Step 3: Watch for login and logout actions
For Persona to function, you need to tell it what to do when a user logs in or out. This is done by calling the navigator.id.watch()
function and supplying three parameters:
-
The
loggedInEmail
of your site’s current user, ornull
if none. You should generate this dynamically when you render a page. -
A function to invoke when an
onlogin
action is triggered. This function is passed a single parameter, an “identity assertion,” which must be verified. -
A function to invoke when an
onlogout
action is triggered. This function is not passed any parameters.
Note: You must always include both onlogin
and onlogout
when you call navigator.id.watch()
.
For example, if you currently think Bob is logged into your site, you might do this:
var currentUser = '[email protected]'; navigator.id.watch({ loggedInEmail: currentUser, onlogin: function(assertion) { // A user has logged in! Here you need to: // 1. Send the assertion to your backend for verification and to create a session. // 2. Update your UI. $.ajax({ type: 'POST', url: '/auth/login', // This is a URL on your website. data: {assertion: assertion}, success: function(res, status, xhr) { window.location.reload(); }, error: function(res, status, xhr) { alert("login failure" + res); } }); }, onlogout: function() { // A user has logged out! Here you need to: // Tear down the user's session by redirecting the user or making a call to your backend. $.ajax({ type: 'POST', url: '/auth/logout', // This is a URL on your website. success: function(res, status, xhr) { window.location.reload(); }, error: function(res, status, xhr) { alert("logout failure" + res); } }); } });
In this example, both onlogin
and onlogout
are implemented by making an asynchronous POST
request to your site’s backend. The backend then logs the user in or out, usually by setting or deleting information in a session cookie. Then, if everything checks out, the page reloads to take into account the new login state.
You can, of course, use AJAX to implement this without reloading or redirecting, but that’s beyond the scope of this tutorial.
You must call this function on every page with a login or logout button. To support Persona enhancements like automatic login and global logout for your users, you should call this function on every page of your site.
Step 4: Verify the user’s credentials
Instead of passwords, Persona uses “identity assertions,” which are kind of like single-use, single-site passwords combined with the user’s email address. When a user wants to log in, your onlogin
callback will be invoked with an assertion from that user. Before you can log them in, you must verify that the assertion is valid.
It’s extremely important that you verify the assertion on your server, and not in JavaScript running on the user’s browser, since that would be easy to forge. The example above handed off the assertion to the site’s backend by using jQuery’s $.ajax()
helper to POST
it to /api/login
.
Once your server has an assertion, how do you verify it? The easiest way is to use a helper service provided by Mozilla. Simply POST
the assertion to https://verifier.login.persona.org/verify
with two parameters:
assertion
: The identity assertion provided by the user.audience
: The hostname and port of your website. You must hardcode this value in your backend; do not derive it from any data supplied by the user.
For example, if you’re example.com
, you can use the command line to test an assertion with:
$ curl -d "assertion=<ASSERTION>&audience=https://example.com:443" "https://verifier.login.persona.org/verify"
If it’s valid, you’ll get a JSON response like this:
{ "status": "okay", "email": "[email protected]", "audience": "https://example.com:443", "expires": 1308859352261, "issuer": "eyedee.me" }
You can learn more about the verification service by reading The Verification Service API. An example /api/login
implementation, using Python, the Flask web framework, and the Requests HTTP library might look like this:
@app.route('/api/login', methods=['POST']) def login(): # The request has to have an assertion for us to verify if 'assertion' not in request.form: abort(400) # Send the assertion to Mozilla's verifier service. data = {'assertion': request.form['assertion'], 'audience': 'https://example.com:443'} resp = requests.post('https://verifier.login.persona.org/verify', data=data) # Did the verifier respond? if resp.ok: # Parse the response verification_data = json.loads(resp.content) # Check if the assertion was valid if verification_data['status'] == 'okay': # Log the user in by setting a secure session cookie session.update({'email': verification_data['email']}) return resp.content # Oops, something failed. Abort. abort(500)
The session management is probably very similar to your existing login system. The first big change is in verifying the user’s identity by checking an assertion instead of checking a password. The other big change is ensuring that the user’s email address is available for use as the loggedInEmail
parameter to navigator.id.watch()
.
Logout is simple: you just need to remove the user’s session cookie.
Step 5: Review best practices
Once everything works and you’ve successfully logged into and out of your site, you should take a moment to review best practices for using Persona safely and securely.
If you're making a production-ready site, you may want to write integration tests that simulate logging a user in and out of your site using BrowserID. To facilitate this action in Selenium, consider using the bidpom library. The sites mockmyid.com and personatestuser.org may also be helpful.
Lastly, don’t forget to sign up for the Persona notices mailing list so you’re notified of any security issues or backwards incompatible changes to the Persona API. The list is extremely low traffic: it’s only used to announce changes which may adversely impact your site.