Contents

Implementing web-based authentication with Platform Single Sign-on

Support modern, phishing-resistant, and flexible authentication methods.

Overview

With web-based authentication, Platform Single Sign-on (Platform SSO) presents a built-in web view that loads your identity provider’s OAuth 2.0/OpenID Connect (OIDC) authorization endpoint. You define the content of the web view and any required authentication steps.

Prepare the SSO extension

Web-based authentication uses the Platform SSO 2.0 protocol and specifically requires support of the key service and shared device keys. For more information, see Creating extensions that support Platform SSO.

The SSO extension needs to support the tokenExchange grant type and return this value from supportedGrantTypes() during device registration.

Provide a login configuration

When you set ASAuthorizationProviderExtensionAuthenticationMethod.openID, or when the options include ASAuthorizationProviderExtensionRequestOptionsOpenIDFallback, the SSO extension needs to provide a login configuration for web-based authentication.

During web-based authentication, a web view provided by the authorization endpoint appears. The URL of the authorization endpoint can be static or dynamic. In both cases, the SSO extension needs to provide information to Platform SSO as part of the login configuration when performing the registration.

Regardless of how you provide the URL, Platform SSO makes the following changes in the login request:

Key

Change

redirect_uri

Removed if already present and set to com.apple.platformsso://callback.

login_hint

Removed if already present and set to the identity provider username.

scope

Removed if already present and re-added as a whitespace-deduplicated merge of the URL’s scope and the Platform SSO specific login scope. If a request object is used, Platform SSO updates the scope to the same value before signing.

Use a static OAuth URL

This method uses a static URL for the authorization endpoint:

Use a dynamic OAuth URL

This method retrieves the authorization URL from the authorization endpoint using the federation pre-authentication request and uses it for authentication:

If you include an authorizationRequest dictionary in the federation pre-authentication response, Platform SSO creates a JWT OpenID request object from it and signs it with the device signing key. It also updates the scope value to include the current scope for the request.

Perform the authentication

Web-based authentication assumes that the identity provider is a nonpublic client for OAuth and uses the following flow:

[Image]

Step 1: The user starts the web-based authentication login flow.

Step 2: Platform SSO either contacts the static authorizationURL directly, or contacts the pre-authentication URL to retrieve a dynamic authorizationURL. If the URL fails to load, the device assumes it’s offline and uses a fallback credential.

The following is an example pre-authentication request:

GET /discovery?oidcrequest&user=user@example.com HTTP/1.1
Host: auth.example.com
Connection: keep-alive
client-request-id: D546DD02-ACBD-4BA6-A48D-2BEA7CDABCDC
Accept: application/json
User-Agent: AppSSOAgent/1 CFNetwork/3887.100.1 Darwin/27.0.0
Accept-Language: en-US,en;q=0.9
Accept-Encoding: gzip, deflate, br
Cache-Control: no-cache

The following is an example pre-authentication response:

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 430
Connection: keep-alive
{
    "account_type": "Federated",
    "federation_protocol": "OIDC",
    "authorizationURL": "https://auth.example.com/authorize?state=ABCD1234&nonce=1EE6E70D-395D-4AE9-A075-F134E2F80420",
    "authorizationRequest": {
        "client_id": "52ADF3E3-DCCA-4507-BB0E-94EE5AFA643C",
        "nonce": "1EE6E70D-395D-4AE9-A075-F134E2F80420",
        "redirect_uri": "com.apple.platformsso://callback",
        "response_type": "code",
        "scope": "openid profile email",
        "state": "ABCD1234"
    }
}

Step 3: Platform SSO loads the webpage located at the authorizationURL. This request includes an updated scope for the current request in the web view. For more information on the available scope value, see Interpret Platform SSO authorization scopes.

Step 4: The user interacts with the web view, which can involve multiple steps that the authorization endpoint defines. Platform SSO loads only the URLs that the WebLoginURLAllowList key in the device management configuration permits. If authentication is successful, the authorization endpoint returns the callback URI com.apple.platformsso://callback, which includes an authorization code and a redirect to engage the Platform SSO extension.

When Platform SSO receives the callback URI, the web view closes.

The following is an example authorization request:

