Migration Guide

Already have Apple Sign In (or another provider) working on your own backend? Mushu Auth can absorb your existing users with zero forced re-auth: your iOS client exchanges a live legacy session token for a Mushu session, invisibly, on next launch.

User IDs are preserved. Mushu uses the same Apple sub (or Google sub) claim as your existing implementation. No user-ID migration needed.

Two ways to migrate

PathWhen to pick itUser-visible impact
Legacy-bridge exchange (recommended) You have active users you don't want to sign out. You can run one more client release that calls Mushu at launch. None. Invisible on next launch.
Natural expiry You want minimum integration work. You're OK waiting weeks-to-months for all legacy sessions to expire. None during session; users re-Apple-Sign-In when their legacy token expires.

Path A: Legacy-bridge exchange (recommended)

Mushu exposes a per-tenant bridge endpoint that validates a JWT issued by your existing backend and mints an equivalent Mushu session in return. Your iOS client handles the swap silently.

1. Create the app and Apple provider

mushu app create "My App" --bundle-id com.my.App --org my-org
# → app_id: app_abc123

mushu auth-providers create \
  --app app_abc123 \
  --provider apple \
  --apple-team-id DC7XXXXXXX \
  --apple-key-id   CJ4XXXXXXX \
  --apple-bundle-ids com.my.App,com.my.AppClip \
  --apple-private-key-file ./AuthKey_CJ4.p8

The same Apple Developer credentials you're already using — no need to rotate or re-issue keys.

2. Attach the legacy bridge

Pick one of these based on how your legacy backend signs its JWTs:

If your backend exposes a JWKS URL (recommended for external teams)

mushu auth-providers attach-legacy-bridge \
  --app app_abc123 \
  --algorithm RS256 \
  --jwks-url https://api.yourbackend.com/.well-known/jwks.json \
  --provider-sub-type apple \
  --issuer   https://api.yourbackend.com \
  --audience your-ios-app

If your backend signs with a shared HS256 secret

mushu auth-providers attach-legacy-bridge \
  --app app_abc123 \
  --algorithm HS256 \
  --signing-secret-file ./legacy-hs256.secret \
  --provider-sub-type apple

The secret is stored in AWS Secrets Manager under your tenant's namespace. Only the ARN is persisted on your provider row.

3. Update your iOS client to exchange on launch

// On app launch, if a legacy session is in keychain but no Mushu session:
if let legacyJWT = LegacyKeychain.loadAccessToken(),
   MushuKeychain.loadAccessToken() == nil {

    let resp = try await MushuAuthClient.legacyExchange(
        legacyToken: legacyJWT,
        appId: "app_abc123"
    )
    MushuKeychain.save(resp.tokens)
    // User stays on the same screen — they'll never know.
}

After the exchange, all auth.mushucorp.com-backed calls (phone enrollment, cross-service identity, etc.) use the Mushu JWT. Your legacy backend continues to accept its own JWT for whatever endpoints haven't been cut over yet.

4. Verify end-to-end with a real token

# Save a real user's legacy JWT to a file
echo "$LEGACY_JWT" > /tmp/legacy.jwt

mushu auth-providers verify \
  --app app_abc123 \
  --legacy-token-file /tmp/legacy.jwt
# ✓ apple provider present (configured=True)
# ✓ Legacy bridge enabled (algorithm=HS256)
# ✓ /auth/legacy-exchange returned a Mushu session

5. Eventually deprecate

Once all active installs have exchanged (track via the linked_at timestamp on Person records):

  1. Ship an iOS update that Apple-Sign-Ins directly through Mushu (POST /auth/apple/exchange) for new users.
  2. Stop accepting new sign-ins on your legacy backend.
  3. Once active legacy-token usage tails off, mushu auth-providers detach-legacy-bridge --app app_abc123 --provider apple.
  4. Turn off legacy JWT validation on your own backend.

Path B: Natural expiry

If you can't ship a client update, the migration still works — it just takes longer. Skip the legacy-bridge step:

  1. Create the app + Apple provider as above.
  2. Update your client to send new Apple-Sign-Ins through Mushu (POST /auth/apple/exchange).
  3. Existing legacy sessions continue working on your backend until they expire. When a user re-authenticates, they land on Mushu.
  4. Once legacy sessions are no longer used, retire the legacy backend at your own pace.

What gets preserved

  • User identity. Mushu's user_id is the Apple (or Google) sub claim — the same stable identifier you're already storing.
  • Person linkage. Mushu layers a Person ID over provider subs, so the same human using multiple apps — even across Apple / Google — ends up as one Person. First sign-in through any mushu-configured app stamps the Person link.
  • App data. Mushu stores only the auth record (user_id, app_id, person_id, optional email). Your app's profile, preferences, and domain data stay in your database.

Rollback

Legacy tokens are still valid on your own backend throughout the migration, so rollback is low-risk:

  1. Point the client back at your own auth endpoint.
  2. Mushu sessions become dormant (they don't break anything — they just stop being used).
  3. Optionally delete the provider config: mushu auth-providers delete --app app_abc123 --provider apple.

FAQ

Does this sign any of my users out?

No. The legacy-bridge path exchanges live sessions silently; the natural-expiry path respects your existing sessions until they expire.

What about TV / device / long-lived tokens?

Any token your legacy backend issues through the same user / sub path can be exchanged by the bridge. Device pairings that are scoped to a user continue to work because the user's Mushu user_id stays the same (Apple's sub).

What if my backend doesn't expose JWKS?

Use the HS256 shared-secret path. Same-team migrations are the common case — you just hand Mushu the symmetric key your backend already signs with. For external teams we strongly recommend adding a JWKS endpoint to your backend; it's a few dozen lines and keeps secrets on your side.

Can a tenant user access platform admin APIs?

No. Mushu enforces the boundary automatically: Mushu JWTs carry an app_id (aid) claim, and platform-management endpoints (apps, orgs, API keys) reject any JWT where aid != app_platform. You don't need to wire this per service.

Need help?

Contact support for a migration consult. If you're moving more than ~10k users, we can help you plan a phased rollout.