Sign in with Apple: How to Add Sign in with Apple in Swift
Last updated
To add Sign in with Apple to an iOS app in Swift, you do four things. Enable the Sign in with Apple capability for the App ID and regenerate the provisioning profiles, add com.apple.developer.applesignin to the entitlements, get a credential with SwiftUI’s SignInWithAppleButton and send the identityToken to your server for validation, and finally revoke tokens when a user withdraws consent or deletes their account. The steps below run from the developer portal setup to a successful sign-in on your own iPhone, with Apple rules current as of October 2026.
Have these ready before you start.
- An Apple Developer Program membership with the Account Holder or Admin role. You need one of these two roles to enable capabilities.
- A SwiftUI project that KXBuild can install and run on your iPhone, with a deployment target of iOS 15 or later. If you don’t have one, build one with Build an iOS app on Windows with SwiftUI and run it on your iPhone.
- An iPhone signed in to an Apple Account with two-factor authentication turned on, and with Developer Mode enabled. See how to turn on Developer Mode.
- A backend endpoint of your own that validates the credential and creates the account. This tutorial uses
https://api.example.com/auth/appleas a placeholder.
When must you offer Sign in with Apple?
App Store Review Guideline 4.8 Login Services says that if your app uses a third-party or social login service (such as Facebook or Google) to set up or authenticate the user’s primary account, you must also offer an equivalent login option. That option has to meet three conditions. It limits data collection to the user’s name and email address, it lets users keep their email address private when they set up the account, and it doesn’t collect interactions with your app for advertising without consent. Sign in with Apple meets all three, so it’s the usual choice.
You don’t need to offer another option in these cases.
| Case | Example |
|---|---|
| The app only uses your company’s own account system | Phone number and verification code, your own username and password |
| Education, enterprise or business apps that require an existing school or company account | An internal company app |
| The app uses a government or industry-backed citizen identification system or electronic ID | Signing in with a national electronic ID |
| The app is a client for a specific third-party service, and users sign in to that service directly to access its content | An email client, a dedicated client for one social platform |
| An alternative app marketplace, or an app distributed from one that uses the marketplace’s own login | — |
Two other login-related rules apply as well. Guideline 5.1.1(v) requires apps that support account creation to let users delete their account from within the app. Apple’s account deletion page says that apps using Sign in with Apple must call the Sign in with Apple REST API to revoke the user’s tokens when the user deletes their account. See the revoke section below. Apple makes the review decision. For common rejection reasons, see common App Review rejection reasons.
What are the steps to add Sign in with Apple?
| Step | Where | Result |
|---|---|---|
| 1. Enable Sign in with Apple for the App ID | Identifiers on developer.apple.com | The App ID has the capability |
| 2. Regenerate the provisioning profiles | Profiles on developer.apple.com | Profiles that include com.apple.developer.applesignin |
| 3. Add entitlements | VS Code | MyApp.entitlements |
| 4. Write the sign-in code | VS Code | Sign-in button, credential handling, state checks |
| 5. Create a Sign in with Apple private key | Keys on developer.apple.com | A .p8 your server uses to create the client_secret |
| 6. Server-side validation and revoke | Your backend | Validate the identityToken, exchange and revoke tokens |
| 7. Test on the device | iPhone | First sign-in, repeat sign-in, sign-in after revoking |
Step 1: How do you enable Sign in with Apple for the App ID?
- Sign in to developer.apple.com, open Certificates, Identifiers & Profiles, and open your app’s App ID on the Identifiers page. It must be an explicit Bundle ID, for example
com.example.myapp. - Check Sign in with Apple in the Capabilities list and click Edit or Configure next to it.
- Choose Enable as a primary App ID and save. Choose this the first time you enable it, or when this is a new app.
- Click Save to save the App ID.
If a Mac app or website of the same product should share accounts, set those App IDs to group with the existing primary App ID, so users only have to grant consent once. See Apple’s About Sign in with Apple.
Step 2: Why regenerate the provisioning profiles?
A provisioning profile records the capabilities the App ID had when the profile was generated. Profiles generated before you enabled Sign in with Apple don’t include it, so you have to regenerate them.
- On the Profiles page, open each profile for this App ID (Development, Ad Hoc, App Store), click Edit, click Save without changing anything, then Download.
- Replace the old files in your project’s
keychainfolder with the new ones and delete the old ones. If old and new copies are both in the folder, the build may pick the old one without the capability.
Double-click the new .mobileprovision in VS Code to open it in the Provisioning Profile Viewer. If the entitlements list com.apple.developer.applesignin, the profile includes the capability. For how the keychain folder works and how profiles are picked, see Certificates and signing. For how App IDs relate to profiles, see how Bundle IDs, App IDs, certificates and provisioning profiles fit together.
Step 3: Add entitlements to the project
Create MyApp.entitlements in the app’s source folder with the content below. If your project already has an entitlements file (for example because it uses push or App Groups), add the key to that file instead of creating a new one.
1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3<plist version="1.0">
4<dict>
5 <key>com.apple.developer.applesignin</key>
6 <array>
7 <string>Default</string>
8 </array>
9</dict>
10</plist>
The key name and value follow Apple’s applesignin entitlement documentation. The value is an array with a single Default item.
Then point the target to this file. Use either approach.
- VS Code extension. Right-click the target, choose KXBuild: Edit Target Settings, enter
MyApp/MyApp.entitlementsin Code Signing Entitlements in the Signing group on the General page, and press Ctrl+S to save. - Command line. Run the command below in the project folder. The path is relative to the folder containing the .xcodeproj.
1kxbuild project add-setting -k CODE_SIGN_ENTITLEMENTS -v MyApp/MyApp.entitlements -t MyApp -P .\MyApp.xcodeproj
To add other capabilities later, double-click the .entitlements file and edit it in the Property List Editor. To find the setting, see the Project Settings Editor.
Step 4: Write the sign-in code in Swift
The three files below work with a deployment target of iOS 15 or later and also compile in Swift 6 strict concurrency mode. SignInWithAppleButton comes from SwiftUI, and the credential types come from the AuthenticationServices framework.
Store the user identifier in the keychain
Keep the user identifier in the keychain, not in UserDefaults. Create KeychainStore.swift.
1import Foundation
2import Security
3
4/// Stores a few strings in the keychain. They usually survive deleting and reinstalling the app
5enum KeychainStore {
6 private static let service = "com.example.myapp.auth"
7
8 static func save(_ value: String, for key: String) {
9 delete(key)
10 let query: [String: Any] = [
11 kSecClass as String: kSecClassGenericPassword,
12 kSecAttrService as String: service,
13 kSecAttrAccount as String: key,
14 kSecValueData as String: Data(value.utf8),
15 kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock,
16 ]
17 SecItemAdd(query as CFDictionary, nil)
18 }
19
20 static func read(_ key: String) -> String? {
21 let query: [String: Any] = [
22 kSecClass as String: kSecClassGenericPassword,
23 kSecAttrService as String: service,
24 kSecAttrAccount as String: key,
25 kSecReturnData as String: true,
26 kSecMatchLimit as String: kSecMatchLimitOne,
27 ]
28 var item: CFTypeRef?
29 guard SecItemCopyMatching(query as CFDictionary, &item) == errSecSuccess,
30 let data = item as? Data else { return nil }
31 return String(data: data, encoding: .utf8)
32 }
33
34 static func delete(_ key: String) {
35 let query: [String: Any] = [
36 kSecClass as String: kSecClassGenericPassword,
37 kSecAttrService as String: service,
38 kSecAttrAccount as String: key,
39 ]
40 SecItemDelete(query as CFDictionary)
41 }
42}
Sign-in manager
Create AppleSignInManager.swift. It prepares the request, handles the credential, sends the tokens to your server and checks the credential state. Replace loginURL with your own endpoint.
1import Foundation
2import AuthenticationServices
3import CryptoKit
4
5/// Request body sent to your own server
6struct AppleLoginRequest: Codable {
7 let identityToken: String
8 let authorizationCode: String
9 let rawNonce: String
10 let user: String
11 let email: String?
12 let givenName: String?
13 let familyName: String?
14}
15
16@MainActor
17final class AppleSignInManager: ObservableObject {
18 // Replace with your own server's sign-in endpoint
19 static let loginURL = URL(string: "https://api.example.com/auth/apple")!
20
21 @Published private(set) var userID: String? = KeychainStore.read("appleUserID")
22 @Published var message: String?
23
24 private var currentNonce: String?
25 private var revokeObserver: NSObjectProtocol?
26
27 var isSignedIn: Bool { userID != nil }
28
29 init() {
30 // The system posts this when the user stops using Sign in with Apple for the app in Settings
31 revokeObserver = NotificationCenter.default.addObserver(
32 forName: ASAuthorizationAppleIDProvider.credentialRevokedNotification,
33 object: nil,
34 queue: .main
35 ) { [weak self] _ in
36 Task { @MainActor in
37 self?.signOut(message: "Apple Account authorization was revoked. Please sign in again.")
38 }
39 }
40 }
41
42 /// Called when the button is tapped. Asks for name and email and adds a nonce
43 func prepare(_ request: ASAuthorizationAppleIDRequest) {
44 let nonce = Self.randomNonce()
45 currentNonce = nonce
46 request.requestedScopes = [.fullName, .email]
47 request.nonce = Self.sha256(nonce)
48 }
49
50 /// Handles the result from the button
51 func handle(_ result: Result<ASAuthorization, Error>) async {
52 switch result {
53 case .success(let authorization):
54 guard let credential = authorization.credential as? ASAuthorizationAppleIDCredential else {
55 message = "The credential isn't an Apple ID credential."
56 return
57 }
58 await signIn(with: credential)
59 case .failure(let error):
60 if let authError = error as? ASAuthorizationError, authError.code == .canceled {
61 return // The user tapped Cancel, so don't show anything
62 }
63 message = "Sign-in failed: \(error.localizedDescription)"
64 }
65 }
66
67 private func signIn(with credential: ASAuthorizationAppleIDCredential) async {
68 // user is this person's stable identifier within your developer team. Use it as the account key
69 let user = credential.user
70
71 // Email and name only come back on the first authorization, so save them right away
72 if let email = credential.email {
73 KeychainStore.save(email, for: "appleEmail")
74 }
75 if let name = credential.fullName {
76 let formatted = PersonNameComponentsFormatter().string(from: name)
77 if !formatted.isEmpty {
78 KeychainStore.save(formatted, for: "appleFullName")
79 }
80 }
81
82 guard let tokenData = credential.identityToken,
83 let identityToken = String(data: tokenData, encoding: .utf8),
84 let codeData = credential.authorizationCode,
85 let authorizationCode = String(data: codeData, encoding: .utf8),
86 let rawNonce = currentNonce else {
87 message = "No identityToken was returned. Please try again."
88 return
89 }
90
91 let body = AppleLoginRequest(
92 identityToken: identityToken,
93 authorizationCode: authorizationCode,
94 rawNonce: rawNonce,
95 user: user,
96 email: credential.email ?? KeychainStore.read("appleEmail"),
97 givenName: credential.fullName?.givenName,
98 familyName: credential.fullName?.familyName
99 )
100
101 do {
102 var request = URLRequest(url: Self.loginURL)
103 request.httpMethod = "POST"
104 request.setValue("application/json", forHTTPHeaderField: "Content-Type")
105 request.httpBody = try JSONEncoder().encode(body)
106 let (_, response) = try await URLSession.shared.data(for: request)
107 guard (response as? HTTPURLResponse)?.statusCode == 200 else {
108 message = "Server validation failed."
109 return
110 }
111 KeychainStore.save(user, for: "appleUserID")
112 userID = user
113 message = nil
114 } catch {
115 message = "Network error: \(error.localizedDescription)"
116 }
117 }
118
119 /// Call at launch and when returning to the foreground to confirm the authorization is still valid
120 func checkCredentialState() async {
121 guard let userID else { return }
122 do {
123 let state = try await ASAuthorizationAppleIDProvider().credentialState(forUserID: userID)
124 switch state {
125 case .authorized:
126 break
127 case .revoked:
128 signOut(message: "Apple Account authorization was revoked. Please sign in again.")
129 case .notFound:
130 signOut(message: "Please sign in again.")
131 case .transferred:
132 message = "The app moved to a new developer team. The account needs to be migrated."
133 @unknown default:
134 break
135 }
136 } catch {
137 // If the check fails (for example no Apple Account is signed in), keep the current state and check again later
138 print("getCredentialState failed: \(error)")
139 }
140 }
141
142 func signOut(message: String? = nil) {
143 KeychainStore.delete("appleUserID")
144 userID = nil
145 self.message = message
146 }
147
148 // MARK: - nonce
149
150 static func randomNonce(length: Int = 32) -> String {
151 let charset = Array("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-._")
152 var generator = SystemRandomNumberGenerator()
153 return String((0..<length).map { _ in charset.randomElement(using: &generator)! })
154 }
155
156 static func sha256(_ input: String) -> String {
157 SHA256.hash(data: Data(input.utf8)).map { String(format: "%02x", $0) }.joined()
158 }
159}
A few key points.
- Use
useras the account key. Apple’s documentation says the user identifier is unique and stable within your developer team. Identify users by it, not by email. When a user hides their email, you get a forwarding address atprivaterelay.appleid.com. - Name and email come only once. According to Apple’s Authenticating users with Sign in with Apple, the name and email are only returned the first time a user authorizes your app with Sign in with Apple. Later sign-ins, even on other devices, don’t return them again. They only come back after the user stops using the app with Sign in with Apple and authorizes it again.
- Save them right away. Save them immediately and send them to your server as soon as you can, so you can retry if the network fails. Later
identityTokens still contain the email, but not the name. - The nonce prevents replay attacks. The request carries the SHA-256 of a random string, and the
noncefield in theidentityTokenis that hash. Send the raw random string to your server, which hashes it with SHA-256 and compares. - Don’t treat
.canceledas an error. You get it when the user taps Cancel in the sign-in sheet, so there’s no need to show a message.
Sign-in screen and app entry point
Create LoginView.swift. For the basic setup of a Swift project, see build a Swift iOS app on Windows. The button style follows light and dark mode, with a white button on a dark background.
1import SwiftUI
2import AuthenticationServices
3
4struct LoginView: View {
5 @EnvironmentObject private var auth: AppleSignInManager
6 @Environment(\.colorScheme) private var colorScheme
7
8 var body: some View {
9 VStack(spacing: 20) {
10 if auth.isSignedIn {
11 Text("Signed in")
12 .font(.title2.bold())
13 Text(KeychainStore.read("appleFullName") ?? "Apple user")
14 Text(KeychainStore.read("appleEmail") ?? "No email provided")
15 .foregroundStyle(.secondary)
16 Button("Sign out") {
17 auth.signOut()
18 }
19 } else {
20 SignInWithAppleButton(.signIn) { request in
21 auth.prepare(request)
22 } onCompletion: { result in
23 Task { await auth.handle(result) }
24 }
25 .signInWithAppleButtonStyle(colorScheme == .dark ? .white : .black)
26 .frame(height: 50)
27 }
28
29 if let message = auth.message {
30 Text(message)
31 .font(.footnote)
32 .foregroundStyle(.red)
33 }
34 }
35 .padding(24)
36 }
37}
Replace your app’s entry file (for example MyAppApp.swift) with the code below. It checks the credential state at launch and every time the app returns to the foreground.
1import SwiftUI
2
3@main
4struct MyAppApp: App {
5 @StateObject private var auth = AppleSignInManager()
6 @Environment(\.scenePhase) private var scenePhase
7
8 var body: some Scene {
9 WindowGroup {
10 LoginView()
11 .environmentObject(auth)
12 .task { await auth.checkCredentialState() }
13 }
14 .onChange(of: scenePhase) { phase in
15 if phase == .active {
16 Task { await auth.checkCredentialState() }
17 }
18 }
19 }
20}
With a deployment target of iOS 17 or later, you can switch onChange to the newer two-parameter form { _, phase in ... }. The old form shows a deprecation warning but still works. In a UIKit project, use ASAuthorizationAppleIDButton with ASAuthorizationController. The credential handling is the same as above. See Apple’s sample project.
What’s in the credential
| Field | Content | When it’s present |
|---|---|---|
user |
User identifier, unique and stable within your team | Every time |
email |
Real email or forwarding address | First authorization only |
fullName |
Name, which the user can edit in the sign-in sheet | First authorization only |
identityToken |
A JWT for your server to validate | Every time |
authorizationCode |
A one-time code your server exchanges for a refresh token | Every time |
realUserStatus |
Whether the user looks real, likelyReal, unknown or unsupported |
First authorization only |
How does the app know a user stopped using Sign in with Apple?
Users can stop using Sign in with Apple for your app in Settings at any time, so your app has to confirm the authorization still exists. In Swift, getCredentialState(forUserID:completion:) can be written as try await credentialState(forUserID:). It’s a local call that doesn’t go over the network, so it’s fine to call it every time the app returns to the foreground. Apple’s Verifying a user recommends tying your app’s sign-in session to the result of this call.
| State | Meaning | What to do |
|---|---|---|
.authorized |
The authorization is valid | Carry on |
.revoked |
The user revoked the authorization | Sign out and go back to the sign-in screen |
.notFound |
This user has no authorization with your app | Sign out and go back to the sign-in screen |
.transferred |
The app was transferred to another developer team | Migrate user identifiers using Apple’s team transfer process |
If the user revokes the authorization while the app is running, the system posts credentialRevokedNotification, which the code above observes to sign out. The system doesn’t post this notification when a user permanently deletes their Apple Account. Apple’s documentation says to detect that with getCredentialState, so keep the foreground check.
How does your server validate the identityToken?
The credential your app receives must be validated on your server before you create a sign-in session. The server does three things. It validates the identityToken, exchanges the authorizationCode for a refresh token, and revokes the tokens when the user deletes their account.
Five checks for the identityToken
| Check | How |
|---|---|
| Signature | Fetch Apple’s public key set from https://appleid.apple.com/auth/keys, pick the key matching the kid in the JWT header, and verify the signature with the header’s alg |
iss |
Must be https://appleid.apple.com |
aud |
Must be your client_id, which for an iOS app is its Bundle ID |
exp |
The current time must be before the expiration time |
nonce |
Equals the SHA-256 of the raw random string your app sent |
Once these pass, check that the sub field equals the user your app sent, and use sub as the account key. The checks come from Apple’s Verifying a user, and the key endpoint is described in Fetch Apple’s public key for verifying token signature. The key set may contain several keys, and the number can change. Don’t hard-code them. Pick by kid and cache them for a while.
Create a Sign in with Apple private key
When your server calls Apple’s token endpoint, it sends a client_secret, which is a JWT signed with a Sign in with Apple private key.
- On the Keys page of Certificates, Identifiers & Profiles, click “+”, enter a name, check Sign in with Apple and click Configure next to it.
- Choose the associated primary App ID, save, then click Continue and Register.
- Download the
.p8file and note the Key ID. You can only download this file once.
Each primary App ID can have up to two associated private keys. See Apple’s Create a Sign in with Apple private key. This key is separate from the APNs key used for push. For the push key options, see .p8 or .p12 for push notifications.
The client_secret JWT looks like this, signed with ES256.
1{
2 "alg": "ES256",
3 "kid": "YOUR_KEY_ID"
4}
5{
6 "iss": "YOUR_TEAM_ID",
7 "iat": 1792000000,
8 "exp": 1807000000,
9 "aud": "https://appleid.apple.com",
10 "sub": "com.example.myapp"
11}
Set sub to the client_id, which is the Bundle ID, and it’s case-sensitive. exp can be at most 15777000 seconds (six months) after the current time, or the secret is rejected. See Apple’s Creating a client secret. Regenerate it on your server every few months.
Exchange the authorization code for a refresh token
1curl -X POST "https://appleid.apple.com/auth/token" \
2 -H 'content-type: application/x-www-form-urlencoded' \
3 -d 'client_id=com.example.myapp' \
4 -d 'client_secret=JWT_FROM_THE_PREVIOUS_STEP' \
5 -d 'code=AUTHORIZATION_CODE_FROM_THE_APP' \
6 -d 'grant_type=authorization_code'
On success it returns access_token, refresh_token and id_token. Store the refresh_token with the account on your server, because you need it when the account is deleted. An authorization code can only be used once, and the app gets a new one on every sign-in. See Apple’s Generate and validate tokens.
Apple lets you use the refresh token to check that the user’s Apple Account is still in good standing at most once a day. Calling more often gets throttled.
How do you revoke tokens when an account is deleted?
When a user deletes their account in your app, have your server delete the data and then call the revoke endpoint, so the user’s authorization for your app is no longer valid.
1curl -X POST "https://appleid.apple.com/auth/revoke" \
2 -H 'content-type: application/x-www-form-urlencoded' \
3 -d 'client_id=com.example.myapp' \
4 -d 'client_secret=THE_SAME_CLIENT_SECRET_JWT' \
5 -d 'token=STORED_REFRESH_TOKEN' \
6 -d 'token_type_hint=refresh_token'
On success it returns 200 with no body. It also returns 200 if the token was already invalid. If you didn’t store a refresh token, you can revoke with the access token and set token_type_hint to access_token. See Apple’s Revoke tokens. After revoking, the next time the user signs in to your app with Apple, they authorize name and email again as if it were the first time.
Users can also stop using Sign in with Apple for your app or delete their Apple Account outside your app. Register a server notification endpoint in the App ID’s Sign in with Apple configuration, and Apple sends notifications when these events happen. Event types include consent-revoked (authorization withdrawn), account-deleted (Apple Account deleted), and email-disabled and email-enabled (forwarding email turned off or on). For the format, see Apple’s Processing changes for Sign in with Apple accounts.
If a user hides their email and you need to email them, first register your sending domains or addresses in the developer portal and set up SPF or DKIM. Otherwise the relay service won’t deliver your messages.
How do you test Sign in with Apple on an iPhone?
First sign-in
- In the KXBuild view, set the configuration node to Configuration: Debug, run KXBuild: Build & Run and install the app on your phone. For debugging, see Debug iOS apps on Windows with LLDB, breakpoints and crash logs.
- Tap the Sign in with Apple button. The system shows the authorization sheet with name and email options. You can edit the name, and for email choose Share My Email or Hide My Email.
- Confirm with Face ID, Touch ID or your passcode. The app shows “Signed in” with the name and email.
Set a breakpoint at the start of signIn(with:) to see that email and fullName have values. If your server isn’t ready yet, comment out the loginURL request and save user directly to get the app side working first.
Repeat sign-in
Tap “Sign out” and tap the button again. This time the sheet only has a Continue button, and email and fullName are both nil at the breakpoint. That’s expected, and it’s why you save them the first time.
Revoke the authorization to retest the first sign-in
To see the full first-time authorization again, follow Apple’s Manage apps that you use with Sign in with Apple.
- Open Settings on the phone and tap your name at the top.
- Tap Sign in with Apple.
- Select your app in the list, tap Delete and confirm.
- Go back to the app. When it returns to the foreground,
checkCredentialState()gets.revokedand signs out automatically. - Tap the sign-in button again, and the sheet shows the name and email options again.
Deleting and reinstalling the app doesn’t reset the authorization. The user identifier and the “only once” rule stay the same.
How do you fix common Sign in with Apple errors?
| Symptom | Cause | Fix |
|---|---|---|
Tapping the button fails with ASAuthorizationError 1000 (unknown) |
The signature doesn’t include com.apple.developer.applesignin, usually because the App ID doesn’t have the capability, the profile wasn’t regenerated, or the target doesn’t use the entitlements file |
Double-click the .mobileprovision to confirm the capability, search for CODE_SIGN_ENTITLEMENTS in the Project Settings Editor to confirm the path, and rebuild |
| The system asks you to sign in to an Apple Account in Settings | The phone isn’t signed in to an Apple Account, or two-factor authentication is off | Sign in to an Apple Account in Settings, turn on two-factor authentication and try again |
| Error 1001 (canceled) | The user tapped Cancel, or the sheet was dismissed | Don’t show a message, let the user tap again |
| Error 1004 (failed) or 1002 (invalidResponse) | Authorization failed or the response was invalid | Try again later, or switch networks if yours is restricted |
The build says the profile doesn’t include com.apple.developer.applesignin |
The profile was generated before you enabled the capability | Edit and save the profile, download it again and replace the old file |
Installation fails with signed with invalid entitlements |
The entitlements contain the key but the profile doesn’t | Same as above. See signing errors |
email and fullName are nil |
It isn’t the first authorization | Revoke the authorization as described above and retest. In a production app, read them from the data you saved |
aud validation fails on the server |
client_id was set to a Services ID or another Bundle ID |
For sign-in from an iOS app, aud is the app’s Bundle ID |
/auth/token returns invalid_client |
The client_secret expired, the signature is wrong, sub doesn’t match client_id, or the Key ID or Team ID is wrong |
Regenerate the client_secret and check kid, iss and sub |
/auth/token returns invalid_grant |
The authorization code was already used or has expired | Use the new code from each sign-in and exchange it as soon as you receive it |
The error code values follow ASAuthorizationError.Code in the SDK. For REST API errors, see Apple’s ErrorResponse. For signing and profile errors outside Sign in with Apple, see 16 common iOS code signing and .ipa install errors, and how to fix them.
FAQ
My app has Google or Facebook login. Do I have to add Sign in with Apple?
Under Guideline 4.8, an app that uses a third-party or social login to set up the primary account must also offer a login option that only collects name and email, lets users hide their email, and doesn’t track for advertising. Sign in with Apple meets these conditions and is the most common choice. Apps that only use their own account system, or enterprise or education accounts, don’t need it.
Why don’t I get the email and name on the second sign-in?
Apple only returns the name and email the first time a user authorizes your app. Later sign-ins return only the user identifier and tokens. Save them and send them to your server the first time. Later identityTokens still contain the email, but not the name.
How do I reset Sign in with Apple while testing?
On the iPhone, go to Settings > [your name] > Sign in with Apple, select the app and tap Delete. The next sign-in asks for name and email again.
What do I need to do when a user deletes their account?
Offer account deletion inside the app. After your server deletes the data, call https://appleid.apple.com/auth/revoke with the stored refresh token to revoke the authorization. Apple requires this for apps that use Sign in with Apple.
What causes ASAuthorizationError 1000?
1000 is the unknown error, and the most common cause is that the signature lacks com.apple.developer.applesignin. The App ID doesn’t have the capability, the profile wasn’t regenerated, or the target has no entitlements file set. If signing is correct and it still fails, delete the app and rebuild and install it with the new profile.