📦 SwiftyLaunch Modules
🔐 AuthKit
Sign In with Apple Flow

Sign in with Apple Authentication Flow

AuthKit (SwiftyLaunch Module) - Sign in with Apple Authentication Flow

Apps generated with AuthKit include a complete authentication flow for signing in with Apple. This flow includes:

  • Signing In - The user can sign in with their email and password.
  • Signing Out - The user can sign out of their account.
  • Account Creation - Automatic account creation during the sign in process
  • Account Deletion - The user can delete their account

Signing In & Signing Out, Account Creation

If the user is not signed in and tries to access features that require authentication, they will be presented with a sign in sheet, that will prompt them to sign in. After pressing Sign in with Apple, a native Apple sign in sheet will be shown, where the user can sign in with their Apple Account.

Demo

Sign In and Out Demo

Walkthrough

In the demo, the user is not signed in and tries to open the account settings, which requires the user to be signed in (Lock SwiftUI Views to Signed-In Users). When presented with the SignInView, the user has the option to sign in with their email and password, reset their password, create a new account, or sign in with Apple.

In this case, the user signs in with Apple, and is then able to access the account settings. After pressing on the SignInWithAppleButton, the handleSignInWithAppleRequest function of DB is called, which handle the initial sign in request and generate a new nonce string. After the initial request is done, the onComplete function calls the handleSignInWithAppleCompletion function of DB, which will handle the sign in process with Firebase.

This will either create a new user or sign in an existing user, and the user will be redirected to the account settings. In contrast to the account creation process with email and password, the default account username automatically gets inferred by sign in with Apple, instead of being randomly generated.

If there was an error during the sign-in process, it is handled by showing the user a corresponding in-app notification. If the sign in was successful, the sign in sheet will be hidden.

The sign in state is automatically saved in the DB object and can be access through the authState variable.

FirebaseBackend.swift
// ...
public enum AuthState {
	case signedOut
	case signedInUnverified // means user is signed in but hasn't verified his email address yet
	case signedIn
}
// ...
@MainActor
public class DB: ObservableObject {
    // ...
    @Published public var authState: AuthState = .signedOut
    // ...
}
// ...

SignInView

SignInView.swift
// ...
public struct SignInView: View {
	@ObservedObject var db: DB
    // ...
	public var body: some View {
		NavigationStack(path: $signInFlowPath) {
            // ...
            VStack {
                // ... sign in hero section
 
                // ... sign in fields
 
                SignUpButtons(shouldShowEmailSignUpScreen: {
					signInFlowPath.append(SignInFlowPath.emailSignUp)
				})
            }
            // ...
		}
        // ...
	}
}

SignUpButtons

Contains the buttons related to social sign ins and account creation.

SignUpButtons.swift
struct SignUpButtons: View {
 
	@EnvironmentObject var db: DB
 
	var body: some View {
		VStack(alignment: .leading) {
			AppleAuthButton()
			// ... sign up with email button
			// ... by signing in you agree to tos, privacy policy text
		}
	}
}
SignUpButtons.swift
struct AppleAuthButton: View {
 
	@EnvironmentObject var db: DB
    // ...
 
	var body: some View {
		SignInWithAppleButton(
			.continue,
			onRequest: { request in
                // ...
				db.handleSignInWithAppleRequest(request)
			},
			onCompletion: { complete in
                // ...
                try await db.handleSignInWithAppleCompletion(complete) { /* ... handle successful sign in */ }
                // ...
			}
		)
		// ...
	}
}

Apple Sign in Handling in DB object

FirebaseBackend.swift
@MainActor
public class DB: ObservableObject {
 
    public func handleSignInWithAppleRequest(_ request: ASAuthorizationAppleIDRequest) {
        request.requestedScopes = [.fullName, .email]
		// ... nonce generation
    }
 
    public func handleSignInWithAppleCompletion(_ result: Result<ASAuthorization, Error>) async throws -> Bool {
        // ...
 
        if case .success(let authResults) = result {
            if let appleIDCredential = authResults.credential as? ASAuthorizationAppleIDCredential {
 
                // ...
				let credential = OAuthProvider.credential(/* ... */)
				do {
					let result = try await Auth.auth().signIn(with: credential)
					await updateDisplayName(for: result.user, with: appleIDCredential)
					// ...
				} catch {
                    // ... error handling
				}
			}
            // ...
        }
        // ...
    }
 
}

Account Deletion

Account deletion can be initiated in the the account settings (AccountSettingsView). The user can press the "Delete Account" button, which requires an additional confirmation to prevent accidental deletion. After that, a reauthentication sheet is shown, prompting the user to sign in with apple to confirm the identity. On successful reauthentication, the user is signed out and their account is deleted.

Demo

AccountSettingsView

AccountSettingsView.swift
public struct AccountSettingsView: View {
 
	// ...
	@EnvironmentObject var db: DB
	@State private var showAccountDeleteDialog = false
	@State private var reAuthSheetRef = false
	// ...
 
	public var body: some View {
		List {
			// ... account settings header (image, name, email)
 
			Section {
				// ... change name button
 
				// ... photos picker
 
				// ... change password button
 
				Button("Delete Account", role: .destructive) {
					// ...
					showAccountDeleteDialog = true
				}
				.confirmationDialog(
					"Are you sure you want to delete your Account?", isPresented: $showAccountDeleteDialog
				) {
					Button("Confirm Account Deletion", role: .destructive) {
 
						// show sheet to re-authenticate
						showReAuthSheet(db: db, reAuthSheetRef: $reAuthSheetRef) { result in
							switch result {
								case .success: // on success, hide re-auth sheet and show password sheet after delay
									// ...
									await Task.sleep(for: .seconds(0.5))
									await db.deleteUser()
									// ...
									dismissReAuthSheet(reAuthSheetRef: $reAuthSheetRef)
								case .canceled: // on cancel, hide re-auth sheet
									dismissReAuthSheet(reAuthSheetRef: $reAuthSheetRef)
								case .forgotPassword:
									// ... show password reset sheet
							}
						}
					}
				}
				// ... sign out button
			}
		}
		// ...
	}
	// ...
}

The deleteUser() function of the DB object

This function deletes the current user's account and logs the user out. This function also requires additional token revocation steps (opens in a new tab) in comparison to deleting users that signed in with email and password. It is an asynchronous function that throws an error if the deletion fails.

FirebaseBackend.swift
extension DB {
public func deleteUser() async throws {
		// ...
		if provider == .apple {
			// ...
			do {
                // ...
				try await Auth.auth().revokeToken(withAuthorizationCode: authCodeString)
				// ...
			} catch {
                // ... error handling
			}
		}
        // ...
		do {
            // ...
			try await currentUser.delete()
			// ...
			try signOut()
            // ...
		} catch {
			// ... error handling
		}
	}
}