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 |
|---|---|
| Removed if already present and set to |
| Removed if already present and set to the identity provider username. |
| 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:
Set federationType or fallbackFederationType to ASAuthorizationProviderExtensionLoginConfiguration.FederationType.openID.
Set authorizationURL to the full static URL to use for login.
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:
Set federationType or fallbackFederationType to ASAuthorizationProviderExtensionLoginConfiguration.FederationType.dynamicOpenID.
Set authorizationURLKeypath to the key for the authorization in the pre-authentication response. The default key path is
authorizationURL. Platform SSO handles this URL the same way as a staticauthorizationURL.
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-cacheThe 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...5o1Slk1yxQFsmYwtgThe 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_challengeThe 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...Iewy1GSQOJwThe 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_typeis set tourn:ietf:params:oauth:grant-type:token-exchange.subject_tokenis the full absolute URL from the authorization response including the scheme and host.subject_token_typeis set tourn: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 |
|---|---|
| Platform SSO prompts the user to re-authenticate to elevate privileges (administrator authorization prompt). |
| 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, |
| An in-session authentication prompt; for example, background re-authentication at session start, after a network change, or on a token-refresh timer. |
| Platform SSO creates a new local account at the login window using credentials from the identity provider. |
| 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. |
| The user logs in at the login window or unlocks FileVault. Also the default scope when no other context applies. |
| The user is in the password-change flow. |
| Setup Assistant drives this authentication during initial device setup, including embedded system-session authentication that isn’t an elevation prompt. |
| Authentication for an Authenticated Guest Mode session. |
| 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 |
|
In-session prompt |
|
Login window, or FileVault unlock |
|
Password change |
|
Setup Assistant |
|
Screen 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,
logindoubles 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.