Mobile App SSO Developers Guide

Achieving federated single sign-on for native applications has been a challenge for mobile application developers. The lack of a defined standard or best practice has lead to proprietary and complex solutions that have not proven interoperable with the variety of native applications an enterprise installs on their phone. The Native Applications SSO (NAPPS) working group of the OpenID Foundation was established to solve this challenge in a standards-based manner and has been working through several options to solve this challenge, most notably implementing the concept of a “token agent” designed to handle getting tokens and distributing these tokens to configured applications.

Recently the major mobile OS vendors (Apple and Google) look to have solved this challenge once and for all; announcing web browser components that provide the security, privacy and simplicity of the mobile system browser in an application-friendly manner.

Previously a developer had only two options to display web content (such as a federated authentication flow):

  • "bounce" the user over to the system browser - this caused a poor user experience as the user was flipped out of the application to the system browser, then back into the application; or
  • display an in-app “webview” to render html content inside their app - requiring a relatively large amount of code, the inability to share session across multiple instances and introducing privacy and security concerns for an end-user

There is now a third option available in the major mobile operating systems. Both Apple (iOS9+ - SFSafariViewController) and Google (Chrome 45+ - Chrome Custom Tabs) have added a web controller that provides all the benefits of the native system browser in a control that can be placed within an application. This user experience benefits by providing access to shared cookies across instances and keeping the user inside the application, and security and privacy concerns are mitigated as the application doesn’t have access to the data provided in the browser component. All this in a simple interface for developers to implement.

In this guide we will explore both these new browser controls and the authentication and authorization protocols used to present the identity to the application. Guiding you through handling end-user authentication to your application, getting tokens for back-end API calls and enabling single sign-on across multiple applications.

 

There are two complementary open standard protocols that are used to facilitate authentication (or authorization) to a mobile app and in-turn to its associated API: OAuth 2.0 and OpenID Connect 1.0.

The OAuth 2.0 protocol allows an end-user to authorize an application to call APIs on behalf of them. This is achieved by the application being issued an OAuth 2.0 access token which the application provides to the back-end API as authorization for the user. The application itself doesn’t receive any information about the user, it is just provided with a token it can pass along to the API.

OpenID Connect extends the OAuth 2.0 protocol to include an ID token which provides details around the identity and the authentication of that identity to the application. This allows an application to identify the user, the context of their authentication (how, when, where) and retrieve additional profile information about the identity.

Depending on the requirements of the application either or both of these protocols can be put in use. For example an application may not care about the identity of the user, just that they are authorized to make an API call, therefore OAuth 2.0 may be sufficient. However, another application may want to know details about the identity authenticating so that it can render content in an appropriate language and welcome the user by name. This application may leverage OpenID Connect for that personalisation.

How the user is authenticated by their identity provider adds an additional consideration. The user may be authenticated locally by the application’s authentication provider or may participate in a federated sign-on flow to authenticate at their home provider (using a protocol such as SAML to achieve this). Generally the application need not be concerned with this step however may need to assist in discovery of the end-users authentication provider.

This results in four actions for the application developer to consider:

  • Getting and refreshing access tokens (OAuth 2.0) - The prime concern for an application developer is to obtain a security token to be able to call its back-end APIs. This can be achieved solely by the OAuth 2.0 protocol if the application has no need or care to identify the user.

  • Authenticating the user to the application (OpenID Connect 1.0) - The application may also want to authenticate and identify the end-user, for example to provision an account for a new user or to personalise application content.

  • Authenticating the user at the authentication provider (SAML, OpenID Connect, Local) - Although not primarily an application function, the user needs to be authenticated at their authoritative identity provider (whether that is a local authentication at the application authentication provider or a federated sign-on at the end-user’s home authentication provider). An application may need to assist in the process to discover the end-user’s responsible authentication provider.

  • Single sign-on across applications (choice of browser component) - SSO consists of repeating this authentication / authorization pattern across multiple applications. The new browser components provide access to shared cookies allowing applications to re-use authentication sessions.

The architecture of this solution is very similar to web application single sign-on in that the user agent (browser) is used to facilitate SSO by re-using the authentication session.

Mobile Browser Components