POST /authorize?state=ABCD1234&nonce=1EE6E70D-395D-4AE9-A075-F134E2F80420&login_hint=user@example.com&redirect_uri=com.apple.platformsso://callback&scope=openid%20offline_access%20urn:apple:platformsso%20urn:apple:platformsso:auth:auth-prompt HTTP/1.1
Host: auth.example.com
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Content-Type: application/x-www-form-urlencoded
Origin: null
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko)
Content-Length: 1434
Sec-Fetch-Dest: document
Accept-Language: en-US,en;q=0.9
Priority: u=0, i
Accept-Encoding: gzip, deflate, br, zstd
Connection: keep-alive

request=ewogICJraWQiJCCPb0y...5o1Slk1yxQFsmYwtg

The request value is:

  • A Base64-encoded version of a JWT OpenID request object.

  • Signed with the device signing key so the authorization endpoint can strongly identify the requesting Mac.

The following is an example JWT request object:

{ 
    "kid": "fXvsEh1WFSFiXGNAUkqE38RjQwjFfEFscsWrk4YaM28=", 
    "x5c": [ "MIIBgjCCASmgA...JmggW3EQOU" ], 
    "typ": "platformsso-authorization-request+jwt", 
    "alg": "ES256" 
}.{ 
    "client_id": "52ADF3E3-DCCA-4507-BB0E-94EE5AFA643C", 
    "nonce": "1EE6E70D-395D-4AE9-A075-F134E2F80420", 
    "redirect_uri": "com.apple.platformsso://callback", 
    "scope": "openid profile email offline_access urn:apple:platformsso urn:apple:platformsso:auth:auth-prompt", 
    "state": "ABCD1234", 
    "response_type": "code" 
}.[Signature]

The following is an example response:

HTTP/1.1 302 Found
Content-Length: 197
Location: com.apple.platformsso://callback?code=gAJZwNpLDPardp8NKwvyyJLYefxLqMQH&state=ABCD1234
Content-Type: text/html; charset=utf-8
Connection: keep-alive
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="refresh" content="0;url=com.apple.platformsso://callback?code=gAJZwNpLDPardp8NKwvyyJLYefxLqMQH&state=ABCD1234">
    </head>
    <body>
    Redirecting...
    </body>
</html>

When receiving the response, Platform SSO verifies that:

  • The state query parameter is present, non-empty, and equal to the state value Platform SSO sent in the original request.

  • The code query parameter is present and non-empty.

Step 5: Platform SSO uses the provided authorization code to authenticate with the identity provider. To begin the login, Platform SSO first retrieves an anti-replay value.

The following is an example anti-replay value request:

POST /auth/token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
User-Agent: AppSSOAgent/1 CFNetwork/3887.100.1 Darwin/27.0.0
client-request-id: F7263024-A625-400C-A75D-A0C011E4AAC0
Connection: keep-alive
Accept: application/json
Accept-Language: en-US,en;q=0.9
Content-Length: 24
Accept-Encoding: gzip, deflate, br
Cache-Control: no-cache

grant_type=srv_challenge

The following is an example anti-replay value response:

HTTP/1.1 200 OK
Cache-Control: no-store, no-cache
Content-Type: application/json
Content-Length: 122
Connection: keep-alive

{"Nonce":"AwABAAAAAAADAOz_B...3AQwVsWCxHYQgAA"}

Using the received anti-replay value, the device sends a login request to the identity provider.

The following is an example HTTP login request:

POST /auth/token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
User-Agent: AppSSOAgent/1 CFNetwork/3887.100.1 Darwin/27.0.0
client-request-id: F7263024-A625-400C-A75D-A0C011E4AAC0
Connection: keep-alive
Accept: application/platformsso-login-response+jwt
Accept-Language: en-US,en;q=0.9
Content-Length: 2660
Accept-Encoding: gzip, deflate, br
Cache-Control: no-cache

platform_sso_version=1.0&grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=ewogICJraWQiI...Iewy1GSQOJw

The assertion value is a Base64-encoded version of a JWT login request.

Platform SSO includes the validated authorization response from step 4 as the value of the subject_token in the login request. The login request is a normal Platform SSO login request with three differences:

  • grant_type is set to urn:ietf:params:oauth:grant-type:token-exchange.

  • subject_token is the full absolute URL from the authorization response including the scheme and host.

  • subject_token_type is set to urn:apple:platformsso:authorization-code-response.

