mcpField
A
auth0-swift-major-migration

auth0-swift-major-migration

github.com/auth0開発55132 回インストール1 日前に更新
リポジトリ

auth0-swift-major-migration について

既存の Auth0.swift 統合を v2 から v3 に移行するスキルです。API の利用状況を調べ、関連する変更だけを適用し、既存の設計や規約に沿って作業します。

主な機能

  1. 01Swift ファイル内の Auth0 API 利用状況の監査
  2. 02対象リリースと依存関係の確認
  3. 03関連する破壊的変更のみ適用
  4. 04既存のコードスタイルと設計の維持
  5. 05ビルド確認と移行時の安全手順

活用シーン

  1. 01iOS プロジェクトの Auth0.swift v2 から v3 への移行
  2. 02実際に使う API だけの更新
  3. 03クリーンで復元可能な移行の準備

インストール

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

$npx skills add https://github.com/auth0/agent-skills --skill auth0-swift-major-migration

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

同梱ファイル

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

references/
process.md
security.md
tests/
benchmark-config.json
evals.json
graders.json
graders.ts
package.json
prompt.md
run-evals.mjs

詳細

Auth0.swift v3 への移行

既存の Auth0.swift v2 統合を v3 に移行します。コードの変更はすべて、プロジェクトが影響を受ける API を実際に呼び出していることを確認する検索を経てから行います。たとえば、プロジェクトで CredentialsManager を使用していない場合、CredentialsManager に関するコードには一切手を加えません。変更は、プロジェクト既存のアーキテクチャと Apple プラットフォームの規約に従います。

使用しないケース

  • 新しい Auth0 統合(Auth0.swift がまだ存在しない場合):auth0-swift を使用してください
  • マイナー/パッチアップデート(例:2.17 → 2.18):pod update Auth0 を実行するか SPM を更新してください。移行は不要です
  • Android アプリ:auth0-android を使用してください
  • React Native/Expo:auth0-react-native または auth0-expo を使用してください

前提条件

  • 既存の Auth0.swift v2 統合
  • Xcode がインストールされており、プロジェクトが現在のバージョンで正常にビルドできること
  • プロジェクトが git バージョン管理下にあり、作業ツリーがクリーンであること

移行ワークフロー

エージェントへの指示: すべての手順を順番に実行してください。目標は、最小限かつ適切な変更セットでビルドを成功させることです。コード変更を伴う各手順は、ステップ 4 のファイル読み取り監査を経てから実行してください。プロジェクトのソースファイルで API が見つからなかった場合は、その領域の手順全体をスキップします。プロジェクトがすでに呼び出しているもの以外のコードを追加してはいけません。


Step 1 — 実行前の確認と安全のためのバックアップ
# 1a. Verify clean working tree — stop if there are uncommitted changes
git status --porcelain

出力が空でない場合は、ユーザーに確認します:

「コミットされていない変更があります。続行する前に stash しますか(git stash)、それとも先にコミットしますか?」

# 1b. Create a safety branch the user can reset to at any time
git checkout -b auth0-v3-migration-backup
git checkout -
# 1c. Pick an available simulator, then confirm the project builds before touching anything
SIM=$(xcrun simctl list devices available -j \
  | python3 -c "import sys,json; d=json.load(sys.stdin); \
    phones=[dev for devs in d['devices'].values() for dev in devs \
            if 'iPhone' in dev.get('name','') and dev.get('isAvailable')]; \
    print(phones[0]['name'] if phones else 'iPhone 16')")
xcodebuild build \
  -scheme <SCHEME> \
  -destination "platform=iOS Simulator,name=${SIM}" \
  2>&1 | tail -5

ビルドに失敗した場合は、停止してください。まず既存の問題を修正するようユーザーに依頼します。


Step 2 — 現在のバージョンと移行先バージョンを確認

プロジェクトの依存関係ファイルから、現在の Auth0.swift のバージョンを確認します:

# Check Package.resolved first (most reliable)
find . -name "Package.resolved" | xargs grep -A3 '"auth0/Auth0.swift"\|Auth0.swift"' 2>/dev/null | grep '"version"'

# Fallback: Podfile.lock
grep "^  - Auth0 " Podfile.lock 2>/dev/null

# Fallback: Cartfile.resolved
grep "auth0/Auth0.swift" Cartfile.resolved 2>/dev/null

# Fallback: Package.swift
grep -A2 'auth0/Auth0.swift' Package.swift 2>/dev/null

移行先バージョンを決定します。 次の 2 つの方法があります:

方法 A — ユーザーが移行先バージョンの引数($ARGUMENTS)を渡した場合:

使用する前に、公開済みリリースと照合して検証します。次の3 つすべての条件を満たす必要があります:

# List all published Auth0.swift v3 release tags
curl -s https://api.github.com/repos/auth0/Auth0.swift/releases | python3 -c "
import sys, json
releases = json.load(sys.stdin)
v3 = [r for r in releases if r['tag_name'].startswith('3') and not r['draft']]
for r in v3:
    print(r['tag_name'])
"
  1. 存在する — 指定されたタグが上記の公開済みリリース一覧に含まれている。
  2. 正しいメジャーバージョン — タグがv3のメジャー系列に含まれている(3 で始まる)。2.x やその他のメジャーバージョンは無効なため、拒否します。
  3. ダウングレードではない — タグがプロジェクトで検出されたバージョンより新しい。

いずれかのチェックに失敗した場合は、処理を停止してユーザーに確認します。 黙って別のバージョンに切り替えてはいけません。例:

  • 「3.9.9 は Auth0.swift の公開済みリリースではありません。公開済みの v3 リリースは 3.0.0-beta.2、…です。有効な v3 タグを指定するか、引数を省略して最新の v3 リリースを自動で解決してください。」
  • 「2.10.0 は v3 ではなく v2 のリリースです。このスキルは v3 への移行を行います。v3 タグ(例:3.0.0-beta.2)を指定するか、引数を省略してください。」
  • 「3.0.0-beta.1 は、プロジェクトにすでにある 3.0.0-beta.2 より古いため、ダウングレードになります。より新しい v3 タグを指定するか、引数を省略してください。」

方法 B — 引数がない場合:最新の v3 リリース(プレリリースを含む)を自動で解決します:

# Newest v3.x release tag (stable or pre-release), most recent first
curl -s https://api.github.com/repos/auth0/Auth0.swift/releases | python3 -c "
import sys, json
releases = json.load(sys.stdin)
v3 = [r for r in releases if r['tag_name'].startswith('3') and not r['draft']]
if v3:
    print(v3[0]['tag_name'])
else:
    print('')
"

結果を <TARGET_TAG> として記録し、以降のすべての手順で使用します。

<TARGET_TAG> がプレリリースの場合(-beta、-rc などを含む場合)、続行する前にユーザーに知らせます: 「最新の v3 リリースは <TARGET_TAG>(プレリリース)です。このバージョンに移行します。引数としてタグを渡すと、別のタグを指定できます:auth0-swift-major-migration <tag>。」

v3 リリースが存在しない場合(リゾルバーが空を返した場合)は、停止して、移行先となる公開済みの v3 リリースがないことをユーザーに伝えます。


Step 3 — v3 SDK のソースを取得して読む

