One Logo Pool Ball
wwwwwwwwwwwwwwwwwww

Authentication

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.

Usage

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 }
}

How it works

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.

API reference

MemberReturns
isAvailabletrue on iOS, false elsewhere
signIn(options?)A promise of success or cancel
getCredentialState(user)A promise of the state
AppleSignInOptionsTypeDescription
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.
stringReturned unchanged in the credential.
noncestringPlaced in the identity token, for your server to match.
stringPlaced in the identity token, for your server to match.
AppleCredentialTypeDescription
userstringThe stable user ID for your team.
stringThe stable user ID for your team.
statestring | nullThe state you passed.
string | nullThe state you passed.
fullNameAppleFullName | nullName parts, on the first sign-in only.
AppleFullName | nullName parts, on the first sign-in only.
emailstring | nullThe email or relay address, on the first sign-in only.
string | nullThe email or relay address, on the first sign-in only.
realUserStatusAppleRealUserStatuslikelyReal, unknown, or unsupported.
AppleRealUserStatuslikelyReal, unknown, or unsupported.
identityTokenstring | nullA JWT signed by Apple, for your server.
string | nullA JWT signed by Apple, for your server.
authorizationCodestring | nullA short-lived code to exchange with Apple.
string | nullA 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.

Platform differences

iOSAndroidWeb
AvailableYesNoNo
Sign inSystem sheetRejectsRejects
State checkYesRejectsRejects

Limits and gotchas

Examples

Sign in with a nonce

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.

Sign out when Apple access is revoked

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.

Show the button only where it works

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.