The breakthrough for native application SSO lies in these new browser components (SFSafariViewController in iOS9+ and Chrome Custom Tabs in Chrome 45+). Using the Apple platform as an example, a developer now has three options for presenting web content in their application:

  1. System browser (Safari) - useful for presenting a native view for web content (for example a link to content external to the application such as a third-party website)
  2. WKWebView - used when the application needs to manipulate the web content which may be used to re-format content that is not available via native APIs or in applications such as password managers
  3. SFSafariViewController - the new in-app version of the system browser, useful to present all forms of web content that does not need to be manipulated

These three components are summarised by feature in the table below: 

Feature System Browser
(i.e. Safari)
Web View Component
(i.e. WKWebView)
New Component
(SFSafariViewController)
Authentication session shared across instances (i.e. shared cookie) YES
(persistent cookies are shared between Safari and SFSafariViewControllers)
NO
(Each webview is sandboxed)
YES
(persistent cookies are shared between Safari and SFSafariViewControllers)
Visible alerts for secure insecure content (ie padlock) YES
(URL and "SSL padlock")
MAYBE
(Developer is responsible for building UI)
YES
(URL and "SSL padlock")
User data is kept private (ie not accessible by app) YES NO
(Developer has access to data submitted)
YES
Supported authentication mechanisms X509, HTML Form, HTTP Basic Requires developer to handle authentication challenges X509, HTML Form, HTTP Basic
Browser appears inside application content (user never leaves app) NO
(User is "bounced" over to Safari)
YES YES
Implementation effort for developer Simple
(1 line of code)
Complex
(developer must build UI and handle authentication challenges)
Simple
(3 lines of code)

Applications that already use either the system browser or a webview to redirect the user through a federated authentication flow can just present this authentication flow in the new controller and be able to support native application SSO.

Note: Depending on the web content an application developer needs to present in their application, the new view controllers could be used solely for the authentication process to obtain API security tokens via the single sign-on process. An application could then use the other browser options to display content.

 

Using OpenID Connect for Authentication of the User to the Application

Federated sign-on is an important authentication mechanism for mobile developers: SaaS providers need to provide SSO for their enterprise customers to their mobile and web applications, consumer applications want to continue an authentication across a web application, a mobile application and the back-end API, and enterprises want secure authentication for their users regardless on the user's location.

OAuth 2.0 is widely used by application developers to obtain an access token to authorize access to back-end APIs. This access token however is intended for the API, not the application itself. Although a user may have authenticated to authorize the application to call APIs on their behalf, this authentication event (or any information around the end-user) is not provided to the application.

OpenID Connect provides this identity layer on top of OAuth 2.0, providing an ID token containing information about the user and the authentication event and a “userinfo” endpoint that provides additional profile attributes about the end-user.

By leveraging the OpenID Connect basic profile a developer can not only securely authenticate their end user, but also receive an OAuth access token to call back-end APIs and, optionally, an OAuth 2.0 refresh token to obtain new access tokens without having to re-authenticate the user.

Note: Although OpenID Connect is recommended for authentication to the application, the actual end-user authentication may leverage a federated sign-in protocol such as SAML to authenticate the user at their organization's authentication provider.

Critical to the security of the transaction is the Proof Key for Code Exchange (PKCE) extension to the OAuth 2.0 protocol. A major security concern is a malicious application impersonating your application by using the same application URL to steal the authorization code and exchange it for the tokens. It is near impossible to secure the OAuth client secret on a mobile device (during distribution of the application and over API calls) so PKCE secures the transaction between the application and the OAuth Authorization Server by providing a "dynamic secret" allowing the authorization server to bind the authorization code exchange request with the original authorization request.

Note: Along with the new browser components, both Android and iOS introduce new “universal link” mechanisms that can be used to associate a https url with a native application. These linking mechanisms have not as yet been proven to solve this use case.

 

The OpenID Connect flow for a mobile application can be described in the following diagram: 

Initiate authorization request

When an authentication event is requested (i.e. a user selects the “login” option) the developer needs to construct the OpenID Connect authorization request URL and redirect the user to that location. This is where the web view controller is used to facilitate this browser flow and enable SSO across applications.

The following code snippet provides an iOS example of constructing an authorization URL and redirecting the user via the SFSafariViewController:

let authorizationUrl = "https://sso.pingdeveloper.com/as/authorization.oauth2?
  state=ABC-123-DEFG&
  code_challenge=321-XYZ-3456&
  scope=openid%20profile%20email&
  client_id=ac_client&
  response_type=code&
  redirect_uri=com.pingidentity.developer.mobile_app%3A%2F%2Foidc%2Fcb"

