# Auth0 Swift Major Migration

> >

- **Type:** Skill
- **Install:** `agentstack add skill-auth0-agent-skills-auth0-swift-major-migration`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [auth0](https://agentstack.voostack.com/s/auth0)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** Apache-2.0
- **Upstream author:** [auth0](https://github.com/auth0)
- **Source:** https://github.com/auth0/agent-skills/tree/main/plugins/auth0/skills/auth0-swift-major-migration

## Install

```sh
agentstack add skill-auth0-agent-skills-auth0-swift-major-migration
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Auth0.swift v3 Migration

Migrates an existing Auth0.swift v2 integration to v3. Every code change is gated on a search that confirms the project actually calls the affected API — if the project never uses `CredentialsManager`, no `CredentialsManager` code is touched. Changes follow the project's existing architecture and Apple platform conventions.

## When NOT to Use

- **New Auth0 integration** (no existing Auth0.swift): Use [auth0-swift](/auth0-swift)
- **Minor/patch update** (e.g., 2.17 → 2.18): Run `pod update Auth0` or update SPM — no migration needed
- **Android apps**: Use [auth0-android](/auth0-android)
- **React Native / Expo**: Use [auth0-react-native](/auth0-react-native) or [auth0-expo](/auth0-expo)

## Prerequisites

- Existing Auth0.swift v2 integration
- Xcode installed; project builds cleanly on the current version
- Project under git version control with a clean working tree

---

## Migration Workflow

> **Agent instruction:** Execute every step in order. The goal is a green build with the smallest correct changeset. Each code-change step is gated by the Step 4 file-reading audit — if the API was not found in the project's source files, skip the entire step for that area. Never add code the project doesn't already call.

---

### Step 1 — Pre-flight & Safety Backup

```bash
# 1a. Verify clean working tree — stop if there are uncommitted changes
git status --porcelain
```

If the output is non-empty, ask the user:
> *"You have uncommitted changes. Should I stash them before proceeding (`git stash`), or would you like to commit first?"*

```bash
# 1b. Create a safety branch the user can reset to at any time
git checkout -b auth0-v3-migration-backup
git checkout -
```

```bash
# 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  \
  -destination "platform=iOS Simulator,name=${SIM}" \
  2>&1 | tail -5
```

If the build fails, stop. Ask the user to fix the existing issues first.

---

### Step 2 — Detect Current & Target Versions

Detect the current Auth0.swift version from the project's dependency files:

```bash
# 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
```

**Resolve the target version.** There are two paths:

**Path A — the user passed a target version argument (`$ARGUMENTS`):**

Validate it against the published releases before using it. It must pass **all three** checks:

```bash
# 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. **Exists** — the requested tag appears in the published release list above.
2. **Correct major** — the tag is within the **v3** major line (starts with `3`). A `2.x` or any other major is not valid; reject it.
3. **Not a downgrade** — the tag is newer than the version detected in the project.

> **On any check failing, STOP and ask the user.** Do not silently fall back. For example:
> - *"`3.9.9` isn't a published Auth0.swift release. Published v3 releases are: `3.0.0-beta.2`, … . Please pass a valid v3 tag, or omit the argument to auto-resolve the latest v3 release."*
> - *"`2.10.0` is a v2 release, not v3. This skill migrates to v3. Pass a v3 tag (e.g. `3.0.0-beta.2`) or omit the argument."*
> - *"`3.0.0-beta.1` is older than the `3.0.0-beta.2` already in your project — that's a downgrade. Pass a newer v3 tag or omit the argument."*

**Path B — no argument: auto-resolve the latest v3 release (including pre-releases):**

```bash
# 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('')
"
```

Record the result as `` and use it in every subsequent step.

> **If `` is a pre-release** (contains `-beta`, `-rc`, etc.), inform the user before continuing:
> *"The latest v3 release is `` (a pre-release). I'll migrate to that. You can pin a different tag by passing it as an argument: `auth0-swift-major-migration `."*
>
> **If no v3 release exists** (the resolver returns empty), stop and tell the user there is no published v3 release to migrate to.

---

### Step 3 — Fetch & Read the v3 SDK Source

Fetch the actual Swift source for the target tag. The signatures here are the authoritative reference for every change made in Step 6.

```bash
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
```

Read the fetched source and note:
- Every public method signature that changed (return type, parameters, `throws` added)
- Types that were renamed or removed
- Protocol requirements that changed
- Default parameter values that changed

This is the ground truth. Every change in Step 6 must match a real signature in these files.

---

### Step 4 — Audit Which Auth0 APIs the Project Uses

**Find all Swift files that import Auth0 — these are the scope of the migration:**
```bash
grep -rl "import Auth0" --include="*.swift" .
```

**Read every file from that list.** Do not grep for specific API patterns — read the full source so you can see exactly how `Auth0`, `webAuth`, `authentication`, `credentialsManager`, and any Auth0 types are used, including calls with domain/clientId parameters, chained builder calls, and any custom conformances.

For each file, identify:

| What to look for | Why it matters |
|---|---|
| Any call to `webAuth()`, `webAuth(domain:)`, `webAuth(domain:clientId:)` | §6.1 – `clearSession` rename; §6.14 – default scope |
| Any call to `.clearSession(` | §6.1 — rename to `logout` |
| Switch/catch on `WebAuthError` with explicit case names | §6.2 — removed and new cases |
| `DispatchQueue.main.async` or `MainActor.run` wrapping an Auth0 callback | §6.3 — removable in v3 |
| Any stored `Request` type annotation (not just chained `.start(…)`) | §6.4 — type changed to `Requestable` |
| Test mocks conforming to `Authentication`, `MFAClient`, or `Requestable` | §6.4 — return type + `@MainActor` update |
| Any call to `credentialsManager.store(` | §6.5 — Bool → throws |
| Any call to `credentialsManager.clear()` or `credentialsManager.clear(forAudience:` | §6.6 — Bool → throws (both overloads) |
| Any access to `credentialsManager.user` (property, not method) | §6.7 — replaced by `userProfile()` method |
| Any call to `credentialsManager.revoke(` | §6.8 — new error paths |
| Any type annotation or declaration using `UserInfo` | §6.9 — renamed to `UserProfile` |
| Any access to `.expiresIn` on a `Credentials`-like object | §6.10 — renamed to `expiresAt` |
| Any type conforming to `CredentialsStorage` | §6.11 — method signatures changed |
| Any call to `Auth0.users(` or `Auth0.users(token:` | §6.12 — Management client removed |
| `login(withOTP:`, `login(withOOBCode:`, `login(withRecoveryCode:`, `multifactorChallenge(` | §6.13 — MFA methods removed |
| Any call to `webAuth()` that does **not** chain `.scope(` | §6.14 — default scope changed |
| Any call to `credentialsManager.credentials(` without explicit `minTTL:` parameter | §6.15 — default minTTL changed from 0 to 60 seconds |

Build a checklist: **"This project uses: [list]"** and **"This project does NOT use: [list]"**. Only work through the §6.x sections that appear in the "uses" list. Skip the rest entirely.

---

### Step 5 — Update the SDK Dependency

Apply only the matching package manager.

Use the `` chosen in Step 2. For stable releases (`3.x.y` with no suffix), use a range specifier. For pre-releases (`3.x.y-beta.z`), pin the exact tag — package managers treat pre-release versions as out-of-range for `~>` / `from:` rules.

**Swift Package Manager (Package.swift):**
```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")
```

Then resolve:
```bash
swift package resolve
```

**CocoaPods (Podfile):**
```ruby
# Stable v3
pod 'Auth0', '~> 3.0'

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

Then:
```bash
pod update Auth0
```

**Carthage (Cartfile):**
```plaintext
# 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"
```

Then:
```bash
carthage update Auth0.swift --use-xcframeworks
```

**Xcode-managed SPM** (no `Package.swift` at root):
- *Stable:* File → Packages → Update to Latest Package Versions, then verify the version rule is *Up to Next Major* from 3.0.0.
- *Pre-release / specific beta:* File → Packages → Update to Latest Package Versions won't resolve a beta unless the dependency already pins an exact version. Tell the user to change the version rule to *Exact Version* and enter `3.0.0-beta.2` (or the chosen tag).

Do **not** build yet — apply all known code changes first.

---

### Step 6 — Apply Breaking Changes

> **Agent instruction:** Work through only the §6.x sections that matched during the Step 4 file-reading audit. Skip every section whose API the project does not use — do not touch those files.
>
> Apply each change exactly as shown. Do not alter surrounding code, rename variables, reformat, or modernise code that isn't being migrated. Match the project's existing style: completion handler → completion handler, async/await → async/await, Combine → Combine.

---

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

**Applies if:** Step 4 found any call to `.clearSession(` in the project's source files.

The `clearSession(federated:)` method was renamed to `logout(federated:)`. The parameter and its default value are unchanged.

**Completion handler:**
```swift
// 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:**
```swift
// v2
try await Auth0.webAuth().clearSession()

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

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

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

**With `federated: true`:** The parameter name is the same — just rename the method:
```swift
// v2
try await Auth0.webAuth().clearSession(federated: true)

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

---

#### 6.2 — `WebAuthError` — removed and new cases in exhaustive `switch` statements

**Applies if:** Step 4 found any `switch` or `catch` on `WebAuthError` with explicit case names in the project's source files.

Two `WebAuthError` cases were **removed** in v3. If the project has an exhaustive `switch` over `WebAuthError` (or explicitly matches these cases), the build will fail.

Three **new** cases were added to surface previously hidden failures.

**Removed cases (will no longer compile if matched):**

| v2 case | v3 behaviour |
|---|---|
| `.invalidInvitationURL` | Removed — now surfaces as `.unknown` |
| `.pkceNotAllowed` | Removed — now surfaces as `.unknown` |

**New cases (can now appear in `catch`/`switch` blocks):**

| v3 case | When it fires |
|---|---|
| `.authenticationFailed` | Server-side failure: wrong password, MFA required, account locked, etc. |
| `.codeExchangeFailed` | Token exchange failed: network issue, invalid grant, backend error |
| `.credentialsManagerError` | Credentials manager failed to store or clear credentials after login/logout; access the underlying error via `.cause` |

**Migration — remove the deleted cases from switch statements:**
```swift
// 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)
        }
    }
}
```

**If the project uses async/await and catches specific cases:**
```swift
// 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()
    hand

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [auth0](https://github.com/auth0)
- **Source:** [auth0/agent-skills](https://github.com/auth0/agent-skills)
- **License:** Apache-2.0

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-auth0-agent-skills-auth0-swift-major-migration
- Seller: https://agentstack.voostack.com/s/auth0
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
