Add Apple Sign-In to Your App
Enable "Sign in with Apple" for your webapp and iOS mobile app users through OAuth42's social authentication feature.
What You'll Build
In this tutorial, you'll configure Apple as a social identity provider for your OAuth42 application. Your users will be able to click "Sign in with Apple" and authenticate using their Apple ID.
How it works:
- User clicks "Sign in with Apple" in your app
- OAuth42 redirects to Apple for authentication
- Apple authenticates the user and returns to OAuth42 (via POST)
- OAuth42 creates/links the user account and issues tokens to your app
- Your app receives the tokens and the user is logged in
Apple's Unique Features:
- Private Relay Email: Users can choose to hide their real email, receiving a unique @privaterelay.appleid.com address
- Name only on first auth: Apple only sends the user's name during the first authorization
- Required for iOS apps: If your iOS app offers third-party sign-in, Apple Sign-In must be offered
Prerequisites
- An OAuth42 account with an existing application
- An Apple Developer account ($99/year membership required)
- Your application already integrated with OAuth42 (see Getting Started with Hosted Auth)
- A domain with valid SSL certificate (Apple requires HTTPS)
Step 1: Create an App ID in Apple Developer Console
First, you need to create an App ID that will be the parent identifier for your Sign in with Apple capability.
- Go to Apple Developer Console - Identifiers
- Click the + button to create a new identifier
- Select App IDs and click Continue
- Select App as the type and click Continue
- Fill in the App ID details:
- Description: Your app name (e.g., "My Awesome App")
- Bundle ID: Use Explicit, e.g.,
com.yourcompany.yourapp
- Scroll down to Capabilities and check Sign in with Apple
- Click Continue, then Register
Note your Team ID! You can find it in the top-right of the Apple Developer Console or in Membership details. It's a 10-character alphanumeric string (e.g., ABC1234567).
Step 2: Create a Services ID for Web Authentication
A Services ID is required for web-based Sign in with Apple. This will be your OAuth client_id.
- Go to Apple Developer Console - Services IDs
- Click the + button to create a new identifier
- Select Services IDs and click Continue
- Fill in the details:
- Description: "OAuth42 Sign in with Apple"
- Identifier: A reverse-domain identifier, e.g.,
com.yourcompany.oauth42.signin
- Click Continue, then Register
- Click on the newly created Services ID to configure it
- Check Sign in with Apple and click Configure
- Configure the web authentication:
- Primary App ID: Select the App ID you created in Step 1
- Domains: Add
api.oauth42.com(or your OAuth42 domain) - Return URLs: Add
https://api.oauth42.com/api/social-auth/callback
- Click Next, then Done, then Continue, then Save
Your Services ID (Identifier) is your client_id - Copy this value, you'll need it when configuring OAuth42.
Step 3: Generate a Private Key for Sign in with Apple
Apple uses a private key to generate JWT client secrets. You'll create a key and download the .p8 file.
- Go to Apple Developer Console - Keys
- Click the + button to create a new key
- Enter a Key Name (e.g., "OAuth42 Sign in with Apple Key")
- Check Sign in with Apple and click Configure
- Select the Primary App ID you created earlier
- Click Save, then Continue, then Register
- Click Download to get the
.p8file
Download the .p8 file immediately! Apple only allows you to download it once. If you lose it, you'll need to create a new key.
Note the Key ID! It's displayed on the key details page (10-character alphanumeric, e.g., XYZ0987654). You'll need this for OAuth42 configuration.
Step 4: Verify Your OAuth42 Callback URL
Make sure you've added the correct OAuth42 callback URL to your Apple Services ID configuration.
Production:
https://api.oauth42.com/api/social-auth/callbackLocal Development:
https://localhost:8443/api/social-auth/callbackApple's Domain Requirements: Apple requires that your callback domain has a valid SSL certificate and is publicly accessible. For local development, you may need to use a tunneling service like ngrok or test with production credentials.
Step 5: Configure Apple in OAuth42 Portal
Now configure Apple as a social provider in your OAuth42 application:
- Log in to the OAuth42 Portal
- Navigate to Applications and select your application
- Click the Social Providers tab
- Click Add Provider
- Select Apple from the dropdown
- Enter the required fields:
- Service ID: Your Services ID identifier (e.g.,
com.yourcompany.oauth42.signin) - found in Certificates, Identifiers & Profiles under Identifiers - Private Key (.p8 contents): Paste the entire contents of your
.p8file (including BEGIN/END lines) - downloaded from Keys when creating the Sign in with Apple key - Team ID: Your 10-character Apple Developer Team ID (e.g.,
ABC1234567) - found in Apple Developer account under Membership - Key ID: Your 10-character Key ID from the private key (e.g.,
XYZ0987654) - found in Certificates, Identifiers & Profiles under Keys
- Service ID: Your Services ID identifier (e.g.,
- Click Add Provider to save
Example .p8 file contents:
-----BEGIN PRIVATE KEY-----
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg...
...your key content here...
-----END PRIVATE KEY-----Secure Storage: Your Apple private key is encrypted with AES-256 before being stored. It's never logged or exposed in plain text. OAuth42 uses the key to generate JWT client secrets on each authentication request.
Step 6: Add Apple Sign-In Button to Your App
If you're using OAuth42's hosted auth pages, the Apple Sign-In button appears automatically when you enable the provider. No code changes needed!
For custom login pages, add an Apple Sign-In button that initiates the social auth flow:
"use client"
import { useState } from 'react'
export default function LoginPage() {
const [isLoading, setIsLoading] = useState(false)
const handleAppleSignIn = async () => {
setIsLoading(true)
// Call OAuth42's social auth init endpoint
const response = await fetch('/api/auth/social/apple', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
provider: 'apple',
client_id: process.env.NEXT_PUBLIC_OAUTH42_CLIENT_ID,
is_signup: false,
}),
})
const data = await response.json()
if (data.authorization_url) {
// Redirect to Apple
window.location.href = data.authorization_url
}
}
return (
<div className="min-h-screen flex items-center justify-center bg-gray-100">
<div className="max-w-md w-full space-y-6 p-8 bg-white rounded-lg shadow">
<h1 className="text-2xl font-bold text-center">Sign In</h1>
{/* Apple Sign-In Button - Following Apple's UI Guidelines */}
<button
onClick={handleAppleSignIn}
disabled={isLoading}
className="w-full flex items-center justify-center gap-3 py-3 px-4 bg-black text-white rounded-lg hover:bg-gray-800 transition-colors"
>
<svg className="w-5 h-5" viewBox="0 0 24 24" fill="currentColor">
<path d="M17.05 20.28c-.98.95-2.05.8-3.08.35-1.09-.46-2.09-.48-3.24 0-1.44.62-2.2.44-3.06-.35C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.54 4.09l.01-.01zM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25z"/>
</svg>
{isLoading ? 'Redirecting...' : 'Sign in with Apple'}
</button>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-gray-300" />
</div>
<div className="relative flex justify-center text-sm">
<span className="px-2 bg-white text-gray-500">or</span>
</div>
</div>
{/* Your regular email/password login form */}
<form className="space-y-4">
<input
type="email"
placeholder="Email"
className="w-full px-4 py-3 border border-gray-300 rounded-lg"
/>
<input
type="password"
placeholder="Password"
className="w-full px-4 py-3 border border-gray-300 rounded-lg"
/>
<button
type="submit"
className="w-full py-3 px-4 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700"
>
Sign In
</button>
</form>
</div>
</div>
)
}Apple UI Guidelines: Apple has strict guidelines for the "Sign in with Apple" button appearance. Always use a black or white button with the Apple logo.
Step 7: Create API Route for Social Auth (Custom Login Only)
If using a custom login page, create an API route to initiate the Apple auth flow:
mkdir -p app/api/auth/social/apple
touch app/api/auth/social/apple/route.tsAdd the following code to app/api/auth/social/apple/route.ts:
import { NextRequest, NextResponse } from 'next/server'
const API_BASE_URL = process.env.OAUTH42_ISSUER || 'https://api.oauth42.com'
export async function POST(request: NextRequest) {
try {
const body = await request.json()
const response = await fetch(`${API_BASE_URL}/api/social-auth/init`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
provider: 'apple',
client_id: body.client_id,
is_signup: body.is_signup || false,
state: body.state,
}),
})
const data = await response.json()
if (!response.ok) {
return NextResponse.json(data, { status: response.status })
}
return NextResponse.json(data)
} catch (error) {
console.error('[Apple Social Auth] Error:', error)
return NextResponse.json(
{ error: 'Failed to initiate Apple auth' },
{ status: 500 }
)
}
}iOS Mobile App Integration
For iOS apps, you can use Apple's native AuthenticationServices framework to provide a seamless Sign in with Apple experience.
Option 1: Native Apple Sign-In (Recommended)
Use the native Sign in with Apple button and exchange the identity token with OAuth42:
import AuthenticationServices
import SwiftUI
struct AppleSignInButton: View {
@Environment(\.colorScheme) var colorScheme
var body: some View {
SignInWithAppleButton(
onRequest: { request in
request.requestedScopes = [.email, .fullName]
},
onCompletion: { result in
switch result {
case .success(let authorization):
handleAppleSignIn(authorization: authorization)
case .failure(let error):
print("Sign in with Apple failed: \(error)")
}
}
)
.signInWithAppleButtonStyle(colorScheme == .dark ? .white : .black)
.frame(height: 50)
}
func handleAppleSignIn(authorization: ASAuthorization) {
guard let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential,
let identityToken = appleIDCredential.identityToken,
let tokenString = String(data: identityToken, encoding: .utf8) else {
return
}
// Exchange Apple identity token with OAuth42
Task {
await exchangeAppleToken(
identityToken: tokenString,
authorizationCode: String(data: appleIDCredential.authorizationCode ?? Data(), encoding: .utf8) ?? ""
)
}
}
func exchangeAppleToken(identityToken: String, authorizationCode: String) async {
// Call your backend API which will forward to OAuth42
// Your backend should call: POST /api/social-auth/apple/native
let url = URL(string: "https://your-api.com/auth/apple/native")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let body: [String: Any] = [
"identity_token": identityToken,
"authorization_code": authorizationCode,
"client_id": "your-oauth42-client-id"
]
request.httpBody = try? JSONSerialization.data(withJSONObject: body)
do {
let (data, _) = try await URLSession.shared.data(for: request)
// Handle OAuth42 tokens response
let tokens = try JSONDecoder().decode(TokenResponse.self, from: data)
// Store tokens and navigate to main app
} catch {
print("Token exchange failed: \(error)")
}
}
}Option 2: Web-Based Flow in WKWebView
Alternatively, you can use the web-based flow by opening OAuth42's hosted auth pages in a ASWebAuthenticationSession:
import AuthenticationServices
class AuthManager: NSObject, ASWebAuthenticationPresentationContextProviding {
func signInWithApple() {
let authURL = URL(string: "https://your-oauth42-app.com/hosted-auth/login?provider=apple")!
let callbackScheme = "yourapp" // Your app's URL scheme
let session = ASWebAuthenticationSession(
url: authURL,
callbackURLScheme: callbackScheme
) { callbackURL, error in
if let error = error {
print("Auth error: \(error)")
return
}
guard let callbackURL = callbackURL,
let components = URLComponents(url: callbackURL, resolvingAgainstBaseURL: false),
let code = components.queryItems?.first(where: { $0.name == "code" })?.value else {
return
}
// Exchange code for tokens
self.exchangeCodeForTokens(code: code)
}
session.presentationContextProvider = self
session.prefersEphemeralWebBrowserSession = true
session.start()
}
func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
return UIApplication.shared.windows.first { $0.isKeyWindow }!
}
}App Store Requirement: If your iOS app offers any third-party sign-in options (Google, Facebook, etc.), Apple requires you to also offer "Sign in with Apple". See App Store Review Guidelines 4.8.
How Apple Account Linking Works
OAuth42 handles Apple accounts with special consideration for Apple's privacy features:
Private Relay Email
If a user chooses to hide their email, Apple provides a unique @privaterelay.appleid.com address. OAuth42 stores this and marks the account as using a private email. You can still send emails to this address - Apple forwards them to the user's real email.
Name Only on First Auth
Apple only sends the user's name during the first authorization. OAuth42 stores this information when received. If the user has authorized before, their name won't be included in subsequent requests.
Stable User ID
Apple provides a stable sub (subject) identifier that uniquely identifies the user across all your apps in the same team. OAuth42 uses this for reliable account linking.
Step 8: Test Apple Sign-In
- Start your application
- Navigate to your login page
- Click "Sign in with Apple"
- You'll be redirected to Apple's sign-in page
- Sign in with your Apple ID (Face ID / Touch ID / Password)
- Choose whether to share or hide your email
- Apple redirects back to OAuth42 (via POST)
- OAuth42 creates/links your user and redirects to your app
- You're now logged in!
Success! Your users can now sign in with their Apple IDs.
Troubleshooting
"Apple Sign In requires team_id" error
You forgot to enter the Team ID when configuring Apple in OAuth42. Go to the Social Providers tab and add your 10-character Team ID from Apple Developer Console.
"Invalid Apple private key" error
The private key is not in the correct format. Make sure you paste the entire .p8 file contents, including the -----BEGIN PRIVATE KEY----- and -----END PRIVATE KEY----- lines.
"invalid_client" error from Apple
This usually means the Team ID, Key ID, or Service ID is incorrect. Double-check all values in Apple Developer Console match what you entered in OAuth42.
"redirect_uri_mismatch" error
The callback URL in your Apple Services ID configuration doesn't match. Verify that https://api.oauth42.com/api/social-auth/callback is listed in your Return URLs.
"Apple did not provide email" error
The user denied email sharing permission. OAuth42 requires an email address to create user accounts. Users can revoke and re-authorize to share their email, or use a different sign-in method.
User's name is missing
Apple only sends the user's name on the first authorization. If they've authorized before, the name won't be included. To get the name again, the user must go to Settings → Apple ID → Password & Security → Apps Using Apple ID → Your App → Stop Using Apple ID, then re-authorize.
Next Steps
- Add Google Sign-In - Offer multiple social login options
- Add MFA for extra security
- iOS SDK Documentation - Complete iOS integration guide
- Security best practices
Need Help?
Having trouble with Apple Sign-In? Check our documentation or reach out to support.