The following is an example JWT login request:

{
    "kid": "fXvsEh1WFSFiXGNAUkqE38RjQwjFfEFscsWrk4YaM28=",
    "x5c": [
        "MIIBgjCCASmgAwIBAgIBATAKBggqhkjOPQQDAjA3MRQwEgYDVQQDEwtkZXZpY2UgdGVzdDELMAkGA1UEBhMCVVMxEjAQBgNVBAoTCUFwcGxlIEluYzAeFw0yNjA2MTAxOTU5NDBaFw0yNzA2MTAxOTU5NDBaMDcxFDASBgNVBAMTC2RldmljZSB0ZXN0MQswCQYDVQQGEwJVUzESMBAGA1UEChMJQXBwbGUgSW5jMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEoPsP+ZfhDo10Ubr4qfn8e/k0U5H8dBq+YtPjohnq2qKO4n5xdvbj3Vsh+oXffe5trPqiXVAxuk5jJKBwbUO25qMmMCQwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCB4AwCgYIKoZIzj0EAwIDRwAwRAIgdsT98cEVhh42mKc6mYYgpneTmhf4mc6EZTkXDHeOYjgCICeSlZIsg5blvS+zTSCBiekjiShL/09yanJmggW3EQOU"
    ],
    "typ": "platformsso-login-request+jwt",
    "alg": "ES256"
}.{
    "jwe_crypto": {
        "alg": "HPKE-P256-SHA256-A256GCM",
        "enc": "dir",
        "kid": "/3VgT0CYtZjhpGqeL/a1GkfrxsmgK93kPx1LkSupcaQ="
    },
    "exp": 1781124050,
    "nonce": "D9B2DD84-2C4A-4EFD-B88A-A203E0B8BA5F",
    "request_nonce": "AwABAAAAAAADAOz_B...3AQwVsWCxHYQgAA",
    "scope": "openid offline_access urn:apple:platformsso urn:apple:platformsso:auth:auth-prompt",
    "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
    "subject_token": "com.apple.platformsso://callback?code=gAJZwNpLDPardp8NKwvyyJLYefxLqMQH&state=ABCD1234",
    "iss": "aaff1524-fa35-40c5-94e3-2b233c5f2965",
    "sub": "user@example.com",
    "claims": {
        "id_token": {
            "groups": {
                "values": [
                    "com.apple.fooadmins",
                    "com.apple.baradmins",
                    "com.apple.foodatetime",
                    "com.apple.foosudo"
                ]
            }
        }
    },
    "aud": "https://auth.example.com/auth/token",
    "subject_token_type": "urn:apple:platformsso:authorization-code-response",
    "username": "user@example.com",
    "client_id": "aaff1524-fa35-40c5-94e3-2b233c5f2965",
    "iat": 1781123750
}.[Signature]

Step 6: The identity provider communicates with the authorization endpoint, which determines whether the provided authorization code is valid and whether login succeeds. In this flow, the identity provider is a nonpublic OAuth client and communication with the authorization endpoint happens out-of-band. The identity provider and its authorization endpoint define how to perform authentication.

Step 7: If all checks pass, the identity provider sends the Platform SSO login response back to the device, including the access token, ID token, and refresh token.

Platform SSO processes and validates the login response the same way as other login requests. It then uses the Platform SSO 2.0 key service to unlock the user’s keybag and complete the login or unlock flow. Platform SSO always uses the key service during web-based authentication.

Interpret Platform SSO authorization scopes

When Platform SSO requests OpenID tokens from your identity provider, it includes one of the scopes below in the authorization request. The scope tells your identity provider why the user is authenticating, so you can apply the correct policy (for example, require multifactor authentication at login but not at screen unlock).

All scope values use the prefix urn:apple:platformsso:auth::

Scope

Usage

elevation

Platform SSO prompts the user to re-authenticate to elevate privileges (administrator authorization prompt).

refresh

A silent token refresh with no user interaction. If refresh fails and falls back to a real login, the scope reverts to the originating caller’s scope (for example, auth-prompt).

