mcpField
A
authentication

authentication

github.com/dpearson2699セキュリティ1.2k3.9k 回インストール13 週間前に更新
リポジトリ

authentication について

AuthenticationServices などを使った iOS のアカウント認証実装を支援するスキルです。Apple でサインイン、パスキー、OAuth、パスワードの自動入力、生体認証による再認証を扱い、認証情報の管理とサーバー側検証も説明します。

主な機能

  1. 01Apple でサインインと認証状態確認
  2. 02パスキーの登録と認証フロー
  3. 03ASWebAuthenticationSession による OAuth
  4. 04パスワード自動入力の認証情報処理
  5. 05LocalAuthentication による生体認証
  6. 06トークン検証と Keychain 保存の指針

活用シーン

  1. 01iOS のログインに Apple でサインインを追加
  2. 02サーバー発行チャレンジを使うパスキー対応
  3. 03サードパーティー OAuth プロバイダーでの認証
  4. 04アカウントの重要操作前の本人確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/dpearson2699/swift-ios-skills --skill authentication

インストール後、Claude Code で/authenticationを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

evals/
evals.json
references/
keychain-biometric.md
passkeys.md

詳細

Authentication

AuthenticationServices フレームワークを使用して iOS に認証フローを実装します。Sign in with Apple、パスキー、OAuth/サードパーティーの Web 認証、Password AutoFill、生体認証による再認証に対応します。

Contents

Sign in with Apple

これらの API を使用する前に、Xcode で「Sign in with Apple」ケイパビリティを追加してください。

UIKit: ASAuthorizationController Setup
import AuthenticationServices

final class LoginViewController: UIViewController {
    func startSignInWithApple() {
        let provider = ASAuthorizationAppleIDProvider()
        let request = provider.createRequest()
        request.requestedScopes = [.fullName, .email]

        let controller = ASAuthorizationController(authorizationRequests: [request])
        controller.delegate = self
        controller.presentationContextProvider = self
        controller.performRequests()
    }
}

extension LoginViewController: ASAuthorizationControllerPresentationContextProviding {
    func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
        view.window!
    }
}
Delegate: Handling Success and Failure
extension LoginViewController: ASAuthorizationControllerDelegate {
    func authorizationController(
        controller: ASAuthorizationController,
        didCompleteWithAuthorization authorization: ASAuthorization
    ) {
        guard let credential = authorization.credential
            as? ASAuthorizationAppleIDCredential else { return }

        let userID = credential.user  // Stable, unique, per-team identifier
        let email = credential.email  // nil after first authorization
        let fullName = credential.fullName  // nil after first authorization
        let identityToken = credential.identityToken  // JWT for server validation
        let authCode = credential.authorizationCode  // Short-lived code for server exchange

        // Save userID to Keychain for credential state checks
        // See references/keychain-biometric.md for Keychain patterns
        saveUserID(userID)

        // Send identityToken and authCode to your server
        authenticateWithServer(identityToken: identityToken, authCode: authCode)
    }

    func authorizationController(
        controller: ASAuthorizationController,
        didCompleteWithError error: any Error
    ) {
        switch (error as? ASAuthorizationError)?.code {
        case .canceled, .notInteractive:
            break
        case .failed:
            showError("Authorization failed")
        default:
            showError("Authorization failed: \(error.localizedDescription)")
        }
    }
}

Credential Handling

Credential data必須の処理
userこの安定したチームごとの識別子を、資格情報の状態確認に備えて保存してください。
email, fullNameこれらの省略可能な値は初回の認証時にのみ届くため、すぐにキャッシュしてください。
identityToken, authorizationCode検証または交換のためにサーバーへ送信してください。クライアント側での証明として信頼しないでください。

realUserStatus は不正防止のシグナルとしてのみ扱い、認証の証明として扱わないでください。

Credential State Checking

アプリを起動するたびに資格情報の状態を確認してください。ユーザーは「設定」>「Apple Account」>「サインインとセキュリティ」から、いつでもアクセスを取り消すことができます。

func checkCredentialState() {
    let provider = ASAuthorizationAppleIDProvider()
    guard let userID = loadSavedUserID() else {
        showLoginScreen()
        return
    }

    provider.getCredentialState(forUserID: userID) { state, _ in
        DispatchQueue.main.async {
            switch state {
            case .authorized:
                proceedToMainApp()
            case .revoked:
                // User revoked -- sign out and clear local data
                signOut()
                showLoginScreen()
            case .notFound:
                showLoginScreen()
            case .transferred:
                // App transferred to new team -- migrate user identifier
                migrateUser()
            @unknown default:
                showLoginScreen()
            }
        }
    }
}
Credential Revocation Notification
NotificationCenter.default.addObserver(
    forName: ASAuthorizationAppleIDProvider.credentialRevokedNotification,
    object: nil,
    queue: .main
) { _ in
    // Sign out immediately
    AuthManager.shared.signOut()
}