移行先タグの実際の Swift ソースを取得します。ここに記載されているシグネチャは、ステップ 6 で行うすべての変更の正式な参照元です。

TAG=<TARGET_TAG>   # the version the developer chose in Step 2, e.g. 3.0.0-beta.2

# List all public Swift files in the SDK
curl -s "https://api.github.com/repos/auth0/Auth0.swift/git/trees/${TAG}?recursive=1" \
  | python3 -c "
import sys, json
for item in json.load(sys.stdin).get('tree', []):
    if item['path'].startswith('Auth0/') and item['path'].endswith('.swift'):
        print(item['path'])
"

# Fetch core public API files
for FILE in WebAuth.swift CredentialsManager.swift Authentication.swift \
            Credentials.swift UserProfile.swift Requestable.swift \
            CredentialsStorage.swift CredentialsManagerError.swift WebAuthError.swift; do
    URL="https://raw.githubusercontent.com/auth0/Auth0.swift/${TAG}/Auth0/${FILE}"
    CONTENT=$(curl -sf "$URL")
    [ -n "$CONTENT" ] && echo "=== $FILE ===" && echo "$CONTENT"
done

# MFA files live in a subdirectory
for FILE in MFA/MFAClient.swift MFA/MFAErrors.swift; do
    URL="https://raw.githubusercontent.com/auth0/Auth0.swift/${TAG}/Auth0/${FILE}"
    CONTENT=$(curl -sf "$URL")
    [ -n "$CONTENT" ] && echo "=== $FILE ===" && echo "$CONTENT"
done

取得したソースを読み、次の点を確認します:

  • 変更されたすべての公開メソッドのシグネチャ(戻り値の型、パラメーター、追加された throws)
  • 名前が変更または削除された型
  • 変更されたプロトコル要件
  • 変更されたデフォルトパラメーター値

これが正確な基準となります。ステップ 6 の変更はすべて、これらのファイルにある実際のシグネチャと一致させる必要があります。


ステップ 4 — プロジェクトで使用されている Auth0 API を監査する

Auth0 をインポートしている Swift ファイルをすべて見つけます。これらが移行の対象範囲です。

grep -rl "import Auth0" --include="*.swift" .

一覧にあるファイルをすべて読みます。 特定の API パターンを grep しないでください。ソース全体を読み、Auth0、webAuth、authentication、credentialsManager、および Auth0 の型がどのように使われているかを正確に確認します。これには、domain/clientId パラメーターを使った呼び出し、連鎖するビルダー呼び出し、カスタム適合も含まれます。

各ファイルで、次の項目を確認します。

