Single Sign On tutorial

To give you a head start a quick glance over terminology used in this tutorial:

User-owned info or data
Single sign on includes authorizing third-party applications to make API requests on behalf of a user. This could potentially lead to unwarranted access to sensitive information. When we speak of info or data in this document, we almost exclusively mean info or data sensitive from the perspective of an individual user (e.g. profile details, private group memberships).
Client
In OAuth 2.0 terminology a client is a piece of software requesting protected resources on behalf of the user owning these resources. In other words: your Speakap Application is an OAuth client trying to retrieve sensitive information from a Speakap user.
App Access Token
Special access token used by Speakap Applications. It represents the app’s credentials and tells the Speakap API on behalf of which application requests are being made.
Authorization Code
A short-lived token that can be redeemed for an access token. It represents a grant issued by a user for an app to access certain user owned data.

Introduction

In the Hello World tutorial we learned that the Speakap API can be reached from both inside the browser — client-side — as well as from another webserver — server-side. Both these methods send an OAuth 2.0 access token to the API, although in the first case this is abstracted away by our JavaScript API. The access token sent from the server is called an App Access Token and is comprised of the Application’s ID and Secret concatenated with a single underscore:

[App ID]_[Secret]

This tutorial will cover a third method with a couple of added benefits over the other two:

  1. Your application will be opened in an external window
  2. Single Sign On! Speakap can be used to authenticate your users
  3. An access token can be retrieved directly from the Speakap Authenticator
  4. Users will give explicit permission to access their info

External position

If you want to start using Speakap as your SSO Provider your use-case probably falls into one of two categories:

Existing application:
 A lot of your users are Speakap users and this will be of great convenience to them.
New application:
 All your users will be Speakap users and this way you don’t need to bother creating an intricate registration and authentication flow.

In the first case your application most likely is already web-accessible, so the external position is a prerequisite. In the second case it’s less of a necessity unless it has its own landing page and/or look and feel.

The external position is mutually exclusive with the main position:

{
    "name": {
        "en-US": "Hello World SSO"
    },
    "icon": "http://developer.speakap.io/world-icon.png",
    "permissions": [
        "get_profiles"
    ],
    "entries": [
        {
            "position": "external",
            "devices": "all",
            "url": "https://yourdomain.com/app/folder/helloworld_sso.html",
            "icon": "\uf0ac",
            "label": {
                "en-US": "Hello World"
            }
        }
    ]
}

Network context

Since API version 1.4.4 the external URL can automatically be appended by two query parameters providing contextual information about the network involved. Auto-appending can be enabled by setting the urlAppendNetwork property to true:

{
    "position": "external",
    "devices": "all",
    "url": "https://yourdomain.com/app/folder/helloworld_sso.php",
    "urlAppendNetwork": true,
    "icon": "\uf0ac",
    "label": {
        "en-US": "Hello World"
    }
}

The parameters added to the URL are:

parameter description
network_eid
Unique identifier of the network
EID that can be used when querying our API
network_auth_url
Auth endpoint of the network
Base URL from which to initiate an SSO-flow

Single Sign On

Now it gets a little more complicated. Somehow you need to know if your users are logged in with Speakap or not. And if they are, you want to know their Speakap identity (i.e. their login or EID). We can discern four separate login flows:

  1. User opens Speakap; logs in; visits your web app
  2. User opens your appliction first; chooses “Login with Speakap”; logs in with Speakap; returns to your web app
  3. Logged-in user opens Speakap; visits your web app and has direct access
  4. Logged-in user opens your application first; chooses “Login with Speakap” and gains immediate access

Redirection

In all the above flows HTTP Redirection makes sure the Speakap user always ends up back where it started. In both successful and erroneous authentication attemps they are redirected back to your application.

The manifest, the redirectUris property to be more specific, can be used to whitelist URLs acceptable for redirection.

{
    "name": {
        "en-US": "Hello World SSO"
    },
    "icon": "http://developer.speakap.io/world-icon.png",
    "permissions": [
        "get_profiles"
    ],
    "entries": [
        {
            "position": "external",
            "devices": "all",
            "url": "https://yourdomain.com/app/folder/helloworld_sso.php",
            "icon": "\uf0ac",
            "label": {
                "en-US": "Hello World"
            }
        }
    ],
    "redirectUris": [
        "https://yourdomain.com/app/folder/helloworld_sso.php"
    ]
}

Auth endpoint

To initiate authentication through Single Sign On, the endpoint below is made available:

https://[subdomain].speakap.com/auth

In the query string the following four parameters are accepted:

client_id App ID Required
redirect_uri One of the URLs whitelisted in the manifest Required
scope OAuth 2.0 scope: we’ll talk about this later. For now, just use “profile.basic.read” Required
state CSRF token Recommended

Example URL:

https://yourdomain.speakap.com/auth?client_id=APP+ID                             ↵
  &redirect_uri=https%3A%2F%2Fyourdomain.com%2Fapp%2Ffolder%2Fhelloworld_sso.php ↵
  &scope=profile.basic.read&state=i8XNjC4b8KVok4uw5RftR38Wgp2BFwql