Token Validation

identityToken は JWT です。検証のためサーバーへ送信してください。クライアント側だけで信頼しないでください。

サーバー側では、https://appleid.apple.com/auth/keys(JWKS)にある Apple の公開鍵を使って JWT を検証してください。iss が https://appleid.apple.com であること、aud がバンドル ID と一致すること、exp が過ぎていないことを確認します。有効期間の短い認証コードをサーバー側で交換し、取得したアプリのセッショントークンを Keychain に保存してください。

Existing Account Setup Flows

ログイン画面を表示する前に、起動時に既存の Sign in with Apple 資格情報とパスワード資格情報をサイレントに確認してください。

func performExistingAccountSetupFlows() {
    let appleIDRequest = ASAuthorizationAppleIDProvider().createRequest()
    let passwordRequest = ASAuthorizationPasswordProvider().createRequest()

    let controller = ASAuthorizationController(
        authorizationRequests: [appleIDRequest, passwordRequest]
    )
    controller.delegate = self
    controller.presentationContextProvider = self
    controller.performRequests(
        options: .preferImmediatelyAvailableCredentials
    )
}

この処理は viewDidAppear またはアプリの起動時に呼び出してください。既存の資格情報が見つからない場合、デリゲートには .notInteractive エラーが届きます。このエラーはサイレントに処理し、通常のログイン UI を表示してください。

Passkeys

webcredentials: Associated Domain と AASA エントリーが設定されたリライングパーティードメインでのみ、パスキーを使用してください。登録とアサーションのどちらにも、新しいサーバーチャレンジ、ASAuthorizationPlatformPublicKeyCredentialProvider、アクティブなプレゼンテーションアンカーを持つ認証コントローラー、セッションを発行する前のサーバー側検証が必要です。

標準的な登録、アサーション、結果処理、AutoFill を利用したフロー、物理セキュリティキーのフローについては、references/passkeys.md を参照してください。

ASWebAuthenticationSession (OAuth)

OAuth やサードパーティー認証(Google、GitHub など)には、ASWebAuthenticationSession を使用してください。認証フローに WKWebView を使用しないでください。

import AuthenticationServices

final class OAuthController: NSObject, ASWebAuthenticationPresentationContextProviding {
    private weak var presentationAnchor: ASPresentationAnchor?

    init(presentationAnchor: ASPresentationAnchor) {
        self.presentationAnchor = presentationAnchor
    }

    func startOAuthFlow() {
        let authURL = URL(string:
            "https://provider.com/oauth/authorize?client_id=YOUR_ID&redirect_uri=myapp://callback&response_type=code"
        )!
        let session = ASWebAuthenticationSession(
            url: authURL, callback: .customScheme("myapp")
        ) { callbackURL, error in
            guard let callbackURL, error == nil,
                  let code = URLComponents(url: callbackURL, resolvingAgainstBaseURL: false)?
                      .queryItems?.first(where: { $0.name == "code" })?.value else { return }
            Task { await self.exchangeCodeForTokens(code) }
        }
        session.presentationContextProvider = self
        session.prefersEphemeralWebBrowserSession = true  // No shared cookies
        session.start()
    }

    func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
        guard let presentationAnchor else {
            fatalError("ASWebAuthenticationSession needs the active window")
        }
        return presentationAnchor
    }
}

SwiftUI では、@Environment(\.webAuthenticationSession) を使用し、.customScheme("myapp") または .https(host:path:) を指定して authenticate(using:callback:preferredBrowserSession:additionalHeaderFields:) を呼び出してください。プロバイダーのフローで共有ブラウザー Cookie を避ける必要がある場合に限り、.ephemeral を優先してください。

Password AutoFill Credentials

Existing Account Setup Flows にある単一のコントローラーで、Sign in with Apple と併せて ASAuthorizationPasswordProvider を提供してください。そのコントローラーのデリゲートで ASPasswordCredential を処理します。

AutoFill が機能するよう、テキストフィールドに textContentType を設定してください。

usernameField.textContentType = .username
passwordField.textContentType = .password

