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]_[APP_SECRET]
This tutorial will cover a third method with a couple of added benefits over the other two:
- Your application will be opened in an external window
- Single Sign On! Speakap can be used to authenticate your users
- An access token can be retrieved directly from the Speakap Authenticator
- 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:
- User opens Speakap; logs in; visits your web app
- User opens your application first; chooses “Login with Speakap”; logs in with Speakap; returns to your web app
- Logged-in user opens Speakap; visits your web app and has direct access
- 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 attempts 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 planned, 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.
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;