確認する内容重要な理由
webAuth()、webAuth(domain:)、webAuth(domain:clientId:) の呼び出し§6.1 — clearSession の名称変更、§6.14 — デフォルトスコープ
.clearSession( の呼び出し§6.1 — logout に名称変更
WebAuthError に対する明示的なケース名を使った switch/catch§6.2 — 削除されるケースと追加されるケース
Auth0 のコールバックをラップする DispatchQueue.main.async または MainActor.run§6.3 — v3 では削除可能
保存された Request<…> 型の型注釈(連鎖する .start(…) だけの場合を除く)§6.4 — 型が Requestable に変更
Authentication、MFAClient、Requestable に適合するテストモック§6.4 — 戻り値の型と @MainActor の更新
credentialsManager.store( の呼び出し§6.5 — Bool から throws へ変更
credentialsManager.clear() または credentialsManager.clear(forAudience: の呼び出し§6.6 — Bool から throws へ変更(両方のオーバーロード)
credentialsManager.user へのアクセス(メソッドではなくプロパティ)§6.7 — userProfile() メソッドに置き換え
credentialsManager.revoke( の呼び出し§6.8 — 新しいエラーパス
UserInfo を使う型注釈または宣言§6.9 — UserProfile に名称変更
Credentials のようなオブジェクトの .expiresIn へのアクセス§6.10 — expiresAt に名称変更
CredentialsStorage に適合する型§6.11 — メソッドシグネチャの変更
Auth0.users( または Auth0.users(token: の呼び出し§6.12 — Management クライアントの削除
login(withOTP:、login(withOOBCode:、login(withRecoveryCode:、multifactorChallenge(§6.13 — MFA メソッドの削除
.scope( を連鎖させていない webAuth() の呼び出し§6.14 — デフォルトスコープの変更
明示的な minTTL: パラメーターを指定しない credentialsManager.credentials( の呼び出し§6.15 — デフォルトの minTTL が 0 秒から 60 秒に変更

チェックリストを作成します。「このプロジェクトで使用しているもの:[一覧]」および「このプロジェクトで使用していないもの:[一覧]」。 「使用しているもの」の一覧にある §6.x セクションだけを進めます。残りはすべて完全にスキップしてください。


ステップ 5 — SDK の依存関係を更新する

該当するパッケージマネージャーのみを適用します。

ステップ 2 で選択した <TARGET_TAG> を使用します。安定版(サフィックスのない 3.x.y)には範囲指定を使用します。プレリリース版(3.x.y-beta.z)にはタグを正確に指定します。パッケージマネージャーでは、プレリリース版が ~> / from: のルールで範囲外として扱われるためです。

Swift Package Manager(Package.swift):

// Stable v3 — range specifier picks up all 3.x.y patches
.package(url: "https://github.com/auth0/Auth0.swift", from: "3.0.0")

// Pre-release / specific beta — exact tag required
.package(url: "https://github.com/auth0/Auth0.swift", exact: "3.0.0-beta.2")

次に、依存関係を解決します。

swift package resolve

CocoaPods(Podfile):

# Stable v3
pod 'Auth0', '~> 3.0'

# Pre-release / specific beta — pin the exact version
pod 'Auth0', '3.0.0-beta.2'

次に:

pod update Auth0

Carthage(Cartfile):

# Stable v3
github "auth0/Auth0.swift" ~> 3.0

# Pre-release / specific beta — pin the exact tag
github "auth0/Auth0.swift" "3.0.0-beta.2"

次に:

carthage update Auth0.swift --use-xcframeworks

Xcode 管理の SPM(ルートに Package.swift がない場合):

  • 安定版: 「File → Packages → Update to Latest Package Versions」を選択し、バージョンルールが 3.0.0 からの Up to Next Major であることを確認します。
  • プレリリース版/特定のベータ版: 依存関係で正確なバージョンがすでに指定されていない限り、「File → Packages → Update to Latest Package Versions」ではベータ版を解決できません。バージョンルールを Exact Version に変更し、3.0.0-beta.2(または選択したタグ)を入力するようユーザーに伝えます。

まだビルドしないでください。 既知のコード変更をすべて先に適用します。


ステップ 6 — 破壊的変更を適用する

エージェントへの指示: ステップ 4 のファイル読み取り監査で該当した §6.x セクションのみを進めます。プロジェクトで使われていない API のセクションはすべてスキップし、該当するファイルには手を加えないでください。

各変更を示されているとおりに適用します。周辺のコードを変更したり、変数名を変更したり、書式を整えたり、移行対象ではないコードをモダン化したりしないでください。プロジェクトで既に使われているスタイルに合わせます。完了ハンドラーは完了ハンドラーのまま、async/await は async/await のまま、Combine は Combine のままにします。

6.1 — WebAuth.clearSession() → WebAuth.logout()

該当する場合: 手順 4 で、プロジェクトのソースファイルに .clearSession( の呼び出しが見つかった場合。

clearSession(federated:) メソッドの名前が logout(federated:) に変更されました。パラメーターとそのデフォルト値に変更はありません。

完了ハンドラー:

// v2
Auth0.webAuth().clearSession { result in
    switch result {
    case .success: handleLogoutSuccess()
    case .failure(let error): handleError(error)
    }
}

// v3
Auth0.webAuth().logout { result in
    switch result {
    case .success: handleLogoutSuccess()
    case .failure(let error): handleError(error)
    }
}

async/await:

// v2
try await Auth0.webAuth().clearSession()

// v3
try await Auth0.webAuth().logout()

Combine:

// v2
Auth0.webAuth().clearSession()
    .sink(receiveCompletion: { ... }, receiveValue: { ... })
    .store(in: &cancellables)

// v3
Auth0.webAuth().logout()
    .sink(receiveCompletion: { ... }, receiveValue: { ... })
    .store(in: &cancellables)

federated: true を指定する場合: パラメーター名は同じです。メソッド名だけを変更してください。

// v2
try await Auth0.webAuth().clearSession(federated: true)

// v3
try await Auth0.webAuth().logout(federated: true)

6.2 — WebAuthError — 網羅的な switch 文で削除されたケースと新しいケース

該当する場合: 手順 4 で、プロジェクトのソースファイルに WebAuthError を対象とし、明示的なケース名を指定した switch または catch が見つかった場合。

v3 では、2 つの WebAuthError ケースが削除されました。プロジェクトに WebAuthError を対象とする網羅的な switch がある場合(またはこれらのケースを明示的に照合している場合)、ビルドに失敗します。

これまで表面化していなかったエラーを示す新しいケースが 3 つ追加されました。

削除されたケース(照合するとコンパイルできなくなります):

v2 のケースv3 での動作
.invalidInvitationURL削除されました。現在は .unknown として表面化します
.pkceNotAllowed削除されました。現在は .unknown として表面化します

新しいケース(catch/switch ブロックに現れるようになりました):

v3 のケース発生する状況
.authenticationFailedサーバー側の失敗:パスワードの誤り、MFA が必要、アカウントがロックされているなど
.codeExchangeFailedトークン交換の失敗:ネットワークの問題、無効な grant、バックエンドエラー
.credentialsManagerErrorログイン/ログアウト後に CredentialsManager で認証情報を保存または消去できなかった場合。.cause で元のエラーにアクセスできます

移行 — switch 文から削除されたケースを取り除きます:

// v2 — exhaustive switch including cases that no longer exist
Auth0.webAuth().start { result in
    switch result {
    case .success(let credentials):
        handle(credentials)
    case .failure(let error):
        switch error {
        case .userCancelled:
            break  // user dismissed — no action needed
        case .pkceNotAllowed:
            // ❌ compile error in v3 — remove this case
            showConfigError("PKCE not allowed")
        default:
            showError(error)
        }
    }
}

// v3 — remove the deleted cases; handle the new ones where appropriate
Auth0.webAuth().start { result in
    switch result {
    case .success(let credentials):
        handle(credentials)
    case .failure(let error):
        switch error {
        case .userCancelled:
            break  // user dismissed — no action needed
        case .authenticationFailed:
            // server rejected the login — show an appropriate message
            showError("Login failed. Please check your credentials.")
        case .codeExchangeFailed:
            // token exchange failed — network or server issue
            showError("Something went wrong. Please try again.")
        case .credentialsManagerError:
            // login succeeded but credentials could not be stored
            // the user is authenticated in memory but will need to log in again next launch
            // access the underlying error via error.cause (WebAuthError.cause: Error?)
            reportToMonitoring(error.cause)
            showError("Could not save your session.")
        default:
            showError(error)
        }
    }
}

プロジェクトで async/await を使用し、特定のケースを捕捉している場合:

// v2
do {
    let credentials = try await Auth0.webAuth().start()
    handle(credentials)
} catch WebAuthError.userCancelled {
    break
} catch WebAuthError.pkceNotAllowed {
    // ❌ compile error in v3 — remove this catch
    showConfigError()
} catch {
    showError(error)
}

// v3 — remove deleted cases; add new ones if the project should handle them
do {
    let credentials = try await Auth0.webAuth().start()
    handle(credentials)
} catch WebAuthError.userCancelled {
    break
} catch WebAuthError.authenticationFailed {
    showError("Login failed. Please check your credentials.")
} catch WebAuthError.codeExchangeFailed {
    showError("Something went wrong. Please try again.")
} catch {
    showError(error)
}

新しいケース .authenticationFailed と .codeExchangeFailed を明示的に処理する必要はありません。default: 分岐で捕捉できます。これらの失敗に対して異なる UI やテレメトリーを表示する場合にのみ、ケースを明示的に追加してください。


6.3 — WebAuth および CredentialsManager のコールバックを囲む冗長なメインスレッドへのディスパッチを削除する

該当する場合: 手順 4 で、Auth0 コールバックの本体を囲む DispatchQueue.main.async または MainActor.run が見つかった場合。

v3 では、すべての完了ハンドラーのコールバック、Combine パブリッシャー、および async/await メソッドがメインスレッド上で結果を返します(@MainActor です)。コールバックの本体を DispatchQueue.main.async { } または await MainActor.run { } で囲む必要はなくなったため、削除できます。

完了ハンドラーのコールバック — ディスパッチのラッパーを削除します:

// v2 — dispatch to main manually
credentialsManager.credentials { result in
    DispatchQueue.main.async {
        switch result {
        case .success(let credentials):
            self.accessToken = credentials.accessToken
            self.isAuthenticated = true
        case .failure(let error):
            self.authError = error
        }
    }
}

// v3 — callback already arrives on main thread
credentialsManager.credentials { result in
    switch result {
    case .success(let credentials):
        self.accessToken = credentials.accessToken
        self.isAuthenticated = true
    case .failure(let error):
        self.authError = error
    }
}

async/await — MainActor.run のラッパーを削除します:

// v2
let credentials = try await Auth0.webAuth().start()
await MainActor.run {
    self.isAuthenticated = true
}

// v3 — start() is @MainActor; already on main thread after the await
let credentials = try await Auth0.webAuth().start()
self.isAuthenticated = true

Auth0 コールバックの本体を保護することだけを目的とするディスパッチのラッパーのみを削除してください。DispatchQueue.main.async ブロックが無関係な UI の処理もディスパッチしている場合は、Auth0 コールバックに関係する部分だけを削除してください。


6.4 — Authentication / MFAClient のメソッドが Request ではなく Requestable を返す — アプリコードとテスト用モック

該当する場合: ステップ 4 で、(a)アプリコードに保存された Request<…> 型注釈、または(b)Authentication、MFAClient、Requestable に準拠する型を持つテスト/モックファイルが見つかった場合です。

v3 では、すべての Authentication および MFAClient のメソッドが、具体型である Request 構造体ではなく、プロトコル型を返します。

  • 認証情報を返すメソッド(login、codeExchange、renew、ssoExchange など)は、any TokenRequestable<T, E> を返すようになりました
  • その他すべてのメソッド(signup、resetPassword、userInfo、jwks など)は、any Requestable<T, E> を返すようになりました

アプリコードへの影響: .start(_:) に直接チェーンする呼び出し箇所は圧倒的多数を占めますが、変更なしでコンパイルできます。問題が発生するアプリコードは、保存された Request<> 型注釈のみです。

// v2 — storing the request in a typed variable
let request: Request<Credentials, AuthenticationError> = Auth0
    .authentication()
    .login(usernameOrEmail: email, password: password,
           realmOrConnection: "Username-Password-Authentication",
           audience: audience, scope: scope)
request.start { result in ... }

// v3 — update the type annotation to the protocol type
// For credential-returning methods:
let request: any TokenRequestable<Credentials, AuthenticationError> = Auth0
    .authentication()
    .login(usernameOrEmail: email, password: password,
           realmOrConnection: "Username-Password-Authentication",
           audience: audience, scope: scope)
request.start { result in ... }

// For non-credential methods (signup, resetPassword, userInfo, jwks):
let request: any Requestable<DatabaseUser, AuthenticationError> = Auth0
    .authentication()
    .signup(email: email, password: password, connection: connection)
request.start { result in ... }

// Most common pattern — chaining directly, no annotation needed, no change required:
Auth0.authentication()
    .login(usernameOrEmail: email, password: password,
           realmOrConnection: "Username-Password-Authentication",
           audience: audience, scope: scope)
    .start { result in ... }  // ✅ unchanged

any TokenRequestable を返すようになった認証情報返却メソッド(全一覧):

  • login(email:code:audience:scope:)
  • login(phoneNumber:code:audience:scope:)
  • login(usernameOrEmail:password:realmOrConnection:audience:scope:)
  • loginDefaultDirectory(withUsername:password:audience:scope:)
  • login(appleAuthorizationCode:fullName:profile:audience:scope:)
  • login(facebookSessionAccessToken:profile:audience:scope:)
  • login(passkey:challenge:connection:audience:scope:organization:) — 2 つのオーバーロード(パスキーによるサインイン+サインアップ)
  • codeExchange(withCode:codeVerifier:redirectURI:)
  • renew(withRefreshToken:audience:scope:)
  • ssoExchange(withRefreshToken:)
  • customTokenExchange(subjectToken:subjectTokenType:audience:scope:organization:parameters:)
  • MFAClient.verify(otp:mfaToken:)、verify(oobCode:bindingCode:mfaToken:)、verify(recoveryCode:mfaToken:)

テストターゲットへの影響 — カスタム Authentication モック:

プロジェクトのテストターゲットに、Authentication または MFAClient プロトコルに準拠するモックまたはスタブがある場合、次の 2 点を変更する必要があります。

  1. 戻り値の型: Request<T, E> を、認証情報メソッドでは any TokenRequestable<T, E> に、その他のメソッドでは any Requestable<T, E> に変更します
  2. start(_:) コールバック: 更新された Requestable プロトコルの要件に合わせて @MainActor を追加します
// v2 — mock Authentication conformance in tests
class MockAuthentication: Authentication {
    var credentialsResult: Result<Credentials, AuthenticationError> = .failure(.init(info: [:], statusCode: 0))

    func login(usernameOrEmail username: String,
               password: String,
               realmOrConnection realm: String,
               audience: String?,
               scope: String) -> Request<Credentials, AuthenticationError> {
        // ❌ compile error in v3 — Request is no longer the return type
        return Request(session: URLSession.shared, ...) // v2 internal — no longer works
    }
}

// v2 — mock Requestable used as stub
struct MockRequest<T, E: Auth0Error>: Requestable {
    let result: Result<T, E>
    func start(_ callback: @escaping (Result<T, E>) -> Void) {
        // ❌ @MainActor missing — does not conform to v3 Requestable
        callback(result)
    }
}

// v3 — updated mock
struct MockRequest<T, E: Auth0Error>: Requestable {
    let result: Result<T, E>
    // ✅ Add @MainActor to match the protocol; dispatch via Task to satisfy @MainActor isolation
    func start(_ callback: @escaping @MainActor (Result<T, E>) -> Void) {
        Task { @MainActor in callback(result) }
    }
}

// v3 — updated Authentication mock returning the correct protocol type
class MockAuthentication: Authentication {
    var credentialsResult: Result<Credentials, AuthenticationError> = .failure(.init(info: [:], statusCode: 0))

    func login(usernameOrEmail username: String,
               password: String,
               realmOrConnection realm: String,
               audience: String?,
               scope: String) -> any TokenRequestable<Credentials, AuthenticationError> {
        // ✅ Return MockTokenRequest, not Request
        return MockTokenRequest(result: credentialsResult)
    }
}

// v3 — TokenRequestable mock (for credential-returning methods)
struct MockTokenRequest<T, E: Auth0Error>: TokenRequestable {
    typealias ResultType = T
    typealias ErrorType = E

    let result: Result<T, E>

    func start(_ callback: @escaping @MainActor (Result<T, E>) -> Void) {
        Task { @MainActor in callback(result) }
    }

    // TokenRequestable adds these claim-validation builder methods — return self
    func validateClaims() -> any TokenRequestable<T, E> { self }
    func withLeeway(_ leeway: Int) -> any TokenRequestable<T, E> { self }
    func withIssuer(_ issuer: String) -> any TokenRequestable<T, E> { self }
    func withNonce(_ nonce: String?) -> any TokenRequestable<T, E> { self }
    func withMaxAge(_ maxAge: Int?) -> any TokenRequestable<T, E> { self }
    func withOrganization(_ organization: String?) -> any TokenRequestable<T, E> { self }
}

上記の MockTokenRequest スタブは、すべての TokenRequestable ビルダーメソッドをスタブ化し、self を返します。ほとんどのテストでは validateClaims() と with* 修飾子は呼び出されないため、self を返すのが適切です。クレームの検証動作を確認する特定のテストがある場合は、これらのメソッドを適切に実装してください。


6.5 — CredentialsManager.store(credentials:) — Bool 戻り値から throws へ

該当する場合: ステップ 4 で、プロジェクトのソースファイル内に credentialsManager.store(credentials: の呼び出しが見つかった場合です。

以前の store(credentials:) は Bool を返していました。v3 では、失敗時にスローし、成功時には Void を返します。

戻り値をチェックしていた場合:

// v2
if credentialsManager.store(credentials: credentials) {
    print("Stored successfully")
} else {
    print("Store failed")
}

// v3 — use do-catch; map the error into the project's existing error handler
do {
    try credentialsManager.store(credentials: credentials)
} catch {
    // replace with whatever logging/error handling the project already uses
    handleError(error)
}

戻り値を破棄していた場合:

// v2 — silently discarded
_ = credentialsManager.store(credentials: credentials)

// v3 — try? discards the error the same way; use if the project didn't handle failures before
try? credentialsManager.store(credentials: credentials)

プロジェクトにエラー処理を組み込めるパターンがある場合は、try? より do-catch を優先してください。意図的にエラーを無視する動作を維持する場合に限り、try? を使用してください。


6.6 — CredentialsManager.clear() と clear(forAudience:scope:) — Bool 戻り値から throws へ

該当する場合: ステップ 4 で、プロジェクトのソースファイル内に credentialsManager.clear() または credentialsManager.clear(forAudience: の呼び出しが見つかった場合です。

以前の 2 つのオーバーロードはどちらも Bool を返していました。v3 では、どちらもスローします。

  • clear() throws — 保存されているメインの認証情報を消去します
  • clear(forAudience:scope:) throws — 特定のオーディエンス向けの API 認証情報を消去します
// v2
_ = credentialsManager.clear()
_ = credentialsManager.clear(forAudience: "https://api.example.com")

// v3
try? credentialsManager.clear()
try? credentialsManager.clear(forAudience: "https://api.example.com")
// or, if the project handles errors:
do {
    try credentialsManager.clear()
} catch {
    handleError(error)
}

6.7 — CredentialsManager.user プロパティから、エラーをスローする userProfile() メソッドへ

該当する場合: ステップ 4 で、プロジェクトのソースファイル内に、メソッド呼び出しではなくプロパティとしての credentialsManager.user へのアクセスが見つかった場合です。

user: UserInfo? の計算プロパティは、userProfile() throws -> UserProfile? に置き換えられました(型名の変更については §6.9 も参照してください)。

// v2 — property access, returns UserInfo?
func currentUser() -> UserInfo? {
    return credentialsManager.user
}

// v3 — method call that throws, returns UserProfile?
func currentUser() -> UserProfile? {
    return try? credentialsManager.userProfile()
}

// v3 — if the project needs to surface errors:
func loadUser() throws {
    let profile = try credentialsManager.userProfile()
    self.userProfile = profile
}

6.8 — CredentialsManager の非同期メソッド — ストレージから例外がスローされた場合の新しいエラーパス

該当する場合: 手順 4 で、プロジェクトのソースファイルに credentialsManager.revoke( の呼び出しが見つかった場合。

CredentialsManager のストレージメソッドが例外をスローするようになったため、これまで暗黙に無視されていた複数の非同期メソッドで、新たな失敗パスが発生するようになります。最も重要なのは revoke() です。プロジェクトが実際に記述しているエラー処理コードのみを更新してください。すでに default: 分岐を使用している呼び出し箇所は変更不要です。

revoke() で新たに発生する可能性のあるエラー:

新しいエラー発生する条件対応方法
.noCredentialsgetEntry が例外をスローした場合 — ストレージに認証情報がなく、取り消すものがありませんすでにログアウト済みとして扱い、ログイン画面に移動します
.revokeFailedリフレッシュトークンを取り消すネットワーク呼び出しが失敗した場合サーバー上でトークンがまだ有効な可能性があるため、エラーを表示します
.clearFailed取り消しには成功したものの、Keychain の削除に失敗した場合サーバー側でトークンが無効になっているため、ログアウト済みとして扱います
// v2 — only .revokeFailed was possible; missing credentials returned .success silently
credentialsManager.revoke { result in
    switch result {
    case .success:
        navigateToLogin()
    case .failure(let error):
        showError(error)  // only .revokeFailed reached here
    }
}

// v3 — new cases surface; update the switch if the project checks specific cases
credentialsManager.revoke { result in
    switch result {
    case .success:
        navigateToLogin()
    case .failure(let error):
        switch error {
        case .noCredentials:
            // nothing was stored — already effectively logged out
            navigateToLogin()
        case .revokeFailed:
            // server revocation failed — refresh token may still be active
            showError("Could not revoke your session. Please try again.")
        case .clearFailed:
            // token revoked server-side but Keychain delete failed
            // treat as logged out — token is no longer valid
            navigateToLogin()
        default:
            showError(error)
        }
    }
}

credentials()、renew()、apiCredentials()、ssoCredentials() で新たに発生する可能性のあるエラー:

新しいエラー発生する条件
.noCredentialsgetEntry が例外をスローした場合(例:Keychain に項目が見つからない)— 以前は try? によって無視されていました
.renewFailedリフレッシュトークンの更新リクエストが失敗した場合 — ネットワークエラー、リフレッシュトークンが無効または期限切れ
.storeFailed更新した認証情報を保存する際に Keychain への書き込みが失敗した場合

これらのエラーを区別する必要があるのは、プロジェクトに既存の catch/failure ハンドラーがある場合のみです。汎用のフォールバックを使用している場合、変更は不要です。

// v3 — if the project wants to distinguish storage failures from network failures:
credentialsManager.credentials { result in
    switch result {
    case .success(let credentials):
        use(credentials)
    case .failure(let error):
        switch error {
        case .noCredentials, .renewFailed:
            // credentials missing or refresh failed — force re-login
            navigateToLogin()
        case .storeFailed:
            // renewed successfully but couldn't save — credentials valid in memory this session
            // user will be asked to log in again on next launch
            reportToMonitoring(error)
            use(/* last known credentials if available */)
        default:
            showError(error)
        }
    }
}

これらの新しい case 分岐は、プロジェクトに CredentialsManagerError を対象とする switch が現在あり、分岐ごとに異なる処理を行う必要がある場合にのみ追加してください。default: 分岐があれば、変更しなくても適切に処理されます。


6.9 — UserInfo → UserProfile の型名変更

該当する場合: 手順 4 で、プロジェクトのソースファイルに UserInfo を参照する型注釈、関数シグネチャ、または変数宣言が見つかった場合。

UserInfo 型は UserProfile に名前が変更されました。UserInfo を参照するすべての型注釈、関数シグネチャ、変数宣言を更新してください。

// v2
var currentUser: UserInfo?
func showProfile(_ profile: UserInfo) { ... }
func fetchUser() -> UserInfo? { ... }

// v3
var currentUser: UserProfile?
func showProfile(_ profile: UserProfile) { ... }
func fetchUser() -> UserProfile? { ... }

プロジェクトが Auth0.authentication().userInfo(withAccessToken:) を呼び出している場合、メソッド名は変わりませんが、戻り値の型は変更されています:

// v2 — returns Request<UserInfo, AuthenticationError>
Auth0.authentication()
    .userInfo(withAccessToken: accessToken)
    .start { (result: Result<UserInfo, AuthenticationError>) in ... }

// v3 — returns Request<UserProfile, AuthenticationError>
Auth0.authentication()
    .userInfo(withAccessToken: accessToken)
    .start { (result: Result<UserProfile, AuthenticationError>) in ... }

6.10 — Credentials.expiresIn → Credentials.expiresAt

該当する場合: 手順 4 で、Credentials、APICredentials、または SSOCredentials オブジェクトの .expiresIn へのアクセスが見つかった場合。

Credentials、APICredentials、SSOCredentials の expiresIn: Date プロパティは expiresAt: Date に名前が変更されました。基になる JSON キーは変わっておらず、変更されたのは Swift のプロパティ名のみです。

// v2
let expiry: Date = credentials.expiresIn

// v3
let expiry: Date = credentials.expiresAt

6.11 — CredentialsStorage のカスタム実装 — メソッドが例外をスローするように変更

該当する場合: 手順 4 で、プロジェクトのソースファイルに CredentialsStorage に準拠する型が見つかった場合。プロジェクトが SimpleKeychain インスタンスを渡すだけの場合はスキップしてください — デフォルトのストレージに変更は不要です。

この項目が該当するのは、プロジェクトが カスタム の CredentialsStorage 実装を提供している場合のみです(つまり、デフォルトの SimpleKeychain を使用するだけでなく、プロトコルに準拠する型を実装している場合)。プロジェクトが SimpleKeychain インスタンスを渡すだけの場合はスキップしてください。

このプロトコルは、戻り値が Bool/Data? から例外をスローするメソッドに変更され、新しい必須メソッド deleteAllEntries() が追加されました。

// v2 — protocol conformance
final class AppKeychain: CredentialsStorage {
    func getEntry(forKey key: String) -> Data? {
        return Keychain.shared.read(key: key)
    }

    func setEntry(_ data: Data, forKey key: String) -> Bool {
        return Keychain.shared.write(data, forKey: key)
    }

    func deleteEntry(forKey key: String) -> Bool {
        return Keychain.shared.delete(key: key)
    }
}

// v3 — methods throw; deleteAllEntries() required
final class AppKeychain: CredentialsStorage {
    func getEntry(forKey key: String) throws -> Data {
        guard let data = Keychain.shared.read(key: key) else {
            throw CredentialsManagerError.noCredentials
        }
        return data
    }

    func setEntry(_ data: Data, forKey key: String) throws {
        guard Keychain.shared.write(data, forKey: key) else {
            throw CredentialsManagerError.storeFailed
        }
    }

    func deleteEntry(forKey key: String) throws {
        guard Keychain.shared.delete(key: key) else {
            throw CredentialsManagerError.revokeFailed
        }
    }

    func deleteAllEntries() throws {
        Keychain.shared.deleteAll()
    }
}

CredentialsStorage プロトコルでは、特定のエラー型を指定せずにメソッドを throws として宣言しています。任意の Error をスローできます。上記の例で CredentialsManagerError のケースを使用しているのは説明のためだけです。実装では、ストレージバックエンドに適したエラー型をスローしてください。再利用する場合は、手順 3 で取得した SDK のソースで CredentialsManagerError のケース名を確認してください。


6.12 — Management クライアントの削除

該当する場合: Step 4 で、プロジェクトのソースファイルに Auth0.users( または Auth0.users(token: の呼び出しが見つかった場合。

Auth0.users(token:) と Users 管理クライアント全体は、v3 で SDK から削除されました。呼び出し箇所を黙って削除しないでください。TODO コメントを追加し、移行の概要に明記してください。

// v2 — direct Management API call in the app
Auth0
    .users(token: managementToken)
    .patch(userId, userPatch: UserPatchAttributes(name: newName))
    .start { result in
        switch result {
        case .success: print("Updated")
        case .failure(let error): print(error)
        }
    }

// v3 — Management client removed; add TODO and preserve intent
// TODO: Auth0.swift v3 removed the Management client.
// Replace this with a call to your own backend endpoint, which
// calls the Auth0 Management API using a machine-to-machine token.
// NEVER embed a Management API token in the client app.
// See: https://auth0.com/docs/secure/tokens/access-tokens/management-api-access-tokens

これはバックエンドの作業が必要です。Step 9 の概要に記録してください。


6.13 — Authentication からの MFA メソッドの削除 → MFAClient に移行

該当する場合: Step 4 で、プロジェクトのソースファイルに login(withOTP:、login(withOOBCode:、login(withRecoveryCode:、multifactorChallenge( のいずれかの呼び出し、または MFAClient に準拠するテストモックが見つかった場合。

Authentication プロトコルの 4 つの MFA メソッドは v3 で削除されました。これらは、Auth0.mfa() からアクセスできる専用の MFAClient プロトコルに置き換えられています。

v2(Authentication)v3(MFAClient)
authentication().login(withOTP: otp, mfaToken: token)mfa().verify(otp: otp, mfaToken: token)
authentication().login(withOOBCode: code, mfaToken: token, bindingCode: binding)mfa().verify(oobCode: code, bindingCode: binding, mfaToken: token)
authentication().login(withRecoveryCode: code, mfaToken: token)mfa().verify(recoveryCode: code, mfaToken: token)
authentication().multifactorChallenge(mfaToken: token, types: types, authenticatorId: id)mfa().challenge(with: id, mfaToken: token)

mfaToken 自体は、これまでと同じ場所から取得します。error.isMultifactorRequired == true の AuthenticationError から、error.mfaRequiredErrorPayload?.mfaToken を使ってトークンを取得できます。


OTP(TOTP 認証アプリ):

// v2
Auth0.authentication()
    .login(withOTP: otpCode, mfaToken: mfaToken)
    .start { result in
        switch result {
        case .success(let credentials): storeCredentials(credentials)
        case .failure(let error): showError(error)
        }
    }

// v3 — verify returns any TokenRequestable<Credentials, MFAVerifyError>
Auth0.mfa()
    .verify(otp: otpCode, mfaToken: mfaToken)
    .start { result in
        switch result {
        case .success(let credentials): storeCredentials(credentials)
        case .failure(let error): showError(error)
        }
    }

// async/await
let credentials = try await Auth0.mfa().verify(otp: otpCode, mfaToken: mfaToken).start()

OOB(SMS/メールコード):

// v2
Auth0.authentication()
    .login(withOOBCode: oobCode, mfaToken: mfaToken, bindingCode: bindingCode)
    .start { result in ... }

// v3 — parameter order changed: oobCode first, bindingCode second
Auth0.mfa()
    .verify(oobCode: oobCode, bindingCode: bindingCode, mfaToken: mfaToken)
    .start { result in ... }

リカバリーコード:

// v2
Auth0.authentication()
    .login(withRecoveryCode: recoveryCode, mfaToken: mfaToken)
    .start { result in ... }

// v3
Auth0.mfa()
    .verify(recoveryCode: recoveryCode, mfaToken: mfaToken)
    .start { result in ... }

MFA チャレンジ(OOB コードの送信を要求):

// v2
Auth0.authentication()
    .multifactorChallenge(mfaToken: mfaToken,
                          types: ["oob"],
                          authenticatorId: authenticatorId)
    .start { result in ... }

// v3 — types parameter removed; pass authenticatorId directly
Auth0.mfa()
    .challenge(with: authenticatorId, mfaToken: mfaToken)
    .start { result in ... }

mfaToken を取得するための MFA 必須エラーの処理(v2 と v3 で変更なし):

Auth0.authentication()
    .login(usernameOrEmail: email,
           password: password,
           realmOrConnection: "Username-Password-Authentication",
           audience: audience,
           scope: scope)
    .start { result in
        switch result {
        case .success(let credentials):
            storeCredentials(credentials)
        case .failure(let error) where error.isMultifactorRequired:
            // mfaToken extracted the same way in both v2 and v3
            if let mfaToken = error.mfaRequiredErrorPayload?.mfaToken {
                presentMFAChallenge(mfaToken: mfaToken)
            }
        case .failure(let error):
            showError(error)
        }
    }

エラー型の変更:AuthenticationError → MFAVerifyError

MFAClient の verify メソッドは any TokenRequestable<Credentials, MFAVerifyError> を返します。プロジェクトで MFA の失敗ハンドラーにおいて以前 AuthenticationError の特定のケースを判定していた場合は、それらを MFAVerifyError に対応付けてください。

// v2 — MFA failures came as AuthenticationError
Auth0.authentication()
    .login(withOTP: otp, mfaToken: mfaToken)
    .start { result in
        switch result {
        case .success(let credentials): storeCredentials(credentials)
        case .failure(let error as AuthenticationError):
            if error.isMultifactorCodeInvalid {
                showError("Invalid code. Please try again.")
            } else {
                showError(error.debugDescription)
            }
        }
    }

// v3 — failures come as MFAVerifyError; fetch MFAErrors.swift for all cases
Auth0.mfa()
    .verify(otp: otp, mfaToken: mfaToken)
    .start { result in
        switch result {
        case .success(let credentials): storeCredentials(credentials)
        case .failure(let error):
            // Check the MFAVerifyError cases in Auth0/MFA/MFAErrors.swift
            // for the exact case names available in the target SDK version
            showError(error.debugDescription)
        }
    }

対象のタグ(Step 3)から Auth0/MFA/MFAErrors.swift を取得し、MFAVerifyError のケースを確認して、プロジェクトで現在行っている特定のエラー処理を対応付けてください。エラーケース名を推測せず、ソースを確認してください。


MFAClient のテストモック:

プロジェクトのテストターゲットに MFAClient に準拠するモックがある場合は、メソッドの戻り値の型を更新し、start(_:) に @MainActor を追加してください(Authentication のモックに関する §6.4 と同じパターンです)。

// v3 — mock MFAClient in tests
struct MockMFAClient: MFAClient {
    var verifyResult: Result<Credentials, MFAVerifyError>

    func verify(otp: String,
                mfaToken: String) -> any TokenRequestable<Credentials, MFAVerifyError> {
        return MockTokenRequest(result: verifyResult)
    }

    func verify(oobCode: String,
                bindingCode: String?,
                mfaToken: String) -> any TokenRequestable<Credentials, MFAVerifyError> {
        return MockTokenRequest(result: verifyResult)
    }

    func verify(recoveryCode: String,
                mfaToken: String) -> any TokenRequestable<Credentials, MFAVerifyError> {
        return MockTokenRequest(result: verifyResult)
    }

    func challenge(with authenticatorId: String,
                   mfaToken: String) -> any Requestable<MFAChallenge, MfaChallengeError> {
        // Fetch MFAClient.swift from the target tag to find MFAChallenge's initializer,
        // then construct a real fixture or return .failure for tests that don't exercise this path
        return MockRequest(result: .failure(/* MfaChallengeError case from MFAErrors.swift */))
    }
    // implement remaining MFAClient requirements using the same pattern
}

§6.4 の MockTokenRequest と MockRequest の構造体を使用してください。MFAClient プロトコルでは、getAuthenticators、enroll(mfaToken:phoneNumber:)、enroll(mfaToken:)、enroll(mfaToken:email:) も必要です。MFAClient.swift に記載された戻り値の型を使用し、同じ方法でスタブを作成してください。

移行したすべての MFA フローを Step 9 の概要に記載し、テナント設定に対してすべての MFA フロー(OTP、OOB、リカバリーコード、チャレンジ要求)をエンドツーエンドで再テストするようユーザーに依頼してください。


6.14 — デフォルトのスコープに offline_access が含まれるようになりました

該当する場合: Step 4 で webAuth()、webAuth(domain:)、または webAuth(domain:clientId:) の呼び出しが見つかった場合。ただし、.scope(…) 修飾子がすでに指定されている呼び出しチェーンは除きます。ファイル内の実際の呼び出し箇所を読み、.scope( が存在するか確認してください。grep は使用しないでください。呼び出しチェーンが複数行にまたがっている場合があります。

v3 では、デフォルトのスコープが "openid profile email" から "openid profile email offline_access" に変更されました。デフォルトのスコープに依存していて、リフレッシュトークンを必要としないアプリでは、明示的に .scope() を呼び出してください。

// v2 — default scope: "openid profile email" (no refresh token)
Auth0.webAuth()
    .audience("https://api.example.com")
    .start { result in ... }

// v3 — default scope includes offline_access (refresh token returned)
// If you want to keep the v2 behaviour (no refresh token), add .scope() explicitly:
Auth0.webAuth()
    .audience("https://api.example.com")
    .scope("openid profile email")  // explicit — no offline_access
    .start { result in ... }

// If refresh tokens are welcome (recommended — enables silent renewal):
// No change needed; the new default is intentional.

どちらの方法を選んだ場合でも、Step 9 の概要ではこれを動作の変更として提示してください。リフレッシュトークンを発行するには、Auth0 テナントでこのアプリのオフラインアクセスを許可する必要があります。


6.15 — CredentialsManager.credentials() — minTTL のデフォルト値が 0 秒から 60 秒に変更されました

該当する場合: Step 4 で、minTTL: パラメーターを明示せずに credentialsManager.credentials( を呼び出している箇所が見つかった場合。

v3 では、CredentialsManager.credentials(withScope:minTTL:parameters:headers:callback:) の minTTL のデフォルト値が 0 から 60 に変更されました。これにより、クレデンシャルマネージャーはトークンが実際に期限切れになるタイミングではなく、その 60 秒前に期限切れと判断し、サイレントリフレッシュを実行します。

これはサイレントな動作の変更です。アプリは変更なしでもコンパイルできますが、トークンの更新が以前より早く行われるようになります。

// v2 — credentials() triggers renewal only when token is actually expired (minTTL default: 0)
credentialsManager.credentials { result in
    switch result {
    case .success(let credentials): use(credentials)
    case .failure(let error): handleError(error)
    }
}

// v3 — credentials() triggers renewal 60 seconds before expiry (minTTL default: 60)
// No code change needed if this behaviour is acceptable (recommended for most apps).
// To restore the v2 behaviour explicitly:
credentialsManager.credentials(minTTL: 0) { result in
    switch result {
    case .success(let credentials): use(credentials)
    case .failure(let error): handleError(error)
    }
}

ほとんどのアプリでは、新しいデフォルト値が望ましいでしょう。期限切れの直前にトークンを更新することで、処理中のリクエストでアクセストークンがリクエストの途中に期限切れになる競合を避けられます。期限切れの瞬間にのみ更新する必要がある理由がアプリにある場合に限り、minTTL: 0 を明示的に設定してください。

Step 9 の概要では、これを動作に関する注意事項として提示してください。


Step 7 — 依存関係とビルドを更新する
# Attempt a build — expect errors for any remaining call sites
xcodebuild build \
  -scheme <SCHEME> \
  -destination "platform=iOS Simulator,name=${SIM}" \
  2>&1

エラーごとに、次の手順を実行してください。

  1. エラーを読み、該当するソース行を特定します
  2. Step 6 の API 変更のいずれかに当てはめます
  3. 修正が Step 3 で取得した実際の SDK シグネチャに一致することを確認します
  4. プロジェクト既存のスタイルに沿って修正します
  5. 再ビルドします

よくあるエラーと原因の対応:

Xcode のエラー考えられる原因
has no member 'clearSession'§6.1 — logout に名前を変更します
error enum element 'pkceNotAllowed' not found in type または 'invalidInvitationURL' not found§6.2 — switch から削除された WebAuthError ケースを削除します
モックで cannot convert return expression of type 'Request<...>'§6.4 — モックの戻り値の型を any TokenRequestable<T,E> または any Requestable<T,E> に更新します
does not conform to protocol 'Requestable'(start に @MainActor がない)§6.4 — モックの start(_:) コールバックに @MainActor を追加します
CredentialsManager に has no member 'user'§6.7 — userProfile() に変更します
cannot find type 'UserInfo'§6.9 — UserProfile に名前を変更します
has no member 'expiresIn'§6.10 — expiresAt に名前を変更します
ストア/クリア時の cannot convert value of type 'Bool'§6.5/§6.6 — do-catch または try? を追加します
does not conform to protocol 'CredentialsStorage'§6.11 — プロトコルメソッドを更新し、deleteAllEntries を追加します
call can throw, but is not marked with 'try'do-catch で囲むか、try? を追加します
sending '...' risks causing data racesプロジェクトが Swift 6 言語モードまたは SWIFT_STRICT_CONCURRENCY=complete を使用している場合にのみ発生します。既存のアクターモデルの範囲内で解決してください。移行エラーではありません

上限: ビルド修正サイクルは最大 10 回です。10 回試行してもビルドが失敗する場合は、作業を中止し、残りのエラーを状況説明とともにユーザーに提示してください。推測で修正しないでください。


Step 8 — テストを実行して検証する
# Run the test suite if one exists (reuse $SIM from Step 1)
xcodebuild test \
  -scheme <SCHEME> \
  -destination "platform=iOS Simulator,name=${SIM}" \
  2>&1 | tail -30

同じ API 変更に起因するテスト失敗(型名の誤り、メソッドの欠落)は、Step 7 と同じルールで修正してください。API の更新を超えるロジック変更が必要なテスト失敗は、ユーザーに報告してください。

# Summarise the diff
git diff --stat

ステップ 9 — 移行の概要

以下を含む簡潔な概要を提示します。

1. 適用した変更(API 領域ごとに分類し、各領域で変更したファイルを列挙)

2. 手動レビューが必要な項目

  • すべてのエラー処理の変更 — 新しいエラー型が正しく処理されることを確認してください
  • プロジェクトで以前 Bool を破棄していた箇所で、エラーを破棄するために使用されたすべての try? — 明示的なエラー処理が必要か確認してください
  • offline_access のデフォルトスコープの変更 — テナントで許可されるよう設定されていること、または明示的なスコープ呼び出しが正しいことを確認してください

3. バックエンド/設定に関するフォローアップ(該当する場合のみ)

  • WebAuthError のケースの変更(§6.2): switch 文から削除されたケースと、新たに追加されたケースを列挙します。.authenticationFailed と .codeExchangeFailed は、ユーザー向けメッセージの変更が有効な場合があります。
  • モックでの Request → Requestable(§6.4): 更新したテスト用モックファイルを列挙します。return self でスタブ化した TokenRequestable のビルダーメソッドがあれば記載し、該当するテストで正しいか確認してください。
  • 新しいエラーパス(§6.8): プロジェクトが呼び出す CredentialsManager の非同期メソッドを列挙し、新たに発生する可能性のあるエラーを記載します。
    • revoke() — .noCredentials(取り消すものがない)、.revokeFailed(サーバーでの取り消しに失敗)、.clearFailed(トークンは取り消されたが、Keychain の削除に失敗)
    • credentials() / renew() / apiCredentials() / ssoCredentials() — .noCredentials(Keychain 項目が見つからない)、.renewFailed(リフレッシュトークンの更新に失敗)、.storeFailed(更新された認証情報を保存できなかった)
    • それぞれのケースで、失敗時の処理によって正しく画面遷移するか、エラーが表示されることを確認してください。
  • Management クライアントの削除(§6.12): TODO でスタブ化した具体的な操作を列挙します。ユーザーがセキュアなバックエンドに実装する必要がある内容を説明します。
  • MFA メソッドの削除(§6.13): MFAClient に更新が必要な MFA フローを列挙します。MFA のエンドツーエンドテストを再実施するようユーザーに依頼してください。
  • デフォルトスコープの変更(§6.14): .scope() を明示的に追加したか、新しい offline_access のデフォルトを採用したかを記載します。テナントでオフラインアクセスが許可されるよう設定されていることを確認してください。
  • デフォルトの minTTL の変更(§6.15): credentialsManager.credentials() は、トークンの有効期限ちょうどではなく、期限の 60 秒前にトークンを更新するようになったことを記載します。この動作が許容されるか、または minTTL: 0 が明示的に設定されたかを確認してください。

4. 適用しなかった任意の改善(簡潔に列挙し、自動適用しないでください)

  • CredentialsManager の新しい clearAll() メソッド — 1 回の呼び出しですべての認証情報を消去します
  • 新しい MFAClient API — プロジェクトで MFA を使用しており、古いメソッドがすでに削除されている場合
  • DPoP(Demonstrating Proof of Possession)のサポート — API で送信者制約付きトークンが必要な場合
  • パスキーによるログイン/サインアップ API(iOS 16.6+、macOS 13.5+)
  • ssoCredentials() — SSO 認証情報の交換が必要な場合

5. ユーザーへの確認 — 移行の変更をコミットするか、任意の改善項目を検討するか、または特定のファイルを一緒に確認するかを尋ねてください。

セキュリティに関する注意: 概要にトークン、シークレット、クライアント認証情報、Keychain の値を絶対に含めないでください。


詳細な参照資料

よくある間違い

間違い正しい対応
Step 4 でプロジェクト内に該当 API が見つからなかったのに、§6.x のセクションを適用するStep 4 のファイル読み込みが適用の条件です。見つからなければ、そのセクション全体をスキップします
API が使用されているかどうかを grep だけで判断するgrep では、複数行にわたる呼び出しチェーン、domain:clientId: パラメーターを含む呼び出し、変数のエイリアスを見落とします。実際のファイルを読みます
プロジェクトで使用していないのに CredentialsManager に手を加えるプロジェクトが実際に呼び出すものだけを移行します
Auth0 以外のコードを囲む DispatchQueue.main ラッパーを削除するAuth0 のコールバック本体内だけにあるディスパッチラッパーのみを削除します
Management API の呼び出し箇所を黙って削除する// TODO: を追加して、概要に記載します。呼び出しを削除すると機能が損なわれます
古い MFA の呼び出し箇所を黙って削除する上記と同様に、TODO を追加して概要に記載します
取得した SDK ソースではなく、推測した知識に基づいて変更を適用するすべての修正は、Step 3 で取得したファイル内のシグネチャにたどれる必要があります
開発者がベータタグを選択したのに from: "3.0.0" を指定する安定版の範囲指定ではベータ版を解決できません。プレリリースには exact: "<TAG>" を使用します
作業ツリーに変更がある状態で移行を開始する事前に必ず git status --porcelain が空であることを確認します
既知の変更を適用せず、すぐにビルドする既知の変更をすべて適用してからビルドし、残りを確認します
ビルドが10回失敗した後も続行する作業を止めて、残っているエラーをユーザーに提示します
移行の概要を省略する必ず概要をすべて作成します。ユーザーに必要な情報です

関連スキル


参考資料

セキュリティ: ビルドログやターミナル出力にトークン、クライアントシークレット、認証情報を決して出力しないでください。バージョン管理にシークレットを決してコミットしないでください。

コメント