Authenticate Users with Mozilla Persona

Authenticate Users with Mozilla Persona

In this article we’ll see how to use the new, future-ready distributed authentication system Mozilla Persona. As the name says, Persona is created and sponsored by the Mozilla Foundation and presents itself as an easier and more secure alternative to OpenID and OAuth.

The Theory Behind Mozilla Persona

Mozilla Persona is built upon the BrowserID technology also developed by Mozilla. You can find an expanded explanation of the concepts in the article How BrowserID Works by Lloyd Hilaiel, but I will condense the key points here.

  • An email address is an identity and it’s verified by the email provider (such as Gmail, Yahoo, etc.) who serves the role of Primary Identity Authority (or simply primary).
  • Authentication takes place in the browser. There is no transaction with third-party sites so it’s efficient and privacy-protecting. The browser has the role of Implementation Provider (IP).

In the ideal (future) workflow, the browser implements the navigator.id API natively and email providers are able to act as the primary authority for their users. We can implement a functional workflow today by using the HTML5 implementation provided by Mozilla and the browserid.org server as a Secondary Identity Authority to verify email addresses.

Let’s say that Mark wants to sign in to the site myfavoritebeer.org using the email address mr.nice.guy@gmail.com. There are three steps to follow:

1. Certificate provisioning
If it’s the first time the email address is used as an identity, the browser sends Mark to a secure authentication page at GMail. The browser then generates a key pair (using navigator.id.genKeyPair()) and sends the public key to GMail.

GMail sends back a certificate, a signed package containing the email address, the user’s public key and an expiration date. It’s signed with Gmail’s private key and packaged as a JSON Web Token (JWT), a signed JSON Object.

The certificate is stored in the browser keychain along with the private key (using navigator.id.registerVerifiedEmail()) and is used in the next step.

If an email provider does not support BrowserID natively, the email is verified by the browserid.org server as a secondary authority. Basically, you receive an email with a link to visit in order to confirm your identity.

2. Assertion generation
The browser generates an assertion, a JWT package proving that Mark owns mr.nice.guy@gmail.com. The package contains the target site (myfavoritebeer.org), an expiration date, and the certificate issued by GMail. It’s all signed with Mark’s private key and sent to myfavoritebeer.org for verification.

If the browser does not support BrowserID natively then the operation is performed by fallback JavaScript code provided by Mozilla.

3. Assertion verification
The site myfavoritebeer.org receives and verifies Mark’s request. First it checks the validity period, then it retrieves GMail’s public key to verify the certificate. The certificate contains Mark’s public key which is used to verify the assertion.

This step assumes GMail supports BrowserID and shares its public keys. At present, the certificate contains an issued-by property pointing to the secondary authority that generated it.

If it all goes well Mark is logged in to the site until the assertion expires.

Adding Persona Authentication to Your Site

The following example uses and extends the information and code from the official Mozilla Persona Quick Setup article in order to obtain a basic yet fully functional system. The buttons use the CSS styles created for Mozilla by Sawyer Hollenshead. The code for the example can be downloaded from the PHPMaster GitHub account.

First here’s a basic index.php file:

<?php
// Check if the user is logged in
$user = null;
if (!empty($_COOKIE['auth'])) {
    $user = $_COOKIE['auth'];
}
?>
<!DOCTYPE html>
<html>
 <head>
  <meta charset="utf-8">
  <title>My Favorite Beer - Mozilla Persona Test Code</title>
  <link rel="stylesheet" href="css/persona/persona-buttons.css">
 </head>
 <body>
  <h1>My Favorite Beer: a test page for Mozilla Persona</h1>
<?php
if (!empty($user)) {
?>
  <p>Hello <strong><?=$user?></strong>!</p>
  <a href="#" class="persona-button persona-signout"><span>Sign out</span></a>
<?php
}
else {
?>
  <a href="#" class="persona-button persona-signin"><span>Sign in with Persona</span></a>
<?php
}
?>
  <!--jQuery Library from Google-->
  <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>

  <!--Persona Library from Mozilla-->
  <script src="https://login.persona.org/include.js"></script>

  <!-- Our custom code -->
  <script src="js/mfb.js"></script>
 </body>
</html>

I’m storing the authentication info (the email address only) in a cookie variable called auth. The first few PHP lines check this cookie and set a local $user variable accordingly.

The $user variable is checked in the main body: if the user is logged in a welcome message is displayed followed by the “Sign out” button. If not the “Sign in” button is displayed by default.

At the bottom of the page you can find the jQuery library, the Mozilla Persona fallback library, and our custom JavaScript code file mfb.js. In this file I’ve set our custom hooks that link the backend to the Persona API.