Upon receiving a GET request to the Auth-endpoint, Speakap will respond by presenting the logged-in user with a confirmation pop up dialog. Unauthenticated users first need to sign in to Speakap.

The confirm dialog allows the user to either grant or refuse authorization to the requested data. A refusal will result in an immediate redirect back to the application and a grant causes the Speakap API to perform an authorization request to the Speakap Authenticator. In response an authorization code is returned which is subsequently forwarded to your application by means of a redirect.

Example of a successful response:

HTTP/1.0 302 Found
Cache-Control: no-cache
Location: https://yourdomain.com/app/folder/helloworld_sso.php ↵
    ?code=...                                                  ↵
    &state=i8XNjC4b8KVok4uw5RftR38Wgp2BFwql
Server: nginx

Example of an error response:

HTTP/1.0 302 Found
Cache-Control: no-cache
Location: https://yourdomain.com/app/folder/helloworld_sso.php ↵
    ?error=oops                                                ↵
    &error_description=Something%20went%20wrong                ↵
    &state=i8XNjC4b8KVok4uw5RftR38Wgp2BFwql
Server: nginx

Access Token

If everything went as plannend, you now have an authorization code at your disposal. You can exchange this code for an access token at the Speakap Authenticator. Keep in mind that:

  • The code has a very short lifetime (never more than a few minutes).
  • You can only use the code once! If you do try to use it more than once, all tokens previously issued using this code might get revoked.
  • The authorization code is bound to your application and the redirect URI you requested it with.

Let’s write some server-side logic to retrieve an access token:

$ch = curl_init('https://authenticator.speakap.io/oauth/v2/token');

curl_setopt_array($ch, array(
    CURLOPT_HEADER => false,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => http_build_query(array(
        'grant_type' => 'authorization_code',
        'code' => '/* Authorization Code */',
        'redirect_uri' => 'https://yourdomain.com/app/folder/helloworld_sso.php',
        'client_id' => '/* App ID */',
        'client_secret' => '/* Secret */'
    )),
    CURLOPT_RETURNTRANSFER => true
));

$response = curl_exec($ch);
curl_close($ch);

$accessToken = json_decode($response, true)['access_token'];

As you can see we also send our client credentials. Remember that this is compulsory when communicating with the Speakap Authenticator. Only after successful authentication requests are handled. We recommend using Basic HTTP Authentication instead of passing a client_secret parameter.

Same example but now using Guzzle:

<?php

// Assume auto-load is in place for the GuzzleHttp namespace

$client = new GuzzleHttp\Client(array(
    'base_uri' => 'https://authenticator.speakap.io'
));

$response = $client->post('/oauth/v2/token', array(
    'headers' => [
        'Authorization' => 'Basic '. base64_encode(/* App ID */ ':' /* Secret */);
    ],
    'form_params' => [
        'grant_type' => 'authorization_code',
        'code' => '/* Authorization Code */',
        'redirect_uri' => 'https://yourdomain.com/app/folder/helloworld_sso.php',
        'client_id' => '/* App ID */'
    ]
));

$accessToken = json_decode($response->getBody(), true)['access_token'];
POST /token HTTP/1.1
Host: authenticator.speakap.io
Authorization: Basic LyogQXBwIElEICovOi8qIFNlY3JldCAqLw==
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=%2F*%20Authorization%20Code%20*%2F          ↵
&redirect_uri=https%3A%2F%2Fyourdomain.com%2Fapp%2Ffolder%2Fhelloworld_sso.php ↵
&client_id=%2F*%20App%20ID%20*%2F

Authorization

You are now able to retrieve a user access token, which makes you capable of doing requests on behalf of a Speakap User. This brings about some information sensitivity concerns:

What information can be accessed?

Is the user aware of what and when information is accessed?

The “what” question is tackled by OAuth scope.

Scope

In OAuth 2.0 scope enables clients to specify what resources and data encompasses their access requests. As part of an authorization request this gives the user insight into what information is asked to be disclosed.

You may have seen something similar in action when installing Facebook or mobile apps. They ask permission to access certain data, like for example: your location, contacts and photos.

Speakap Apps work in a comparable fashion; they need to inform the user what data will be gathered.

Authorize "View your basic profile information"

Clicking “Accept” grants, in this case the Hello World SSO app, access to the user’s basic profile:

  • Name and avatar
  • Jobtitle
  • Preferred language

This is a somewhat incomplete answer to the “when” question; after authorization, an app is from that moment on able to retrieve basic profile information. A user will never know exactly when this data is retrieved, but at any time it’s possible to revoke the grant and withdraw authorization.

A final coding example of how to retrieve a basic profile:

<?php

$networkEID = '/* EID of your Speakap Network */';

$client = new GuzzleHttp\Client(array(
    'base_uri' => 'https://api.speakap.io/networks/' . $networkEID
));

$response = $client->get("/?embed=userProfile", array(
    'headers' => [
        'Accept' => 'application/vnd.speakap.api-v1.4+json',
        'Authorization' => 'Bearer '. $accessToken
    ]
));

$network = json_decode($response->getBody());

$basicProfile = $network->_embedded->userProfile;