Skip to content

Web ​

On web, expo-apple-sign-in has no native module. It loads Apple's JavaScript SDK (Apple JS) into the page and opens Apple's sign-in page in a popup, then turns Apple's response into the same credential that iOS and Android return. Web uses the Services ID and Return URL from Apple Developer, and adds one rule of its own: the redirect URI must have the same origin as the page that starts sign-in.

Configure the Services ID ​

Call AppleAuth.configure once at startup with the Services ID and a Return URL on the web app's own origin. If the web app runs at https://app.example.com, the configuration looks like this:

ts
import { AppleAuth } from 'expo-apple-sign-in'

AppleAuth.configure({
  clientId: 'com.example.app.web',
  redirectUri: 'https://app.example.com/auth/callback',
})

On the Services ID, list app.example.com under Domains and Subdomains and https://app.example.com/auth/callback under Return URLs. Without both values in configure, sign-in rejects with ERR_NOT_CONFIGURED and the message AppleAuth.configure({ clientId, redirectUri }) is required on web. clientId is the Apple Services ID.

Use a same-origin Return URL ​

Before it loads Apple JS, the library compares the origin of redirectUri (scheme, host and port) with window.location.origin. When they differ, sign-in rejects with ERR_NOT_CONFIGURED and a message that starts like this:

text
Web redirectUri origin (https://example.com) must match this page (https://app.example.com).

The check catches a common setup mistake: an Expo web app on a subdomain such as app.example.com that reuses a Return URL registered for the apex site example.com. After a successful login, Apple would send the user to the wrong host. Register a Return URL on the web app's own host and pass that URL on web.

Page originredirectUriResult
https://app.example.comhttps://app.example.com/auth/callbackAccepted
https://app.example.comhttps://example.com/auth/callbackERR_NOT_CONFIGURED, different host
https://app.example.comhttp://app.example.com/auth/callbackERR_NOT_CONFIGURED, different scheme

If redirectUri is not an absolute URL at all, sign-in rejects with ERR_NOT_CONFIGURED and the message AppleAuth.configure({ redirectUri }) must be an absolute HTTPS URL. Received: followed by the value.

If Android uses a Return URL on a different host, register both Return URLs on the same Services ID and pass the right one on each platform. Expo config shows how to do that with environment variables.

How popup sign-in works ​

The first call to AppleAuth.signIn() on a page adds a <script> tag for Apple JS to the document head, or waits for the tag if the page already includes it:

text
https://appleid.apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js

If the script fails to load, the library removes the tag and forgets the failed attempt, so the next call adds a fresh tag and tries again.

The library then calls AppleID.auth.init and AppleID.auth.signIn with your clientId, the redirect URI, the scopes (fullName is sent as name), the state, the SHA-256 hash of the nonce and usePopup: true. Apple's sign-in page opens in a popup window, and the promise from Apple JS resolves in the page that opened it.

Browsers block a popup that no user gesture opened, so call AppleAuth.signIn() from a click or press handler, such as the onPress of a button. Without an onPress prop, AppleButton calls AppleAuth.signIn() from its own press handler.

The redirect URI that the library passes to Apple JS depends on the current page. When redirectUri has the same origin and path as the current page, the library passes the current page URL without its hash, so Apple's popup returns to a document that loaded the SDK. Otherwise it passes redirectUri unchanged. In both cases the URL must be registered as a Return URL on the Services ID.

Apple JS returns the identity token, the authorization code, the state and, on the first authorization only, the user's first name, last name, and email. It does not return the user identifier, so the library takes user.id from the token's sub claim. middleName, namePrefix, nameSuffix, and nickname are always null on web, and realUserStatus is always 'unknown'.

Errors on web ​

SituationCode
clientId or redirectUri is missing, not an absolute URL, or on another originERR_NOT_CONFIGURED
Apple JS failed to load (Failed to load the Apple JS SDK.). The next call tries to load it again.ERR_NOT_AVAILABLE
Apple JS loaded but AppleID.auth is missing (Apple JS SDK did not initialize.)ERR_NOT_AVAILABLE
The code runs without a browser documentERR_NOT_AVAILABLE
Apple JS returned no identity tokenERR_MISSING_IDENTITY_TOKEN
Apple JS rejected with popup_closed_by_user, user_cancelled_authorize, or another code that contains cancel or closedERR_REQUEST_CANCELED
Apple JS rejected with popup_blocked_by_browser (The browser blocked the Apple sign-in popup. Call signIn from a click handler.)ERR_REQUEST_FAILED
Apple JS rejected with any other code (Apple sign-in failed: <code>) or without one (Apple sign-in failed.)ERR_REQUEST_UNKNOWN

Apple JS rejects with a plain object such as { error: 'popup_closed_by_user' }. The library reads its error field to pick the code above.

AppleAuth.isAvailable() only checks for a browser document, so it returns true even when a network policy will block Apple JS. AppleAuth.getCredentialState always returns 'unknown' on web, and AppleAuth.addRevokeListener never calls its listener and returns a subscription whose remove() does nothing.

Content Security Policy ​

If your site sends a Content Security Policy, allow scripts from Apple's host so the library can load Apple JS:

text
script-src 'self' https://appleid.apple.com

Without that entry, the browser blocks the script and sign-in rejects with ERR_NOT_AVAILABLE.

Test locally through a tunnel ​

Apple does not accept localhost or IP addresses as Services ID domains or Return URLs, so a dev server opened on localhost cannot complete sign-in. Expose the dev server through an HTTPS tunnel, register the tunnel host and a Return URL on that host, and open the app through the tunnel URL so that the page origin and redirectUri match. A deployed preview on an HTTPS domain works the same way.

Released under the MIT License.