let safariVC = SFSafariViewController(URL: NSURL(string: authorizationUrl)!)
safariVC.delegate = self
presentViewController(safariVC, animated: true, completion: nil)

The first line creates the authorization URL as per the OpenID Connect protocol. In this example the authorization endpoint is a PingFederate server located at https://sso.pingdeveloper.com/as/authorization.oauth2. There are a number of query parameters that are passed along to the authorization endpoint, those included in this request are:

Parameter Description
state The state parameter is used to mitigate Cross Site Request Forgery (XSRF). The authorization server will return this value unmodified when it redirects the user back to the redirect_uri. The client should ensure that these values match to somewhat bind the response to the redirect_uri to the authorization request.
code_challenge Required for Proof Key for Code Exchange (PKCE) extension. This is similar to how the state parameter works except that it is used by the Authorization Server (AS) to bind the request to exchange the authorization code for the tokens to the initial authorization request.
scope Defines the OAuth 2.0 scopes - for OpenID Connect the openid scope is required.
client_id Identifies the client configured on the OAuth Authorization Server.
response_type The value of “code” indicates that this is an OAuth 2.0 authorization code grant type.
redirect_uri Identifies the location the user is redirected back to once the authentication process has completed. For mobile applications this is generally a custom URI (in this case, com.pingidentity.developer.mobile_app://oidc/cb)

The final three lines above initialize the SFSafariViewController and present it. This presents the SFSafariViewController inside the application with the user redirected through the authorization process.

 

Handle authorization callback

After the user has been authenticated they will be redirected back to the application via the redirect_uri. This is generally a custom scheme (i.e. com.pingidentity.developer.mobile_app://) which instructs the mobile OS to launch the application registered with that URL.

In iOS you configure a URL scheme in the "URL Type" section in the "info.plist" file. In Android, this is handled by creating an <intent-filter> in your AndroidManifest.xml. Refer to the sample applications on GitHub for more details.

The application developer must then parse this URL to extract the “authorization code” and then contact the authorization server directly to exchange that code for the authentication tokens.

An example of a redirect back to the authorization callback URI for a successful authorization request is:
com.pingidentity.developer.mobile_app://oidc/cb?code=abc123&state=ABC-123-DEFG

Note: The redirect_uri will also include a “state” parameter with the same value supplied in the initial authorization request. If this value does not match the value supplied in the original authorization request then the client must fail the authentication.

Note: If the authentication was not successful, a parameter named “error” will be present.

Using the iOS example, the callback will be handled by the application(_:handleOpenURL:) method in the AppDelegate, for example:

func application(application: UIApplication, handleOpenURL url: NSURL) -> Bool {

  // If the callback was for the OpenID Connect authentication, then handle the response
  let baseUrl = url.absoluteString.componentsSeparatedByString("?")[0]
  if baseUrl.lowercaseString == "com.pingidentity.developer.mobile_app://oidc/cb" {

    // Close the SFSafariViewController
    window!.rootViewController?.presentedViewController?.dismissViewControllerAnimated(true , completion: nil)

    // Verify the state parameter matches the value provided in the authorization request
    // Exchange the authorization code for the tokens
    // Validate the id_token
    // Store the tokens / retrieve the userinfo / create application "session"
  }
  return true
}

In the above example, the “code” query parameter is then used as per the OAuth 2.0 authorization code grant type to construct the request to the token endpoint to allow the client to exchange this code for the actual tokens (OpenID Connect id_token, OAuth 2.0 access_token and optionally a refresh_token).

 

Exchanging the authorization code for the tokens

Once the code value has been parsed from the redirect URI, the application must then exchange this for the actual OAuth and OpenID Connect tokens. This is performed by a direct call from the application to the authorization server.

Using our example, the OAuth 2.0 request to exchange the code for the tokens can be achieved by the following example:

let tokenEndpointUrl = "https://sso.pingdeveloper.com/as/token.oauth2"
let postBody = "grant_type=authorization_code&
  client_id=ac_client&
  code=abc123&
  code_verifier=321-XYZ-3456&
  redirect_uri=com.pingidentity.developer.mobile_app%3A%2F%2Foidc%2Fcb"

let codeForTokensRequest = NSMutableURLRequest(URL: NSURL(string: tokenEndpointUrl)!)
codeForTokensRequest.HTTPMethod = "POST"
codeForTokensRequest.HTTPBody = postBody.dataUsingEncoding(NSUTF8StringEncoding)

let httpRequest = NSURLSession.sharedSession().dataTaskWithRequest(codeForTokensRequest) {
  data, response, error in

  // A client-side error occured
  if error != nil {
    print(“An error occurred”)
    return
  }

  let responseCode = (response as! NSHTTPURLResponse).statusCode
  let responseData = NSString(data: data!, encoding: NSUTF8StringEncoding)

  // 200 - Successful token exchange
  if responseCode == 200 {

    // parse the access_token, id_token and refresh_token(opt) from the JSON response body

  // 400 - Token exchange failed
  } else if responseCode == 400 {
    print(“An error occurred”)
    return
  }
}
httpRequest.resume()

The POST body in the example below is constructed with the following parameters:

Parameter Description
grant_type The value of “authorization_code” indicates that this is an OAuth 2.0 authorization code grant type.
client_id Identifies the client configured on the OAuth Authorization Server.
code The “code” value returned in the authorization response.
code_verifier The is part of the Proof Key for Code Exchange (PKCE) extension this is the same value provided in the “code_challenge” earlier. The Authorization Server (AS) uses this value to bind the authorization request to the request to exchange the authorization code for the tokens.
redirect_uri Identifies the location the user is redirected back to once the authentication process has completed. For mobile applications this is generally a custom URI (in this case, com.pingidentity.developer.mobile_app://oidc/cb)

The response to the above request will be a JSON object containing the tokens:


{
  "token_type":"Bearer",
  "expires_in":7200,
  "refresh_token":"yHnhpHelNSZKWibcV2Y1Mf04tF2VySvJIVCqNvYCLf",
  "id_token":"eyJhbGciOiJSUzI1NiIsImtpZCI6InZmYzcxIn0.eyJzdWIiOiJqb2UiLCJhdWQiOiJhY1
     9jbGllbnQiLCJqdGkiOiJaWGFDV3hBTWlzUlp5cWZid3BCV0QzIiwiaXNzIjoiaHR0cHM6XC9cL3Nzb
     y5waW5nZGV2ZWxvcGVycy5jb20iLCJpYXQiOjE0NDI1MzIyMTksImV4cCI6MTQ0MjUzMjUxOSwiYWNy
     IjoidXJuOmFjcjpmb3JtIn0.HVUkAOZ9CNqfTqmqyM7ekq6HhLUj9LHb67OoMRSfu0UUIAzclBR_Xwc
     8Psb76FtZiBX7gwauPkpgZGW10wEjcDAa4GEOcA4Kf0IHN_Jqbk2QND5sRgZHG4xWFMeRSlTVUpVD-k
     _v3npw4TL5CgU5vffpZhOerx7QgegklgVEAb7eYooSnl2I9jljDUdeufhRnqnXSY2OXG6GpT_l-cJcM
     PPZGYbb3vN3sNxayXHeJJPTsyAxI4u3M6wVxRot7V3E-JP8xsZn0jmYPZiDPpQdDCyznx0eWf8TRPPv
     nEf4Z7HBF1VPIorehj7I6maLS5Ri1gsdSiYmlkvSCywRxTZkIA",
  "access_token":"1oN7sBMNKSz1UF8C3wc7xB8MuIdp"
}

 

Validating the tokens

As the authorization code flow was used to get the tokens, validation of the tokens is a much simplified experience. The tokens were received via a direct HTTPS call to the token endpoint removing the requirement to validate the signature on the id_token JWT. The developer still must validate the contents of of the JWT to ensure that the token was issued by the expected authority, that the audience is this client and that the token was issued in a reasonable timeframe and hasn’t expired.

Once the developer has validated the tokens they now know the identity of the end user, how they authenticated and have an access_token that they can use to call back-end APIs on behalf of that user.

Optionally an OAuth refresh token can be issued and stored securely in the application (i.e. in the keychain) the refresh token can be used to obtain a new OAuth access token without having to reauthenticate the user.

Note: For detailed information about validating the OpenID Connect id_token, refer to the OpenID Connect Developers Guide.

 

Retrieve user profile (optional)

Now that you have valid tokens you can retrieve additional information about the authenticated user by calling the OpenID Connect userinfo endpoint.

GET https://sso.pingdeveloper.com/idp/userinfo.openid HTTP/1.1
Authorization: Bearer <OAuth access token>

The results of the userinfo request is a JSON object containing the user’s OpenID Connect profile information. The application can use this profile for personalisation (i.e. render the content according to the user’s language settings or welcome them by name). 

Cookies

In iOS9, cookies between the Safari system browser and the SFSafariViewController are only shared if they are persistent. Session cookies are not shared and can result in different authentication sessions between the system browser and the Safari view controller. Android expects persistent cookies to be used to share the session. This is not the default configuration for PingFederate, however there are simple instructions to switch from using session cookies to persistent cookies.

Changing from session-based to persistent cookies has additional considerations that need to be evaluated (i.e. a user will need to explicitly log-out to delete the cookie, closing the browser does not remove the session cookie as it does in some browser implementations).

 

Universal Links

Both iOS9 and Chrome provide a "universal links" interface that provides a way of linking directly into an application using a https:// url (rather than using a custom URl for the redirect_uri - in the example above, the application callback URL "com.pingidentity.developer.mobile_app://oidc/cb" is not guaranteed to be unique on a device). However in iOS9 as a universal link visited from a browser (i.e. in an OpenID Connect authentication flow) are still rendered in the browser (rather than being redirected to the application).

Scenario Result
Universal link is launched from the system browser Content is rendered in the system browser and a banner allows the user to open the URL in the claimed application
Universal link is launched from the SFSafariViewController Content is rendered in the SFSafariViewController 
Universal link is launched from the native application Content is rendered in the system browser
Universal link is launched from another native application Application is launched to render content 

For iOS therefore using an application URI is the best solution (for now). This also provides the key driver for leveraging the PKCE extension to mitigate a malicious application intercepting and exchanging the authorization code for the tokens.

 

Session Management

Although mobile applications don't necessarily maintain a "session", there are scenarios where an application may want to know if a user has a session already and silently authenticate the user or refresh that session. The OpenID Connect "prompt" parameter allows an application to specify how it wants to handle user interaction during the authorization request. By leveraging the "prompt=none" parameter a user can be silently sent through the authentication process and the tokens returned if the user has a valid session or an error returned if not - allowing an application to check if a user has a session, or handle session extension and session expiry gracefully. The SFSafariViewController can be displayed transparently hiding any interaction from the user.

let safariVC = SFSafariViewController(URL: NSURL(string: authorizationUrl)!)
safariVC.delegate = self
safariVC.modalPresentationStyle = UIModalPresentationStyle.OverCurrentContext
safariVC.view.alpha = 0.0
presentViewController(safariVC, animated: false, completion: nil)

The prompt parameter can also be used to force a user to authenticate if required. Setting "prompt=login" will require that the user interactively logs in.

Note: This workaround just makes the view invisible; while "prompt=none" should suppress any content and return the user to the callback URI, if other interactions interfere with this process (for example prompting the user to select an X509 certificate) the application may appear to "hang" so the application should dismiss the SFSafariViewController.

 

Hybrid Apps

In some cases an application needs to present web content and in some cases that content is authenticated. The same browser component can be leveraged to present the web content to the user and handle authentication (assuming the web content leverages the same authentication mechanism as the mobile application).

If the application needs render web content in a custom webview (such as the WKWebView component in iOS) the application can pass along an OAuth access token to the web component via a URL fragment as it launches the view. The application is not able to grab the cookies from the new web view controllers so session must be passed along by other methods.

 

Consent approval

Now that single sign-on is easy to implement, it may not be apparent to the end-user that they are being sent through an authentication flow. For privacy reasons it is necessary to gain consent by the end-user for the client to act on its behalf and access the user's resources. So when a user is sent through the process to authorize the client, ensure that the user is still presented with the consent screen so they can choose whether they want to authorize the app.

Android requires that the user is redirected back to the application via a user gesture. This means that the user must approve consent before being redirected to the application - bypassing the authorization approval doesn't return the user to the application.

 

Authentication Methods

As we can see in the table above, the SFSafariViewController (and Chrome Custom Tabs) has access to the share keystore. This allows the controller to use an x509 certificate deployed on the device via an MDM to be used for authentication. For iOS, if there is only one certificate available the user will be prompted to select their cert the first time they use it (it will be remembered from then on) however if there are multiple certs installed on the device the user will be prompted each time. As this prompt is performed by the mobile OS, it is not handled as an "interaction" when using the prompt=none parameter. Therefore the developer must handle this gracefully (ie timeout the background authentication after a period of time).