Sign in with Apple on iOS, with tokens your server verifies and a check for revoked accounts
One.Auth.Apple runs Sign in with Apple on iOS: the system sheet where
people confirm with Face ID or their passcode, choose whether to share or
hide their email, and come back with a credential. It is
ASAuthorizationController with an Apple ID request.
The app gets a stable user ID and two tokens for your server. Your server
verifies them with Apple and creates the session; the app never sees a
password. Android and web have no system Sign in with Apple, so there
isAvailable is false and the calls reject.
Turn on the capability in your app config before prebuild, which adds the Sign in with Apple entitlement:
import { one } from 'one/vite'
export default { plugins: [ one({ native: { app: { name: 'MyApp', ios: { bundleId: 'com.example.myapp', usesAppleSignIn: true }, }, }, }), ],}Then call signIn from a button:
import { One } from 'one'
async function signInWithApple() { const result = await One.Auth.Apple.signIn({ requestedScopes: ['fullName', 'email'] }) if (result.type === 'cancel') return null const { user, identityToken, authorizationCode, email, fullName } = result.credential return { user, identityToken, authorizationCode, email, name: fullName?.givenName ?? null }}The request. signIn builds an Apple ID request with the scopes, nonce,
and state you pass, then presents it over the app’s key window. iOS shows
its own sheet; nothing of it renders in your React tree.
The result. Tapping Continue resolves success with a credential.
Closing the sheet resolves { type: 'cancel' }, so backing out is an
outcome you branch on, never an exception. Any other failure rejects with
code E_AUTH_SIGN_IN, and the message carries Apple’s error domain and
number.
The credential. user is the same for this person in every app from
your team, on every device, and is the ID to key accounts on.
identityToken is a JSON Web Token signed by Apple, and
authorizationCode is a short-lived code your server can exchange with
Apple. Both arrive as strings; anything Apple leaves out arrives as null.
First time only. Apple sends fullName and email only the first time
a person signs in to your app. Later sign-ins return null for both, so
store them as soon as they arrive. The email may be a private relay address
that forwards to the real one.
Credential state. getCredentialState(user) asks Apple whether that ID
is still signed in to your app. People can stop using Sign in with Apple
for your app from Settings at any time, and this is how you find out.
| Member | Returns |
|---|---|
| isAvailable | true on iOS, false elsewhere |
| signIn(options?) | A promise of success or cancel |
| getCredentialState(user) | A promise of the state |
| AppleSignInOptions | Type | Description |
|---|---|---|
requestedScopesAppleAuthScope[]fullName, email, or both. Omit for the ID only. | AppleAuthScope[] | fullName, email, or both. Omit for the ID only. |
statestringReturned unchanged in the credential. | string | Returned unchanged in the credential. |
noncestringPlaced in the identity token, for your server to match. | string | Placed in the identity token, for your server to match. |
| AppleCredential | Type | Description |
|---|---|---|
userstringThe stable user ID for your team. | string | The stable user ID for your team. |
statestring | nullThe state you passed. | string | null | The state you passed. |
fullNameAppleFullName | nullName parts, on the first sign-in only. | AppleFullName | null | Name parts, on the first sign-in only. |
emailstring | nullThe email or relay address, on the first sign-in only. | string | null | The email or relay address, on the first sign-in only. |
realUserStatusAppleRealUserStatuslikelyReal, unknown, or unsupported. | AppleRealUserStatus | likelyReal, unknown, or unsupported. |
identityTokenstring | nullA JWT signed by Apple, for your server. | string | null | A JWT signed by Apple, for your server. |
authorizationCodestring | nullA short-lived code to exchange with Apple. | string | null | A short-lived code to exchange with Apple. |
AppleFullName has namePrefix, givenName, middleName, familyName,
nameSuffix, and nickname, each a string or null.
AppleCredentialState is authorized, revoked, notFound, or
transferred. A failed state check rejects with E_AUTH_CREDENTIAL_STATE.
| iOS | Android | Web | |
|---|---|---|---|
| Available | Yes | No | No |
| Sign in | System sheet | Rejects | Rejects |
| State check | Yes | Rejects | Rejects |
identityToken to your server and check its signature
against Apple’s public keys, its audience, and its nonce before creating
a session.isAvailable before showing the button. On Android and web,
signIn rejects with Auth.Apple.signIn needs an iOS build. Offer
another sign-in method there.getCredentialState with the saved ID when the app starts and
sign people out when it is revoked or notFound.usesAppleSignIn, or with an App
ID that lacks the capability, signIn rejects with an authorization
error rather than showing the sheet.One.iOS.SignInWithAppleButton
draws the system button and reports through its own onCompletion.
signIn works from any button you style yourself.import { One } from 'one'
async function signInWithAppleNonce(sendToServer: (body: object) => Promise<void>) { const nonce = crypto.randomUUID() const result = await One.Auth.Apple.signIn({ requestedScopes: ['email'], nonce }) if (result.type === 'cancel') return false const { user, identityToken, email } = result.credential await sendToServer({ user, identityToken, email, nonce }) return true}The server checks that the token’s nonce claim equals the nonce sent with
it, so a token captured from another sign-in cannot be replayed.
import { useEffect } from 'react'import { One } from 'one'
function useAppleSessionCheck(savedUser: string | null, signOut: () => void) { useEffect(() => { if (!savedUser || !One.Auth.Apple.isAvailable) return One.Auth.Apple.getCredentialState(savedUser).then((state) => { if (state === 'revoked' || state === 'notFound') signOut() }) }, [savedUser, signOut])}Running the check on mount catches people who stopped using Sign in with
Apple for your app while it was closed. transferred means the app moved
to another developer team; keep the session and migrate the ID on your
server.
import { Button } from 'react-native'import { One } from 'one'
function SignInOptions({ onApple, onEmail }: { onApple: () => void; onEmail: () => void }) { return ( <> {One.Auth.Apple.isAvailable && <Button title="Sign in with Apple" onPress={onApple} />} <Button title="Sign in with email" onPress={onEmail} /> </> )}isAvailable is read synchronously, so it can decide the layout during the
first render on every platform.
Edit this page on GitHub.