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

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.

  1. Go to Apple Developer Console - Identifiers
  2. Click the + button to create a new identifier
  3. Select App IDs and click Continue
  4. Select App as the type and click Continue
  5. Fill in the App ID details:
    • Description: Your app name (e.g., "My Awesome App")
    • Bundle ID: Use Explicit, e.g., com.yourcompany.yourapp
  6. Scroll down to Capabilities and check Sign in with Apple
  7. 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.

  1. Go to Apple Developer Console - Services IDs
  2. Click the + button to create a new identifier
  3. Select Services IDs and click Continue
  4. Fill in the details:
    • Description: "OAuth42 Sign in with Apple"
    • Identifier: A reverse-domain identifier, e.g., com.yourcompany.oauth42.signin
  5. Click Continue, then Register
  6. Click on the newly created Services ID to configure it
  7. Check Sign in with Apple and click Configure
  8. 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
  9. 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.

  1. Go to Apple Developer Console - Keys
  2. Click the + button to create a new key
  3. Enter a Key Name (e.g., "OAuth42 Sign in with Apple Key")
  4. Check Sign in with Apple and click Configure
  5. Select the Primary App ID you created earlier
  6. Click Save, then Continue, then Register
  7. Click Download to get the .p8 file

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/callback

Local Development:

https://localhost:8443/api/social-auth/callback

Apple'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:

  1. Log in to the OAuth42 Portal
  2. Navigate to Applications and select your application
  3. Click the Social Providers tab
  4. Click Add Provider
  5. Select Apple from the dropdown
  6. 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 .p8 file (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
  7. 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.ts

Add 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

  1. Start your application
  2. Navigate to your login page
  3. Click "Sign in with Apple"
  4. You'll be redirected to Apple's sign-in page
  5. Sign in with your Apple ID (Face ID / Touch ID / Password)
  6. Choose whether to share or hide your email
  7. Apple redirects back to OAuth42 (via POST)
  8. OAuth42 creates/links your user and redirects to your app
  9. 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

Need Help?

Having trouble with Apple Sign-In? Check our documentation or reach out to support.