(function(window, $, undefined) {
    // See: http://www.quirksmode.org/js/cookies.html
    window.readCookie = function(name) {
        var nameEQ = name + '=';
        var ca = document.cookie.split(';');
        for(var i = 0; i < ca.length; i++) {
            var c = ca[i];
            while (c.charAt(0) == ' ') {
                c = c.substring(1, c.length);
            }
            if (c.indexOf(nameEQ) == 0) {
                return c.substring(nameEQ.length, c.length);
            }
        }
        return null;
    };

    // Read auth info (the email address) from cookie
    var currentUser = window.readCookie('auth');

    // If the user is not logged in set the default to null
    if (!currentUser) {
        currentUser = null;
    }

    // The returned value must be URL-decoded
    if (currentUser != null) {
        currentUser = decodeURIComponent(currentUser);
    }

    navigator.id.watch({
        loggedInUser: currentUser,
        onlogin: function(assertion) {
            // A user has logged in! Here you need to send the
            // assertion to your backend for verification and to
            // create a session and then update your UI.
            $.ajax({
                type: 'POST',
                url: 'login.php', // This is a URL on your website.
                data: {assertion: assertion},
                success: function(res, status, xhr) {
                    window.location.reload();
                },
                error: function(xhr, status, err) {
                    alert('Login failure: ' + err);
                }
            });
        },
        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. Also, make sure loggedInUser will get
            // set to null on the next page load.
            $.ajax({
                type: 'POST',
                url: 'logout.php', // This is a URL on your website.
                success: function(res, status, xhr) {
                    window.location.reload();
                },
                error: function(xhr, status, err) {
                    alert('Logout failure: ' + err);
                }
            });
        }
    });

    $(document).ready(function(){

        $('a.persona-signin').click(function(e) {
            navigator.id.request();
            e.preventDefault();
        });
        $('a.persona-signout').click(function(e) {
            navigator.id.logout();
            e.preventDefault();
        });
    });
})(window, jQuery);

First there’s a quick utility function that reads cookies. Then it tries to read the currentUser variable from the cookie. It’s URL-encoded so we need the built-in decodeURIComponent() function to obtain the unencoded email address. It’s important that this variable contains either the user’s valid email address or the JavaScript null value; any other value will cause an infinite reload loop.

Next we “watch” the required onlogin and onlogout actions by calling the navigator.id.watch() API and passing the details of our PHP backend. The login.php and logout.php scripts are called via Ajax POST by this API.

The logout script is pretty simple: it unsets the auth cookie and exits. In a real world application it’s good practice to include a CSRF protection token and perform other security checks.

The login script is responsible for validating the user’s identity with the email provider. In a real app we would also check the user’s identity inside the application’s database.

The main flow is:

<?php
// Call the BrowserID API
$response = PersonaVerify();

// If the authentication is successful set the auth cookie
$result = json_decode($response, true);
if ('okay' == $result['status']) {
    $email = $result['email'];
    setcookie('auth', $email);
}

// Print the response to the Ajax script
echo $response;

First it calls a utility function that encapsulates all the business logic (we’ll see it soon). If the identity check is positive then the auth cookie is set and other post-auth code should be called here; in any case the raw JSON response is returned to the calling script.

The function PersonaVerify() is responsible for the assertion and certificate verification. It’s important that this step is performed server-side to limit the possibility of malicious code injection. Normally in this function we should retrieve the public key from the identity authority and verify it ourselves, but since the specifications are still in development and the BrowserID protocol is not implemented by email providers, the safe way to do it is perform remote validation with Persona verification service.

<?php
function PersonaVerify() {
    $url = 'https://verifier.login.persona.org/verify';

    $assert = filter_input(
        INPUT_POST,
        'assertion',
        FILTER_UNSAFE_RAW,
        FILTER_FLAG_STRIP_LOW|FILTER_FLAG_STRIP_HIGH
    );

    $scheme = 'http';
    if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != "on") {
        $scheme = 'https';
    }
    $audience = sprintf(
        '%s://%s:%s',
        $scheme,
        $_SERVER['HTTP_HOST'],
        $_SERVER['SERVER_PORT']
    );

    $params = 'assertion=' . urlencode($assert) . '&audience='
        . urlencode($audience);

    $ch = curl_init();
    $options = array(
        CURLOPT_URL => $url,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST => 2,
        CURLOPT_POSTFIELDS => $params
    );

    curl_setopt_array($ch, $options);
    $result = curl_exec($ch);
    curl_close($ch);
    return $result;
}

The code for this function comes from the Mozilla Persona developers area.

The assertion parameter is taken from the Ajax POSTed variables and sanitized. The audience parameter is essentially our site URL in the format scheme://host:port, so it’s calculated using the $_SERVER superglobal.

Both parameters are encoded and sent using a cURL HTTP POST call to the Persona Verification Service. The result is a JSON string that is returned to the calling script for parsing.

Summary


In a few steps we have easily integrated a new authentication system for our applications. Thanks to the fallback code we can start using it now and be ready when it will be supported natively by both the browser and the email providers.

If you want a deeper knowledge of the topic you can read the Persona Official Documentation and this article that highlights the differences between Persona and OpenID.

That’s all for now, happy coding!

Image via Fotolia

Web designer, code poet, guitarist and music composer. Vito Tardia (a.k.a. Ragman) as a child used to disassemble things... and growing up he also learned how to reassemble them! He began to work on the Internet in 1996, breaking up web sites with MS Notepad and Netscape Navigator. Two years later he started to build his own sites as a freelancer. His services include web design, digital graphics, web application development, but also consultancy about web 2.0, mobile web, social networks and SEO. In the past he also did audio/video production/post-production and soundtrack composition.

