Prompt
How do I implement Sign in with Apple on web and mobile?
Latest observation
Here’s the practical way to implement Sign in with Apple for web and mobile.
1) What Sign in with Apple gives you
When a user signs in, Apple returns:
- a stable user identifier (
sub) for your app/team - email (sometimes)
- name only on the first authorization
- an identity token (
id_token, a JWT) - an authorization code (
code) you can exchange server-side for tokens
You should treat the server as the source of truth and verify Apple’s tokens on your backend.
2) Setup in Apple Developer
You need:
- an Apple Developer Program account
- your app IDs / service IDs configured
- a private key for Apple Sign In token exchange
- proper redirect/callback URLs
In the Apple Developer portal
Create/configure:
-
App ID
Enable Sign in with Apple -
Service ID (for web)
This is your OAuth client identifier for the browser flow. -
Key
Create a Sign in with Apple key to sign client secrets. -
Redirect URL
Add your backend callback URL(s), e.g.:https://yourdomain.com/auth/apple/callback
You will need these values:
- Team ID
- Key ID
- Client ID
- mobile: your app bundle ID
- web: your Service ID
- Private key (.p8)
3) Recommended architecture
Use this pattern:
- Frontend (web/mobile) starts Apple sign-in
- Apple returns to your backend
- Backend verifies Apple response
- Backend creates/links your local user
- Backend issues your own session/JWT
Do not rely only on client-side parsing of Apple tokens for authentication.
4) Web implementation
Typical flow
- User clicks “Continue with Apple”
- Redirect to Apple authorization endpoint
- User authenticates
- Apple redirects back to your callback URL with
codeandid_token - Backend exchanges
codefor tokens - Backend verifies
id_token - Backend logs the user in
Authorization endpoint
Your frontend can redirect to Apple’s auth URL like this:
https://appleid.apple.com/auth/authorize
Common query params:
response_type=code id_tokenresponse_mode=form_postclient_id=YOUR_SERVICE_IDredirect_uri=https://yourdomain.com/auth/apple/callbackscope=name emailstate=RANDOM_CSRF_TOKENnonce=RANDOM_NONCEresponse_type=code id_token
Example redirect URL
https://appleid.apple.com/auth/authorize?
response_type=code%20id_token&
response_mode=form_post&
client_id=com.example.web&
redirect_uri=https%3A%2F%2Fyourdomain.com%2Fauth%2Fapple%2Fcallback&
scope=name%20email&
state=xyz123&
nonce=abc456
5) Backend: exchange code for tokens
Apple token endpoint:
https://appleid.apple.com/auth/token
You send:
client_idclient_secretcodegrant_type=authorization_coderedirect_uri
Client secret
For Apple, your client_secret is a JWT you generate with:
- header:
alg:ES256kid: your Key ID
- claims:
iss: your Team IDiat: issued-atexp: expiration (max ~6 months is commonly used; keep it shorter)aud:https://appleid.apple.comsub: your client ID (Service ID for web)
Example token request
POST /auth/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=AUTH_CODE_FROM_APPLE&
redirect_uri=https%3A%2F%2Fyourdomain.com%2Fauth%2Fapple%2Fcallback&
client_id=com.example.web&
client_secret=YOUR_SIGNED_JWT
6) Verify Apple’s identity token
The id_token is a JWT signed by Apple. Verify:
- signature against Apple’s public keys
issishttps://appleid.apple.comaudmatches your client IDexpis validnoncematches what you sentsubis the Apple user ID you store
Apple JWKS endpoint:
https://appleid.apple.com/auth/keys
7) Mobile implementation
iOS / iPadOS
Use Apple’s native framework:
AuthenticationServicesASAuthorizationAppleIDProviderASAuthorizationController
This is the standard native flow.
Swift example
import AuthenticationServices
class AppleSignIn: NSObject, ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding {
func startSignIn() {
let provider = ASAuthorizationAppleIDProvider()
let request = provider.createRequest()
request.requestedScopes = [.fullName, .email]
request.nonce = "hashedNonceHere"
let controller = ASAuthorizationController(authorizationRequests: [request])
controller.delegate = self
controller.presentationContextProvider = self
controller.performRequests()
}
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
if let credential = authorization.credential as? ASAuthorizationAppleIDCredential {
let identityToken = credential.identityToken
let authorizationCode = credential.authorizationCode
let userId = credential.user
let email = credential.email
let fullName = credential.fullName
// Send token/code to backend
}
}
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
print("Apple sign in error: \(error)")
}
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
return UIApplication.shared.connectedScenes
.compactMap { $0 as? UIWindowScene }
.first?.windows.first { $0.isKeyWindow }!
}
}
iOS backend handling
Send to your backend:
identityTokenauthorizationCode- possibly
userandnonce
Then backend:
- verifies
id_token - exchanges
authorization_codeif needed - links or creates your local user
- creates session/JWT
Android
Apple does not provide a native Android SDK for Apple Sign In. For Android, use the web OAuth flow in a browser/custom tab and handle the redirect back to your app.
Typical approach:
- Open Apple auth URL in browser/custom tab
- Use a redirect URI that returns to your app via:
- app link / universal link
- custom scheme, if allowed in your design
- Handle
codeon backend or via your app and exchange server-side
For most teams, the cleanest Android implementation is:
- start web OAuth in a browser
- redirect to backend callback
- backend issues your app session token
- app receives your app’s auth result
8) Cross-platform mobile guidance
If you have both iOS and Android:
- iOS: native Apple auth framework
- Android: web OAuth flow
- Backend unified for both:
- verify Apple token
- map Apple
subto your user - issue your app session
9) Important implementation details
A) Store the Apple user identifier
The key stable identifier is:
subfrom the verifiedid_token
Use it to link the Apple account to your user record.
B) Email may only be returned once
Apple may provide email only on first sign-in. Save it immediately if you need it.
C) Name is only returned once
Same for fullName on iOS.
D) Use nonce protection
Generate a random nonce on your backend/client, send a hashed version to Apple, and verify it in the returned id_token.
E) Handle private relay emails
Apple may return addresses like:
user@privaterelay.appleid.com
Treat them as real user emails if your system supports them.
10) Example backend verification flow
Pseudo-steps:
- Receive
codeandid_token - Verify JWT signature using Apple JWKS
- Check claims:
issaudexpnonce
- Extract:
subemail
- Look up user by Apple
sub - If user exists, log in
- If not, create account and link Apple identity
- Issue your own session/JWT
11) Common mistakes
- Using the wrong client ID:
- web uses Service ID
- iOS uses bundle ID
- Not verifying the
id_tokenserver-side - Forgetting to include a valid
redirect_uri - Not handling Apple’s
form_postresponse mode - Expecting email/name on every login
- Not persisting Apple
sub - Not validating
stateandnonce
12) If you’re using a framework
If you tell me your stack, I can give you exact code. Common setups:
-
Web
- Node/Express
- Next.js
- Django
- Rails
- Spring Boot
-
Mobile
- Swift/iOS
- React Native
- Flutter
- Kotlin/Android
13) Minimal implementation checklist
Web
- Create Service ID
- Add redirect URL
- Generate Apple client secret JWT
- Redirect to Apple auth endpoint
- Handle callback
- Verify JWT and exchange code
- Create local session
Mobile iOS
- Enable Sign in with Apple capability
- Use AuthenticationServices
- Request name/email
- Send token/code to backend
- Verify on backend
Android
- Use web OAuth flow
- Handle redirect back to app/backend
- Verify on backend
- Issue app session
If you want, I can provide:
- a Node.js/Express backend example,
- a Next.js implementation, or
- a Swift iOS + backend example.