OAuth SSO in iOS Using ASWebAuthenticationSession

This article provides a comprehensive guide on implementing OAuth single sign-on (SSO) flows in an iOS application using Apple's ASWebAuthenticationSession API. You will learn how to configure the authentication session, handle redirects to capture authorization tokens, and use the prefersEphemeralWebBrowserSession property to optionally share or isolate session cookies with the Safari web browser.

Understanding ASWebAuthenticationSession

ASWebAuthenticationSession is the standard Apple framework used to authenticate a user through a web service, most commonly for OAuth 2.0 and OpenID Connect flows. It presents a secure, embedded web view that loads the authentication provider's login page and listens for a specific callback URL scheme to return the authentication result to your app.

Step 1: Set Up the Presentation Context

To display the authentication view, your view controller must import the AuthenticationServices framework and conform to the ASWebAuthenticationPresentationContextProviding protocol. This tells the system which window should present the browser session.

import UIKit
import AuthenticationServices

class LoginViewController: UIViewController, ASWebAuthenticationPresentationContextProviding {
    
    var authSession: ASWebAuthenticationSession?

    func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
        return self.view.window ?? ASPresentationAnchor()
    }
}

Step 2: Initialize the Authentication Session

To start an OAuth flow, you need the authorization URL provided by your OAuth service and a custom URL scheme registered in your app's Info.plist (e.g., myapp://).

When the user completes the login on the web page, the OAuth provider redirects to your custom URL scheme with an authorization code or token appended as a query parameter. ASWebAuthenticationSession intercepts this redirect and triggers its completion handler.

func startOAuthFlow() {
    guard let authURL = URL(string: "https://example-oauth-provider.com/authorize?client_id=123&redirect_uri=myapp://callback&response_type=code") else {
        return
    }
    
    let callbackScheme = "myapp"
    
    authSession = ASWebAuthenticationSession(url: authURL, callbackURLScheme: callbackScheme) { callbackURL, error in
        if let error = error {
            print("Authentication failed: \(error.localizedDescription)")
            return
        }
        
        guard let callbackURL = callbackURL else { return }
        
        // Extract the authorization code from the callback URL
        let queryItems = URLComponents(string: callbackURL.absoluteString)?.queryItems
        if let code = queryItems?.first(where: { $0.name == "code" })?.value {
            print("Successfully received authorization code: \(code)")
            // Proceed to exchange the code for an access token
        }
    }
    
    authSession?.presentationContextProvider = self
}

The ability to share session cookies with the Safari browser is controlled by a single boolean property on the session instance: prefersEphemeralWebBrowserSession.

Option A: Share Cookies with Safari (Enable SSO)

By default, prefersEphemeralWebBrowserSession is set to false. When set to false, the session shares cookies and website data with the user's default Safari browser.

If the user is already logged into the identity provider in Safari, they will not have to enter their credentials again in your app, achieving true single sign-on. Because this accesses shared browser data, iOS will display a system alert asking the user for permission: "App" Wants to Use "domain.com" to Sign In.

// Shares cookies with Safari. Triggers the system permission dialog.
authSession?.prefersEphemeralWebBrowserSession = false

Option B: Do Not Share Cookies (Private Session)

If you set prefersEphemeralWebBrowserSession to true, the authentication session uses a private, temporary browser session.

No cookies or data are read from or written to Safari. The user will be forced to enter their login credentials every time, even if they have an active session in Safari. This option bypasses the iOS system permission alert, providing a slightly faster UI transition at the cost of single sign-on convenience.

// Isolates the session. No cookies are shared with Safari.
authSession?.prefersEphemeralWebBrowserSession = true

Step 4: Start the Session

Once you have configured the session and chosen your cookie-sharing preference, call the start() method to present the web view to the user.

// Complete implementation inside your login method
authSession?.prefersEphemeralWebBrowserSession = false // Set to true to disable cookie sharing
authSession?.start()
  • prefersEphemeralWebBrowserSession = false: Uses Safari cookies. Enables seamless SSO across Safari and other apps using the same setting. Displays a system consent alert before opening the web view.
  • prefersEphemeralWebBrowserSession = true: Ignores Safari cookies. Forces a fresh login every time. Does not display the system consent alert. Useful for apps requiring high security or providing a "Log in with a different account" feature.