Providers
Apple Provider
Configure Sign in with Apple for Keyloom. Complete Apple Developer setup, certificates, team ID, private keys, and troubleshooting.
Apple Provider
Add Sign in with Apple to your Keyloom app. This guide covers the complete Apple Developer setup process including certificates and private keys.
Prerequisites
- Apple Developer account (individual or organization)
- Keyloom app with
@keyloom/providersinstalled - Public app URL for callback configuration
- OpenSSL or similar tool for certificate handling
Apple Developer setup
1. Create App ID
- Go to Apple Developer Console
- Navigate to Certificates, Identifiers & Profiles → Identifiers
- Click "+" to create new App ID
- Select "App IDs" and continue
- Configure:
- Description: Your app name
- Bundle ID:
com.yourcompany.yourapp(explicit, not wildcard) - Capabilities: Enable "Sign In with Apple"
2. Create Services ID
- In Identifiers, click "+" again
- Select "Services IDs" and continue
- Configure:
- Description: Your app name (web)
- Identifier:
com.yourcompany.yourapp.web(different from App ID)
- Enable "Sign In with Apple"
- Click "Configure" next to Sign In with Apple:
- Primary App ID: Select the App ID created above
- Web Domain:
yourapp.com(your domain, no protocol) - Return URLs:
https://yourapp.com/api/auth/oauth/apple/callback
3. Create Private Key
- Navigate to Keys section
- Click "+" to create new key
- Configure:
- Key Name: "Sign in with Apple Key"
- Services: Enable "Sign In with Apple"
- Click "Configure" and select your Primary App ID
- Register the key and download the .p8 file
- Note the Key ID (10-character string)
4. Get Team ID
- In Apple Developer Console, go to Membership
- Note your Team ID (10-character string)
Environment variables
APPLE_CLIENT_ID=com.yourcompany.yourapp.web
APPLE_TEAM_ID=ABC123DEF4
APPLE_KEY_ID=XYZ987WVU6
APPLE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg...
-----END PRIVATE KEY-----"
NEXT_PUBLIC_APP_URL=https://yourapp.comPrivate key handling
Option 1: Environment variable (recommended)
# Convert .p8 file to single-line format
cat AuthKey_XYZ987WVU6.p8 | tr '\n' '\\n'Add to .env.local:
APPLE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg...\\n-----END PRIVATE KEY-----"Option 2: File path
APPLE_PRIVATE_KEY_PATH=./keys/AuthKey_XYZ987WVU6.p8Provider configuration
import { defineKeyloom } from "@keyloom/core";
import { apple } from "@keyloom/providers";
export default defineKeyloom({
baseUrl: process.env.NEXT_PUBLIC_APP_URL!,
providers: [
apple({
clientId: process.env.APPLE_CLIENT_ID!,
teamId: process.env.APPLE_TEAM_ID!,
keyId: process.env.APPLE_KEY_ID!,
privateKey: process.env.APPLE_PRIVATE_KEY!,
// Optional: customize scopes
scope: "name email",
}),
],
// ... rest of config
});Sign-in UI example
export function SignInWithApple() {
return (
<a
href="/api/auth/oauth/apple/start?callbackUrl=/dashboard"
className="flex items-center gap-2 px-4 py-2 bg-black text-white rounded"
>
<AppleIcon />
Continue with Apple
</a>
);
}User profile mapping
Apple returns profile data in the ID token:
{
"sub": "001234.567890abcdef123456789",
"email": "user@privaterelay.appleid.com",
"email_verified": "true",
"name": {
"firstName": "John",
"lastName": "Doe"
}
}Keyloom maps it to:
{
id: profile.sub,
email: profile.email,
name: profile.name ? `${profile.name.firstName} ${profile.name.lastName}` : null,
image: null, // Apple doesn't provide profile images
emailVerified: profile.email_verified === "true",
}Callback URL patterns
- Development:
http://localhost:3000/api/auth/oauth/apple/callback - Production:
https://yourapp.com/api/auth/oauth/apple/callback - Must match exactly what's configured in Apple Developer Console
Error handling
Common Apple OAuth errors:
invalid_client: Client ID, team ID, or key ID mismatchinvalid_grant: Authorization code expired or invalidinvalid_request: Missing required parametersunsupported_response_type: Incorrect response type
// In your error boundary or callback handler
if (error?.code === "oauth_error" && error?.provider === "apple") {
switch (error?.details?.error) {
case "invalid_client":
return "Apple configuration error. Check client ID, team ID, and key ID.";
case "invalid_grant":
return "Authorization expired. Please try signing in again.";
default:
return "Apple sign-in failed. Please try again.";
}
}Security considerations
- Private Key: Store securely; never commit to version control
- Team ID/Key ID: Can be public but keep organized
- Client Secret: Generated dynamically using private key (handled by Keyloom)
- Email Privacy: Users may choose private relay emails
Apple-specific features
- Private Email Relay: Users can hide their real email
- Name Sharing: Users can choose to share or hide their name
- Two-Factor Authentication: Built into Apple ID
- Cross-Platform: Works on iOS, macOS, web
Troubleshooting
Invalid client error
- Verify Client ID matches Services ID identifier
- Check Team ID is correct (10 characters)
- Ensure Key ID matches the downloaded key
Private key issues
- Verify .p8 file format and line endings
- Check private key is properly escaped in environment variable
- Ensure key has "Sign In with Apple" capability enabled
Callback URL mismatch
- Must exactly match what's configured in Apple Developer Console
- Check protocol (HTTP vs HTTPS), domain, and path
- Verify domain is added to Web Domain list
Missing user data
- Apple only provides name/email on first authorization
- Subsequent sign-ins may only include user ID
- Store user data on first sign-in for future reference
Performance considerations
- Apple OAuth is reliable but can be slower than other providers
- Client secret generation adds minimal overhead
- Cache user profile data after first sign-in
See also
- Providers overview: /docs/providers/overview
- Security: /docs/security/overview
- Installation: /docs/getting-started/installation
Next steps
- Test the complete sign-in flow in development
- Configure production Apple Developer settings
- Handle private email relay addresses in your app logic
How is this guide?