iOSアプリ開発において、Firebase AuthenticationでSign in with Appleを利用する機会があったので、その時の設定手順を備忘として記事にしておきます。
前提
Firebaseプロジェクトのセットアップを行なっておく
Firebaseコンソールで、FirebaseプロジェクトをiOSアプリ用にセットアップしておきます
セットアップ方法は公式の手順で十分わかりやすいと思うので、この記事では解説を省略します。
後ほど使うので、公式の手順のステップ4で、FirebaseAuthを追加しておくと良さそうです。
Apple Developer Programに参加しておく
Sign In With Appleを使うにはCapabilityをXcodeのプロジェクトに追加する必要がありますが、これはApple Developer Programに参加しているアカウントが必要になります。
以下のようにXcodeの設定のAccountsに、Apple Developer Programに参加したAppleアカウントが紐づけられていることを確認してから、後述する手順を行ってください。
Provisioning Profileをアプリに設定しておく
この記事の説明では、Provisioning Profileを設定している前提になります。
設定手順を説明している記事はたくさんあると思うので、ここでは省略します。
個人的には、以下記事がわかりやすかったです。
https://hirokuma.blog/?p=2715
手順
Capabilityの設定
アプリのメインターゲットの"Signing & Capabilities"から、"+ Capability"を押下します

表示されるポップアップで、"Sign in with Apple"を選択します

"Sign in with Apple"の表示が追加されていればOK

Firebase Authenticationの設定
作成しているFirebaseプロジェクトのコンソール画面から、"Authentication"を選択し、"始める"を押下します

有効にするのトグルボタンを、押下します。
また、次の手順で使用するので、赤枠のURLは控えておきます。
その後、保存ボタンを押下します。

以下のように、Appleプロバイダが有効になっていればOKです。

App IDにSign in with Appleの設定を追加
Apple Developer ProgramのIdentifiersの画面を開きます。
そこで、対象のアプリのApp IDを選択します。(今回の例では"homete for debugを選択します")

下にスクロールすると、Sign in with Appleの項目があるので、チェックボックをチェックし、Editボタンを押下します。

赤枠部分に、Firebase Authenticationの設定で控えたURLを入力し、Saveボタンを押下します。

最後にSaveボタンを押下しApp IDの内容を編集を確定します。

Provisioning Profileをマニュアル管理している場合は、CapabilityとApp IDの編集でプロジェクトのSigningにエラーが出ると思うので、必要に応じてProvisioning Profileの更新を行ってください。
認証処理を実装
Sign in with AppleでAppleアカウントの認証
SwiftUIでSign in with Apple用のボタンを実装します。(あくまで実装例です)
このボタンをタップした時にSign in with AppleでAppleアカウントの認証を行いますが、この時点ではFirebase側でのログインまでは行っていません。
onSignInのクロージャでSignInUpWithAppleButtonの呼び出し側でFirebaseの認証を行います。
import AuthenticationServices
import SwiftUI
import CryptoKit
struct SignInUpWithAppleButton: View {
@Environment(\.colorScheme) var colorScheme
@State var currentNonce: SignInWithAppleNonce?
let onSignIn: (Result<SignInWithAppleResult, any Error>) async -> Void
var body: some View {
SignInWithAppleButton {
handleRequest(request: $0)
} onCompletion: {
handleCompletion(result: $0)
}
.signInWithAppleButtonStyle(colorScheme == .light ? .black : .white)
.id(colorScheme)
}
}
private extension SignInUpWithAppleButton {
func handleRequest(request: ASAuthorizationAppleIDRequest) {
let nonce = generateNonce()
request.requestedScopes = [.fullName]
request.nonce = nonce.sha256
currentNonce = nonce
}
func handleCompletion(result: Result<ASAuthorization, any Error>) {
Task {
switch result {
case .success(let authorization):
guard let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential,
let currentNonce else {
preconditionFailure("No sent sign in request.")
}
guard let appleIDToken = appleIDCredential.identityToken,
let idTokenString = String(data: appleIDToken, encoding: .utf8) else {
await onSignIn(.failure(DomainError.failAuth))
return
}
let result = SignInWithAppleResult(tokenId: idTokenString, nonce: currentNonce.original)
await onSignIn(.success(result))
case .failure(let error):
print("failed sign in with apple: \(error)")
if let error = error as? ASAuthorizationError,
error.code != .canceled {
await onSignIn(.failure(DomainError.failAuth))
}
}
}
}
func generateNonce() -> SignInWithAppleNonce {
var randomBytes = [UInt8](repeating: 0, count: 32)
let errorCode = SecRandomCopyBytes(kSecRandomDefault, randomBytes.count, &randomBytes)
if errorCode != errSecSuccess {
fatalError(
"Unable to generate nonce. SecRandomCopyBytes failed with OSStatus \(errorCode)"
)
}
let charset: [Character] =
Array("0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._")
let nonce = randomBytes.map { byte in
// Pick a random character from the set, wrapping around if needed.
charset[Int(byte) % charset.count]
}
return .init(original: String(nonce), sha256: sha256(String(nonce)))
}
private func sha256(_ input: String) -> String {
let inputData = Data(input.utf8)
let hashedData = SHA256.hash(data: inputData)
let hashString = hashedData.compactMap { String(format: "%02x", $0) }.joined()
return hashString
}
}
struct SignInWithAppleNonce {
let original: String // 暗号化されていないNonce
let sha256: String // sha256で暗号化されたNonce
}
struct SignInWithAppleResult {
let tokenId: String // Sign in with Appleで取得したトークン
let nonce: String // Sign in with Appleで使用したNonce
}
Sign in with Apple用のUIはAuthenticationServicesフレームワークで提供されているので、ボタンのUIはそれほどいじれません。
上記例では、signInWithAppleButtonStyleでダークモードであれば白く、そうでなければ黒く表示します。
Appleアカウントの認証情報でFirebaseへログインの処理実装
上記で定義したSignInUpWithAppleButtonのクロージャで、Sign in with Appleによる認証情報を使用して、Firebase Authenticationでログイン処理を行います。
import FirebaseAuth
import SwiftUI
struct LoginView: View {
var body: some View {
SignInUpWithAppleButton {
await onSignInWithApple($0)
}
}
}
private extension LoginView {
func onSignInWithApple(_ result: Result<SignInWithAppleResult, any Error>) async {
switch result {
case .success(let success):
do {
let credential = OAuthProvider.credential(
providerID: .apple,
idToken: tokenId,
rawNonce: nonce
)
let authInfo = try await Auth.auth().signIn(with: credential)
<ログイン後の処理>
}
catch {
handleError(error)
}
case .failure(let failure):
handleError(failure)
}
}
func handleError(_ error: any Error) {
<エラー処理>
}
}
try await Auth.auth().signIn(with: credential)の部分でFirebase側へのログインは完了します。
実際に実機やシミュレーターでアプリを動かし、Appleアカウントでサインインしてみると、Firebaseのコンソール画面にログインした情報が表示されています。
これが出ていれば、最低限のログイン処理は完了です。(ログイン情報な監視や、ログアウト処理に関してはFirebase Authentication共通なので省略します)
Sign in with Appleはシミュレーターでも確認できますが、事前にシミュレーターの設定アプリからAppleアカウントにサインインしておく必要があります。
おわり
以上がFirebase AuthenticationでSign in with Appleを利用する方法でした。
設定周りは他のプロバイダに比べればちょっと大変だなという印象でした。(あんまり多くのプロバイダ試してないので、この辺の感想はそのうち手のひら返ししそうですが)