Visit Site Twitter

20 Responses to “Authenticate Users with Mozilla Persona”

  1. eranga December 1, 2012 at 7:26 pm

    Nice article. However, if this is only support by FF, I wonder how can I use in real world.

    • Lars Gunther December 3, 2012 at 11:02 am

      There is a JavaScript fallback that makes Persona work in any (JS-enabled) browser.

  2. violacase December 4, 2012 at 8:16 pm

    Very interesting. Thanks for making time writing this article. I would really appreciate it if you are willing to write follow up articles if new developments arrive!

    • Vito Tardia December 5, 2012 at 2:33 am

      I hope so, I have a personal interest in it :)

  3. eduardocasas December 5, 2012 at 3:24 am

    A few weeks ago I developed an example powered with jQuery and PHP that works in every browser with JS enabled. If you want to take a look you can find it in github:
    https://github.com/eduardocasas/Mozilla-Persona-Simple-App-Example

    • Vito Tardia December 7, 2012 at 4:42 am

      I’ve just tried, well done Eduardo!

  4. kenneth December 10, 2012 at 2:29 am

    Good article … but CURL is not my favourite … any other way of implementing this without curl?

    • Vito Tardia December 11, 2012 at 1:55 am

      Thanks Kenneth. If you write or find a good wrapper library cURL is the easiest way. In alternative you can use PHP file_get_contents() with the native streams or built a client using sockets. The latter is a good programming exercise but it’s quite complicated.

    • Timothy Boronczyk December 12, 2012 at 7:29 pm

      I believe point number 5 of 5 Inspiring and Useful PHP Snippets is what you’re looking for — Using file_get_contents as a cURL Alternative.

  5. James Dempsey December 11, 2012 at 10:47 pm

    Absolutely brilliant walkthrough the new system. I found this resource far more engaging than Mozilla’s explanation, and will definitely come back to this site when further developing my php sites.

    • sudheesh C R December 24, 2012 at 11:07 pm

      Hello friends,
      I was trying to execute above mentioned persona integrated sample application. But after login through persona it redirects to same page. Plz anbody can tell me the solution

      • Vito Tardia December 27, 2012 at 1:55 am

        Hi subheesh,

        this example always reload the same page after login or logout. The AJAX success function performs a window.location.reload(). You should replace this statement with your custom url.

        Hope that helps

        Vito

        • Sudheesh January 1, 2013 at 11:46 pm

          Thanks a lot Sir. I wanted to have more discussion on regarding this. Sir, I have send you the contact mail on your website.

          • Sudheesh January 2, 2013 at 1:53 am

            Hello Vito,
            As per your suggestion I have changed the window.location=”page1.php” in onlogin method of persona but how to signout in persona. In your example even though I login through persona I cant see Hello and my name.
            Thanks

  6. John C December 28, 2012 at 3:42 pm

    Thanks for the demo site, which I’ve tried out. This is the 1st demo of Persona I’ve come close to understanding.
    I’m fairly new to PHP but would like a reliable user login system that only allows in memmbers.

    I have a function ValidUser($email), which looks up the email of the user logging in, against the user table in the Database and returned TRUE or FAlse
    How could I fit this into your code?
    eg this fails and I get the infinite loop in the browser.

    $result = json_decode($response, true);
    if (‘okay’ == $result['status']) {
    $email = $result['email'];
    if (ValidateEmail($email)) {
    setcookie(‘auth’, $email);
    echo $response;
    }
    else
    {
    setcookie(‘auth’,”", time() – 3600);
    echo $response;
    }
    }

    • Vito Tardia January 2, 2013 at 2:40 am

      Hi John, I didn’t try it out but I think you get the infinite loop because of your “else” statement which sets an empty string value. Any value other than valid email or Javascript null causes this loop.

  7. sudheesh C R January 3, 2013 at 1:58 am

    Hello sir ,
    Thank you for your reply. Sir I have changed window.location=”page1.php” in onlogin method,it works fine. But how can I use persona signout. I tryd ur example, where once I signin through persona and again if i reload the same page I cannot see Hello $user message and signout button.
    waiting for your reply
    Thank you.

    • Vito Tardia January 4, 2013 at 5:03 am

      Hi sudheesh,
      maybe something went wrong, I suggest you to enable error reporting on you php server and check the javascript to see whats going on. I’ve seen you message, if you prefer we can continue the conversation in private mail.


      Vito

      • Sudheesh January 5, 2013 at 3:58 am

        Sure sir , sudheesh088@gmail.com is my official Email ID. May I get your Email ID plz ?, so that we can have a clear discussion on regarding this.
        Thank you

  8. Ian Monroe May 8, 2013 at 3:14 pm

    I had the infinite loop problem on logout as well (though the rest of your example was quite helpful.)
    I’ve managed to solve it by setting the “auth” cookie to an empty string via the javascript callback. That fixed it right up.
    Hopefully that’ll help anyone else who’s having this problem.

Leave a comment

© 1998-2012 SitePoint Pty. Ltd. All Rights Reserved