Using Mozilla Persona with PHP & jQuery

@posted: 2012-10-01 , @author: Chris Cornutt #mozilla #persona #tutorial #javascript #jquery

Mozilla has recently introduced a service that wants to help get rid of passwords for sites all across the web in a simple, easy-to-use service - Mozilla Persona. (Not to be confused with the unfortunately similarly named, soon to be renamed Personas customization feature for their browsers). The Mozilla Persona project in a nutshell:

Persona allows you to sign in to sites using an email address you choose. So, instead of having to manage multiple usernames and passwords across your favorite sites and devices, you’ll have more time to do the important stuff. We’ll manage the details! [...] Persona lets you get started with just your email address; you can add your profile data later, when and where you think it’s appropriate.

If you've ever used something like OpenID the ideas behind Persona will make you feel right at home. The service, launched in a sepearate window, uses the Mozilla service for the authentication based on an email address and password (yes, it doesn't completely do away with them). After signing up, you'll recieve an email to validate the signup and a link to visit to get back to the application.

When you log in there, you then authorize the calling site to be able to access your information. This info, at least initially, only includes your email address and some other metadata (expires time, a unique ID, etc) and something they call an assertion and audience combination:

In Practice - the Frontend

Mozilla has does pretty well when it comes to implmenting this service. They've made it pretty simple to use, but you'll need a little bit of code to back it up. Here's an example of the main HTML page that uses their Javascript for the connection:

<?php
session_start();
?>
<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Mozilla Persona Test</title>
        <script src="https://login.persona.org/include.js"></script>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
        <script src="persona.js"></script>
        <link href="http://twitter.github.com/bootstrap/assets/css/bootstrap.css" rel="stylesheet">
    </head>
    <body>
        <div class="container">
            this is a test<br/>
            <?php
            if (isset($_SESSION['user'])){ echo 'logged in as: '.$_SESSION['user']->email; }
            ?>
            <br/><br/>
            <button class="btn btn-primary" name="login" id="login">Login</button>
            <button class="btn btn-primary" name="logout" id="logout">Logout</button>
        </div>
    </body>
</html>

As you can see, we're including a few different things here:

The next major piece of functionality is the persona.js file that's our custom code. This handles the callbacks Persona throws our way on their custom Javascript objects:

$(function() {
    $('#login').click(function(e){ navigator.id.request(); });
    $('#logout').click(function(e){ navigator.id.logout(); });
    navigator.id.watch({
        loggedInUser: null,
        onlogin: function(assertion) {
            $.post(
                '/auth.php',
                {assertion:assertion},
                function(msg) { console.log('login success!') }
            );
        },
        onlogout: function() {
            $.post(
                '/auth.php',
                {logout:1},
                function(msg) { console.log('logout success!') }
            );
        }
    });
});

One thing to point our first with the above code - with the inclusion of the Persona Javascript file, we get an object in our page: navigator. This is how our code interfaces with theirs. You'll notice the calls to navigator.id.logout and navigator.id.request that are attached to the login/logout buttons. These fire off the requests back to Persona for those actions.

In our example page, when you hit the "Login" button, you'll get a popup like this:

Mozilla Persona Popup

Once you enter in your information and authorize the application, the service calls whatever you have defined in the navigator.id.watch function as your onlogon callback. In our case, it makes a post back to the auth.php script with the Persona assertion as a payload.

In Practice - the Backend

Now we come to the other half of the equation - the backend code to validate the response. In our auth.php file, we're responsible for checking to see if the assertion they've given us is correct. This needs to be POSTed back to ther service along with the audience value for validation. For this, we're going to use the curl functionality that's bundled into most PHP distributions:

<?php
session_start();

if(isset($_POST['assertion'])) {
    $url = 'https://verifier.login.persona.org/verify';
    $c = curl_init($url);
    $data = 'assertion='.$_POST['assertion'].'&audience=http://persona.localhost:80';

    curl_setopt_array($c, array(
        CURLOPT_RETURNTRANSFER  => true,
        CURLOPT_POST            => true,
        CURLOPT_POSTFIELDS      => $data,
        CURLOPT_SSL_VERIFYPEER  => true,
        CURLOPT_SSL_VERIFYHOST  => 2
    ));

    $result = curl_exec($c);
    curl_close($c);

    $response = json_decode($result);
    if ($response->status == 'okay') {
        $_SESSION['user'] = $response;
    }
}
?>

It's a pretty simple piece of code, really - all it does is make a connecton to the verifier.login.persona.org service (HTTPS, of course) and sends along the data to be checked. If everything's good, you'll get a JSON reponse along the lines of:

stdClass Object ( 
    [status] => okay
    [email] => user-email@address-here.com
    [audience] => http://persona.localhost:80
    [expires] => 1348971576253
    [issuer] => login.persona.org
)

You're looking for that okay value in the status to be sure everything's good. You can then use this user information in your application and know they've correctly authorized to your application and approved access.

If you don't have access to cURL on your PHP install, you can also use the PHP streams (thanks to Elizabeth Smith for the implementation):

<?php
$url = 'https://verifier.login.persona.org/verify';
$data = 'assertion='.$_POST['assertion'].'&audience=http://persona.localhost:80';

$params = array(
  'http' => array(
    'method' => 'POST',
    'content' => $data
  ),
  'ssl' => array(
    'verify_peer' => true,
    'verify_host' => true
  )
);
$context = stream_context_create($params);
$result = file_get_contents($url, false, $context);
?>

Logging out is simple too:

<?php
if (isset($_POST['logout'])) {
    session_destroy();
}
?>

Just destroy the session...

Some Drawbacks

There's a few drawbacks with the service as it stands right now:

Behind the Scenes

If you're interested in seeing what's happening behind the scenes, check out this site with full information about how the BrowserID system works. Persona is based on this and is a user-friendly (and developer-friendly) interface to it.

Resources

Check out the other resources below for more details: