โ† All articles

How to Add Sign in with Apple to a SwiftUI App

Add Sign in with Apple to a SwiftUI app in 2026: the button, the credential, the nonce, why name and email arrive only once, and server-side verification.

How to Add Sign in with Apple to a SwiftUI App

Sign in with Apple is the fastest authentication you can add to a SwiftUI app, and for many apps it is not optional. The client side is a single SwiftUI button and one delegate closure. The parts that trip people up are not the button, they are the details around it: the name and email that Apple sends exactly once, the nonce that keeps the sign-in from being replayed, and the identity token that has to be verified on a server rather than trusted on device.

This guide covers the whole path: turning on the capability, adding the button, handling the credential, dealing with the once-only name and email, wiring the nonce and token verification, and re-checking the credential on launch. The examples are current SwiftUI and AuthenticationServices.

On this page

Why Sign in with Apple

Two reasons, one practical and one that is a review requirement.

The practical one is friction. Sign in with Apple authenticates a user with Face ID or Touch ID in a single tap, with no password to create or forget, and Apple's private relay lets users hide their real email. For an indie app, that removes the biggest drop-off point in onboarding.

The requirement is App Store Review Guideline 4.8. If your app offers a third-party or social login (Google, Facebook, and similar) to set up or authenticate the primary account, it must also offer Sign in with Apple as an equivalent option. If your only login is email-and-password you own, or you have no account system, Sign in with Apple is not mandatory, but it is still usually the smoothest option to add.

Step 1: Turn on the capability

This is a one-time project setup, done before any code.

  1. In your Apple Developer account, the App ID for your bundle identifier must have the Sign in with Apple capability enabled.
  2. In Xcode, select your target, open Signing and Capabilities, and add the Sign in with Apple capability. Xcode writes the entitlement for you.

That is the whole configuration. There is no SDK to install; AuthenticationServices ships with the system.

Step 2: Add the SwiftUI button

SignInWithAppleButton is a first-party SwiftUI view. It takes a request closure, where you ask for the scopes you want, and a completion closure, where you receive the result. Use the system button style so it matches Apple's guidelines automatically.

import SwiftUI
import AuthenticationServices
 
struct SignInView: View {
    var body: some View {
        SignInWithAppleButton(.signIn) { request in
            request.requestedScopes = [.fullName, .email]
        } onCompletion: { result in
            switch result {
            case .success(let authorization):
                handle(authorization)
            case .failure(let error):
                print("Sign in with Apple failed: \(error.localizedDescription)")
            }
        }
        .signInWithAppleButtonStyle(.black)
        .frame(height: 50)
        .cornerRadius(10)
    }
}

Request only the scopes you actually use. If you do not need the user's name, do not ask for it. Requesting less builds trust and gives you less data to store and protect.

Step 3: Handle the credential

On success you get an ASAuthorization. Cast its credential to ASAuthorizationAppleIDCredential and pull out what you need. The important field is user, a stable identifier for this Apple ID and your app. Use it as the primary key for the account; it does not change across sign-ins or devices.

func handle(_ authorization: ASAuthorization) {
    guard let credential =
        authorization.credential as? ASAuthorizationAppleIDCredential
    else { return }
 
    let userID = credential.user                 // stable key, always present
    let email = credential.email                 // first sign-in only
    let fullName = credential.fullName           // first sign-in only
    let identityToken = credential.identityToken // JWT, send to your server
 
    // Persist userID, and send identityToken to your backend to verify.
}

Notice that identityToken is Data. It is a signed JWT that proves the sign-in happened. Do not treat the presence of a credential as proof by itself; the token is what your server checks.

The name and email gotcha

This is the single most common Sign in with Apple bug: Apple sends the user's fullName and email only on the very first authorization. On every sign-in after that, the credential comes back with a valid user and identityToken, but email and fullName are nil.

The consequence is simple. Capture the name and email the first time and persist them (on your server, keyed by user). If you wait until later to save them, they are gone, and the only way to get them back is for the user to remove your app from their Apple ID settings and sign in again.

Practical rule: on the first successful sign-in, write the name and email immediately. Treat later sign-ins as identity only, and read the profile from your own store.

Step 4: Add a nonce and verify on your server

If a real account or any server-side session is involved, you need two things: a nonce and server verification. Skipping them is the difference between a demo and something you can ship.

A nonce is a random value you generate per request. You send the SHA256 hash of it in the request, and keep the raw value. Apple embeds it in the identity token, and your server compares it back, which stops an attacker from replaying a token they captured.

import CryptoKit
 
func randomNonce(length: Int = 32) -> String {
    let charset = Array("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-._")
    var result = ""
    while result.count < length {
        for byte in (0..<16).map({ _ in UInt8.random(in: 0...255) }) {
            if result.count >= length { break }
            if Int(byte) < charset.count { result.append(charset[Int(byte)]) }
        }
    }
    return result
}
 
func sha256(_ input: String) -> String {
    SHA256.hash(data: Data(input.utf8))
        .map { String(format: "%02x", $0) }
        .joined()
}

Generate the nonce before showing the button, hash it into the request, and send the raw nonce plus the identity token to your backend.

let rawNonce = randomNonce()
 
SignInWithAppleButton(.signIn) { request in
    request.requestedScopes = [.fullName, .email]
    request.nonce = sha256(rawNonce)          // send the hash to Apple
} onCompletion: { result in
    // On success: POST identityToken + rawNonce to your server.
}

On the server, verify the identity token before you trust it: confirm Apple's signature against their public keys, check that the audience matches your bundle identifier, that the token has not expired, and that the nonce equals the raw value you generated. Most auth backends (Firebase Auth, Supabase, and others) do this for you when you hand them the token and the raw nonce, which is the usual reason to reach for one instead of writing the JWT checks yourself. For more on the server-side discipline, see iOS App Security Best Practices.

Step 5: Check credential state on launch

A user can revoke your app's access from their Apple ID settings at any time. Because your app may still be holding a local session, check the credential state on launch and sign the user out locally if it is no longer valid.

let provider = ASAuthorizationAppleIDProvider()
provider.getCredentialState(forUserID: savedUserID) { state, _ in
    switch state {
    case .authorized:
        break                 // still valid, carry on
    case .revoked, .notFound:
        // clear the local session and return to the sign-in screen
        break
    default:
        break
    }
}

This keeps your app honest. Without it, a user who revoked access still appears signed in until your token happens to expire.

FAQ

Is Sign in with Apple required for every app?

No. It is required by Guideline 4.8 only if you offer a third-party or social login for the primary account. If you have no accounts, or only your own email-and-password, it is optional but still a low-friction choice.

Why are email and fullName nil on the second sign-in?

Apple sends them only on the first authorization. Persist them the first time, keyed by the credential's user value, and read them from your own store afterward.

Do I really need the nonce?

If any server session or real account is involved, yes. The nonce is what lets your backend prove the identity token was issued for this specific request and not replayed. For a purely local, no-server app you can skip it, but most apps are not that.

Can I verify the identity token on the device instead of a server?

No. The token is the security boundary, and the device is the untrusted side. Verify it on a server you control, or hand it to an auth provider that does.

Does it work without Face ID or a passcode?

Yes. Sign in with Apple falls back to the Apple ID password when biometrics are unavailable, so it works on any device signed in to an Apple ID.

Sign in with Apple is deceptively small on the client and genuinely important on the server. Get the button and the once-only profile capture right, add a nonce, verify the token off-device, and re-check the credential on launch, and you have an authentication flow that is both frictionless for users and safe to build a paid app on top of.


Spaceport generates a production-ready SwiftUI Xcode project with authentication already wired up: Sign in with Apple and Sign in with Google, alongside RevenueCat subscriptions, onboarding, analytics, and App Store Connect pricing across 25 markets. The sign-in flow above, nonce and credential handling included, ships working in the generated project, so you start from a real account system instead of a login screen stub. From an indie iOS dev, for indie iOS devs.

Read more at spaceport.build

Community appsJoin Discord