Persona Login

Mike Fisher edited this page Feb 23, 2014 · 4 revisions
Clone this wiki locally

Persona is a password less login system created by Mozilla (read more). For additional information, look at the Mozilla developer site.


First step to integrate it is, creating a library (+helpers) which does all the complicated stuff

// file: application/libraries/Authentication.php
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class Authentication {

    private $CI;
    private $email;

    function __construct() {
        // get super object
        $this->CI =& get_instance();

        // set email
        $this->email = $this->CI->session->userdata('email');
    }

    /** get email
     *
     * \return  email address if set, otherwise false
     */
    public function get_email() {
        return $this->email;
    }

    public function login($assertion) {
        // verify assertion
        $result = $this->verify_assertion($assertion);

        // check for success
        if ($result->status === 'okay') {
            $this->email = $result->email;
            $this->CI->session->set_userdata(array('email' => $result->email));
        }
    }

    public function logout() {
        // logout
        $this->email = false;
        $this->CI->session->sess_destroy();
    }

    private function verify_assertion($assertion) {
        $audience = (empty($_SERVER['HTTPS']) ? 'http://' : 'https://') . $_SERVER['SERVER_NAME'] . ':' . $_SERVER['SERVER_PORT'];
        $postdata = 'assertion=' . urlencode($assertion) . '&audience=' . urlencode($audience);

        $this->CI->load->helper('post');
        $result = post_request('https://verifier.login.persona.org/verify',$postdata);

        return json_decode($result);
    }
}
// file: application/helpers/post_helper.php
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
if (!function_exists('post_request')) {
    function post_request($url, $data, $optional_headers = null)
    {
        $params = array('http' => array(
            'method' => 'POST',
            'content' => $data
        ));
        if ($optional_headers !== null) {
            $params['http']['header'] = $optional_headers;
        }
        $ctx = stream_context_create($params);
        $fp = @fopen($url, 'rb', false, $ctx);
        if (!$fp) {
            throw new Exception("Problem with $url, $php_errormsg");
        }
        $response = @stream_get_contents($fp);
        if ($response === false) {
            throw new Exception("Problem reading data from $url, $php_errormsg");
        }
        return $response;
    }
}

Next we need a controller, which provides the ability to login / logout. This controller will interact with the library.

// file: application/controllers/auth.php
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class Auth extends CI_Controller {

    public function login() {
        if (isset($_POST['assertion']))
            $this->authentication->login($_POST['assertion']);
    }

    public function logout() {
        $this->authentication->logout();
    }
}

Library, helpers and the controller is setup, now let's add the HTML / js stuff.

<head>
    <script src="https://login.persona.org/include.orig.js"></script>
    <script src="jquery.js"></script>
    <link rel="stylesheet" type="text/css" href="buttons.css" />
</head>
<body>
<!-- persona -->
<?php if ($email = $this->authentication->get_email()) { ?>
    <span><?php echo $email; ?></span>
    <a class="persona-button orange" id="signout"><span>Sign out</span></a>
<?php } else { ?>
    <a class="persona-button orange" id="signin"><span>Sign in</span></a>
<?php } ?>
<script type="text/javascript">
    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(); };
    };

    navigator.id.watch({
        <?php
            if (($email = $this->authentication->get_email() ) !== false)
                echo "loggedInUser: '$email',\n";
            else
                echo "loggedInUser: null,\n";
        ?>

        onlogin: function (assertion) {
            $.ajax({
                type: 'POST',
                url: '<?php echo $this->config->item('base_url'); ?>auth/login',
                data: {assertion: assertion},
                success: function(res, status, xhr) {window.location.reload();},
                error: function(res, status, xhr) {alert('unable to login, please try again later');}
            });
        },

        onlogout: function () {
            $.ajax({
                type: 'POST',
                url: '<?php echo $this->config->item('base_url'); ?>auth/logout',
                success: function(res, status, xhr) {window.location.reload();},
                error: function(res, status, xhr) {alert('unable to logout, please try again later');}
            });
        }
    });
</script>
</body>

Note: in a stock installation, the onlogin/onlogout AJAX URLs should include index.php/ before auth/.

Note: CSS button style


The last thing we need to do, is to add our new library as well as the session library to the autoload variable inside the config.

$autoload['libraries'] = array('session', 'authentication');