auth-prompt

An in-session authentication prompt; for example, background re-authentication at session start, after a network change, or on a token-refresh timer.

create-user

Platform SSO creates a new local account at the login window using credentials from the identity provider.

fallback

Fallback uses OpenID because the primary credential (for example, Touch ID) isn’t usable. Platform SSO sends this scope on both the authorization request and the corresponding token verification.

login

The user logs in at the login window or unlocks FileVault. Also the default scope when no other context applies.

password-change

The user is in the password-change flow.

setup-assistant

Setup Assistant drives this authentication during initial device setup, including embedded system-session authentication that isn’t an elevation prompt.

temporary-session

Authentication for an Authenticated Guest Mode session.

unlock

The user unlocks the Mac from the screen-locked state (already logged in).

For most session-driven flows, Platform SSO selects the scope purely from the session type:

Session type

Resulting scope

Elevation prompt

elevation

In-session prompt

auth-prompt

Login window, or FileVault unlock

login

Password change

password-change

Setup Assistant

setup-assistant

Screen unlock

unlock

A few scopes fall outside the session-type mapping:

  • Platform SSO applies the special-purpose scopes (create-user, temporary-session, fallback, refresh) based on the specific feature path instead of the session type.

  • When no specific context is available, login doubles as the default. Treat it as the safe baseline and reserve stricter policy for the more specific scopes.

  • For fallback, Platform SSO sends the same scope on both legs of the OpenID handshake (the initial authorization request and the subsequent token verification), so your identity provider can apply policy decisions consistently across both.

Sync the password

Optionally, you can sync the password the user enters during web-based authentication to the local user account. Use this option when the device management configuration sets the AllowWebLoginPasswordSync key to true. This requires the identity provider to embed a JavaScript snippet (like the following conceptual example) in the authorization endpoint login page:

<script>
    function syncPasswordWithPlatformSSO() {
        // 1. Check for the Platform SSO API.
        if (!window.apple?.platformSSO) {
            // Platform SSO not available; no action required
            // because password sync is a supplementary feature.
            return;
        }
        // 2. Get the password from your sign-in form.
        const password = document.getElementById('password').value;
        // 3. Send the password to Platform SSO for local account sync.
        if (window.apple.platformSSO.syncPassword) {
            window.apple.platformSSO.syncPassword(password);
        }
    }
    // Call from your form's submit handler.
    document.getElementById('signinForm').addEventListener('submit', function() {
        syncPasswordWithPlatformSSO();
        // Form submission continues normally.
    });
</script>

Support QR code authentication

QR code scanning is a supplementary feature that the authorization endpoint’s webpage initiates during web-based authentication. The sign-in page calls a JavaScript API injected into the web view, which opens an attached camera for QR code scanning. The JavaScript caller receives the scanned data and stores it for inclusion in the OAuth credential submission.

To add QR code scanning to your login page, use a JavaScript snippet like the following conceptual example:

<script>
    async function authenticateWithQR() {
        // 1. Check for the Platform SSO API.
        if (!window.apple?.platformSSO) {
            // Fallback: Show a manual entry field.
            showManualCodeEntry();
            return;
        }

        try {
            // 2. Call the built-in QR scanner.
            const scannedData = await window.apple.platformSSO.scanQR();

            // 3. Use scanned data in your authentication flow
            // for example, submit as a one-time code, 
            // TOTP seed, or device binding token.
            await submitQRCodeToServer(scannedData);

        } catch (error) {
            // 4. Handle any cancellation or failure.
            if (error.message === "cancelled") {
                // User canceled the scan.
            } else {
                // Scanner error (camera unavailable, 
                // timeout, and so on).
            }
        }
    }
</script>

The scanQR() function resolves with:

  • The QR code payload as a UTF-8 encoded string.

  • The QR code content determines the format of the string (for example, URL, token, and so on).

The scanQR() function rejects with an Error where error.message is one of:

  • cancelled: The user dismissed the scanner.

  • error: An internal error occurred (for example, a camera session or scanner configuration failure).

  • invalid: The scan didn’t return a valid QR code. Retry the scan.

  • timeout: The scan exceeded the system time limit.

  • unavailable: No camera is available.

See Also

Authentication