生体認証

アカウント設定を表示する前や機密性の高い操作を開始する前に、ローカルで再認証するには LocalAuthentication の LAContext を使用してください。返された Bool を、保存された秘密情報のロックを解除する証明として扱わないでください。秘密情報は Keychain のアクセス制御で保護してください。標準的な LAContext、フォールバック、SecAccessControl、.biometryCurrentSet のパターンについては、references/keychain-biometric.md を参照してください。

必須: Info.plist に NSFaceIDUsageDescription を追加してください。このキーがないと、Face ID 対応デバイスでクラッシュします。

セキュリティ境界

このスキルが担当するのは、ユーザー向けのアカウント認証、すなわち Sign in with Apple、パスキー、Password AutoFill、ASAuthorizationController、OAuth セッションの表示、認証情報の状態、ローカルでの生体認証による再認証です。セキュリティの詳細な作業は swift-security に振り分けてください。対象は、Keychain のアーキテクチャと移行、CryptoKit、Secure Enclave、証明書のピン留めと信頼性、Keychain の共有、ストレージの強化、OWASP MASVS/MASTG です。ここでは必要最小限のストレージに関する方針のみを扱います。トークンと秘密情報は Keychain に保存し、LAContext.evaluatePolicy だけで保護された秘密情報を解放しないでください。

SwiftUI の SignInWithAppleButton

ログイン画面が SwiftUI の場合は、SwiftUI ビューで SignInWithAppleButton を使用してください。.fullName と .email をリクエストし、成功した結果を ASAuthorizationAppleIDCredential にダウンキャストして、共通のトークン検証フローに渡してください。.signInWithAppleButtonStyle(...) でスタイルを設定してください。

よくある間違い

  • ローカルにセッションを保存していることを、Apple ID 認証情報が引き続き有効である証拠とみなすこと。起動時に認証情報の状態を確認し、失効通知に対応してください。
  • 既存アカウントの設定フローを試す前に、ログイン画面全体を表示すること。.notInteractive を「ローカルに認証情報がない」場合の通常の処理として扱ってください。
  • email や fullName を強制アンラップすること。初回の認証時にキャッシュし、以降は nil を処理してください。
  • プレゼンテーションコンテキストプロバイダーなしで ASAuthorizationController を作成すること。認証 UI には、アクティブなプレゼンテーションアンカーが必要です。
  • ID トークン、認証コード、アクセストークン、パスワード、パスキーのサーバー状態を UserDefaults、ファイル、Core Data に保存すること。秘密情報は Keychain に保存し、パスキーのリライングパーティー検証はサーバー側で行ってください。
  • リライングパーティーのドメインに webcredentials: Associated Domains を設定せずにパスキーのリクエストを追加すること、または無関係な Web サイトでアプリネイティブのパスキーを使おうとすること。
  • 認証作業の範囲を CryptoKit、Secure Enclave、証明書のピン留め、OWASP MASVS に広げること。これらは swift-security に振り分けてください。

レビューチェックリスト

  • Xcode プロジェクトに「Sign in with Apple」機能を追加済み
  • ASAuthorizationControllerPresentationContextProviding を実装済み
  • アプリの起動ごとに認証情報の状態を確認済み(getCredentialState(forUserID:completion:))
  • credentialRevokedNotification のオブザーバーを登録済み。サインアウトを処理済み
  • 初回の認証時に email と fullName をキャッシュ済み(以降も取得できるとは想定しない)
  • identityToken をサーバーに送信して検証し、クライアント側だけで信頼していない
  • トークンを UserDefaults やファイルではなく Keychain に保存済み
  • ログイン UI を表示する前に performExistingAccountSetupFlows を呼び出し済み
  • エラーケースを処理済み:.canceled、.failed、.notInteractive
  • 生体認証のために Info.plist に NSFaceIDUsageDescription を設定済み
  • OAuth に WKWebView ではなく ASWebAuthenticationSession を使用済み
  • 適切な場合に OAuth の prefersEphemeralWebBrowserSession を設定済み
  • AutoFill のためにユーザー名/パスワードのフィールドに textContentType を設定済み
  • パスキーのリライングパーティーに webcredentials: Associated Domains を設定済み
  • パスキー登録/アサーションのチャレンジをサーバーから取得し、サーバー側で検証済み
  • Keychain、CryptoKit、Secure Enclave、証明書のピン留め、MASVS に関する詳細な作業を swift-security に振り分け済み

参考資料

コメント