Sitemap

Enabling SSO for the AppAuth SDK in iOS

5 min readJan 20, 2023

--

There are 3 options for enabling SSO with session cookies when it is not available by default in an OAuth 2.0 client built with AppAuth for iOS:

1. Using Safari for user authentication will allow sharing of session cookies in all versions of iOS. This technique is described in the Selecting User-Agent section.

PROS:

a) Complete control over sharing browser data between authentication events.

CONS:

a) Authentication in Safari forces the user to leave the app.

b) The app cannot control Safari tabs.

2. If calling the default browser for front-channel communications is not acceptable, enabling persistent cookies for session management could be an option in the authorization server configuration. This will allow sharing of the authentication state in the iOS in-app browser tabs. An example of this approach is described in the Enabling Persistent Cookies in the ForgeRock Access Management section.

PROS:

a) Supported with “out of the box” configuration of AppAuth for iOS.

CONS:

a) May require configuration changes in the authorization server.

3. Alternatively, a trusted app could be given access to the user session information and allowed to share it with other apps produced by the same development team, affecting SSO via the back channel (that is, without the user being involved). A particular implementation of this method is demonstrated in the Employing Embedded User-Agent section.

PROS:

a) Does not depend on the operating system policies

b) Does not require an external user-agent

CONS:

a) Requires inter-app communications; hence, works only for apps belonging to the same development team in iOS

b) May violate principles described in the best current practices for implementing OAuth 2.0 clients.

Javascript Messaging :

It is one of the Embedded User-Agent which Utilizes a web view and allows for reusing functionality already built for the browsers.

Building your own embedded browser, with more elementary native controls, may be beneficial in highly specialized cases.

if there is some dynamic content calling for an action to be performed on an authentication page by the native client, the results of which need to be reported back to the web application then Javascript Messaging is the best Embedded User-Agent.

Steps to integrate Javascript Messaging:

1. Imitate dynamic web content with JavaScript injected into the web view content from a source file as below :

// WebViewController.js

(function () {

if (window.webkit) {

var response = {

“authId”: “authId”,

“callbacks”: [

{

“type”: “Code”,

“input”: [],

“output”: []

}

]

}

window.webkit.messageHandlers.callback.postMessage(JSON.stringify(response))

}

}())

2. The posted “callback” message represents a hypothetical request for action from the authentication endpoint to the native app. We will accommodate the message format in a codable structure.

// AuthenticationResponse.swift

import Foundation

// Container for JSON callbacks expected from the authentication endpoint.

struct AuthenticationResponse: Codable {

struct Callback: Codable {

let type: String?

}

let authId: String?

var callbacks: [Callback] = []

}

In order for the source file to be available at the run time, it will need to be added to the app bundle in Target > Build Phases > Copy Bundle Resources. For example:

To be able to inject scripts and handle messages received from the web view content, we will need to configure the web view with the WKUserContentController class and adopt the WKScriptMessageHandler protocol.

Add the following extension, conforming to the WKScriptMessageHandler protocol, to the main view controller:

// ViewController.swift

// . . .

// MARK: Conforming to WKScriptMessageHandler protocol

extension ViewController: WKScriptMessageHandler {

func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {

print(#function)

if message.name == “callback”, let messageBody = message.body as? String, let messageBodyData = messageBody.data(using: .utf8) {

let decoder = JSONDecoder()

var messageBodyJson: AuthenticationResponse?

do {

messageBodyJson = try decoder.decode(AuthenticationResponse.self, from: messageBodyData)

messageBodyJson?.callbacks.forEach {

callback in

if callback.type == “code” {

/**

Example action against the web content.

*/

let scriptSource = “document.body.style.backgroundColor = ‘lightgreen’”

// Example action performed in the native app.

let webView = self.view.viewWithTag(webViewTag) as? WKWebView

let alert = UIAlertController(title: “Native Prompt”, message: “Enter the code. \nThe correct one is: 0000”, preferredStyle: UIAlertController.Style.alert)

alert.addTextField() {

textField in

alert.addAction(

UIAlertAction(title: NSLocalizedString(“Cancel”, comment: “Cancel Action”), style: UIAlertAction.Style.cancel) {

(_: UIAlertAction) in

webView?.removeFromSuperview()

}

)

alert.addAction(

UIAlertAction(title: NSLocalizedString(“Submit”, comment: “Submit Action”), style: UIAlertAction.Style.default) {

(_: UIAlertAction) in

let newValue = textField.text?.trimmingCharacters(in: .whitespacesAndNewlines)

if (newValue != “0000”) {

webView?.removeFromSuperview()

} else {

// Performing the action against the web content.

webView?.evaluateJavaScript(scriptSource, completionHandler: nil)

}

}

)

}

present(alert, animated: false)

}

}

} catch {

print(“Error decoding callback message: “, error.localizedDescription)

}

}

}

}

This example implementation of userContentController(_:didReceive:) will respond to the “callback” message, process its data, invoke an action in the native app, and respond back to the page with an action against the web content.

You can now add WKUserContentController to the web view configuration and use its add(_:name:) method to register a message handler for the “callback” name we used in the WebViewController.js script. For example:

// ViewController.swift

// . . .

// MARK: Authorization methods

extension ViewController {

// . . .

func authorizeWithWebView(

configuration: OIDServiceConfiguration,

clientId: String,

redirectionUri: String,

scopes: [String] = [OIDScopeOpenID, OIDScopeProfile],

completion: @escaping (OIDAuthState?, Error?) -> Void

) {

// . . .

// Configuring the web view for JavaScript interactions.

let userContentController = WKUserContentController()

userContentController.add(self, name: “callback”)

let configuration = WKWebViewConfiguration()

configuration.userContentController = userContentController

// Providing the web view class with initial parameters.

webViewController = WebViewController.init(

appGroup: appGroup,

appGroupCookies: appGroupCookies,

webViewFrame: view.bounds,

webViewConfiguration: configuration

)

// . . .

}

}

// . . .

}

// . . .

Now, when we build and run the app, we’ll be greeted with a native prompt initiated from the JavaScript code on the web page. Responding to the prompt will execute a JavaScript function on the web page, manifesting itself as the page background change.

Utilizing a web view allows for reusing functionality already built for the browsers. Building our own embedded browser, with more elementary native controls, may be beneficial in highly specialized cases. It would, however, require replicating the web functionality with native means and maintaining it separately from the web content.

Conclusion:

There seems to be no standard solution for implementing SSO based on browser cookies in iOS in a way that is compliant with the best current practices and recommendations for OAuth 2.0 clients. In this overview, we extended our experience with the AppAuth SDK for iOS by describing a few additional options for enabling SSO when it is not available with the SDK “out of the box”.

--

--

Anup Sahu
Anup Sahu

Written by Anup Sahu

I'm a Mobile App Developer, public speaker, and tech blogger. Develops mobile apps using iOS, Flutter, and React Native.

No responses yet