{"id":8220,"plugin_id":"plugin_asdk_app_6a183f5bead08191b494b99bc881e8c0","kind":"skill","collection_source":null,"comparison_source":null,"observed_at":"2026-09-30T22:52:16.869Z","digest":"2b91b99bdc4ca2a3e17e2d6cf7291ab6d74634060ded7af87f789b8de1bc8e63","against":null,"payload":{"name":"auth0-to-descope","description":"Use this skill whenever anyone asks about migrating from Auth0 to Descope — whether they're a developer doing it themselves or a technical lead evaluating the move. Triggers on: \"how do I migrate from Auth0\", \"replace Auth0 with Descope\", \"we're moving off Auth0\", \"Auth0 to Descope\", \"switch from Auth0\", \"our app uses express-openid-connect / nextjs-auth0 / auth0-fastapi / other Auth0 SDK and we want to use Descope instead\", or any question about Auth0 features (Actions, FGA, Organizations, Token Vault, CIBA) in the context of Descope. Works for any language or framework with a Descope SDK. Always use this skill before producing migration guidance — do not rely on memory alone.","included_files":[{"relative_path":"references/flows-and-widgets.md","size_in_bytes":11937},{"relative_path":"references/implementation-nuances.md","size_in_bytes":67362}],"skill_md_contents":"---\nname: auth0-to-descope\ndescription: >\n  Use this skill whenever anyone asks about migrating from Auth0 to Descope — whether they're\n  a developer doing it themselves or a technical lead evaluating the move. Triggers on: \"how\n  do I migrate from Auth0\", \"replace Auth0 with Descope\", \"we're moving off Auth0\", \"Auth0 to\n  Descope\", \"switch from Auth0\", \"our app uses express-openid-connect / nextjs-auth0 / auth0-fastapi / other Auth0 SDK and we want to use Descope instead\",\n  or any question about Auth0 features (Actions, FGA, Organizations, Token Vault, CIBA) in the\n  context of Descope. Works for any language or framework with a Descope SDK. Always use this\n  skill before producing migration guidance — do not rely on memory alone.\n---\n\n# Auth0 → Descope Migration Skill\n\nThis skill guides self-service migrations from Auth0 to Descope. It runs in three parts:\n\n1. **MCP Check** — confirm whether the Descope Docs MCP is available and suggest installing it if not\n2. **Migration Plan** — gather context via triage questions, analyze the codebase's auth touchpoints, and produce a human-readable `MIGRATION-PLAN.md` for the user to review\n3. **Execution** — if the user confirms they want to proceed, execute the plan\n\nDo not collapse these parts or skip ahead. The plan must be reviewed before code changes begin.\n\n**Primary references** (both in this skill's directory):\n- `references/implementation-nuances.md` — verified migration patterns for each framework, Auth0 feature-to-Descope mappings, and known gotchas\n- `references/flows-and-widgets.md` — Descope terminology/lingo, Flow structure and templates, Widgets, SSO Setup Suite, Console-vs-code decision guide\n\n---\n\n## Guiding Principles\n\n**Console-first.** Before recommending SDK code for any user-facing auth feature, check whether the Console, a Flow, or a Widget covers the use case. Engineers integrate once (SDK setup + session validation). All subsequent auth evolution — new methods, MFA changes, UI updates — should happen in the Console without code deployments. See `references/flows-and-widgets.md` → Console vs. Code.\n\n**Ask, don't assume.** At any design decision point — embed Flows vs. OIDC compatibility, Flow vs. custom code, Widget vs. custom page, MFA inline vs. separate enrollment, programmatic SSO vs. SSO Setup Suite — use `AskUserQuestion` rather than proceeding with an assumption. The cost of a wrong assumption compounds across 20+ files. Uncertainty about architecture or intent is always worth a question.\n\n**MCP over memory.** When the Docs MCP is available (confirmed in Part 1), use `ask-question-about-descope` to verify every SDK method name, option shape, and return type before writing it. Do not fall back to \"verify the exact method name in the SDK type declarations\" as a hedge — just verify it directly.\n\n---\n\n## Part 1: MCP Check (BLOCKING)\n\nBefore doing anything else, check whether the Descope Docs MCP is available by calling\n`search-descope-docs` with a simple query (e.g., \"session validation\").\n\n**If the tool is available:** proceed to Part 2 immediately.\n\n**If the tool is not available**, show this message and use `AskUserQuestion` to ask whether\nthey want to install it first:\n\n> **Descope Docs MCP is not installed.**\n>\n> This skill uses the Descope Docs MCP to look up current API signatures, SDK methods, and\n> feature availability during migration. Without it, guidance is based on static training data,\n> which may be stale and can produce SDK calls that don't exist.\n>\n> You can install it in a few minutes at **https://docs-mcp.descope.com/** (server URL:\n> `https://docs-mcp.descope.com/mcp`). It significantly improves the accuracy of the\n> migration output — especially for SDK lookups and flow-specific configuration.\n>\n> **Would you like to install the MCP before we continue, or proceed without it?**\n\n- If they choose to install: pause and wait. Once they confirm it's installed, re-check by calling `search-descope-docs` again before proceeding.\n- If they choose to proceed without it: continue, but flag any SDK-specific answers as \"based on last known documentation — verify against the current SDK.\"\n\nDo not proceed to Part 2 until this step is resolved.\n\n---\n\n## Part 2: Migration Plan\n\nPart 2 has two sub-steps:\n\n1. **Triage** — ask the questions needed to understand scope (migration questions go here since answers shape the plan)\n2. **Codebase Analysis + Plan File** — scan the project, produce `MIGRATION-PLAN.md`, and pause for review\n\n### Step 0: Triage (BLOCKING — requires `AskUserQuestion`)\n\n**Use the `AskUserQuestion` tool to gather the information below. Do not infer answers\nfrom memory, prior conversations, or assumptions — even if you think you know.**\nThe migration path differs based on these answers; getting them wrong wastes the user's\ntime and produces incorrect guidance.\n\nDo not proceed to Step 0.5 until the user has answered.\n\n**First `AskUserQuestion` call (up to 4 questions):**\n\n1. **Backend language / framework** — Present the most likely options based on any cues\n   in the conversation (e.g., Express, Next.js, Flask/FastAPI, Go). The user can always\n   pick \"Other.\"\n2. **Migration goal** — Full cut-over, incremental/phased migration, or just evaluating.\n3. **Existing user base** — Are they migrating an app with active users in Auth0, or\n   starting fresh? This determines whether user migration planning is needed (password\n   hashes, bulk import, phased vs. big-bang cutover, forced re-login on cutover).\n4. **Preferred migration style** — Do they want to embed Descope Flows/Widgets directly (full native migration), or preserve their existing OIDC client library and point it at Descope's OIDC endpoints (OIDC compatibility layer)? Note: B2B features (Organizations/SSO/SCIM management) have no OIDC-layer equivalent and require native SDK calls regardless of path.\n\n**Second `AskUserQuestion` call — Auth0 feature usage (use `multiSelect: true`):**\n\n4. **Which Auth0 features are in use?** Present the highest-impact categories:\n   - Actions / Rules / Hooks (custom login logic)\n   - Organizations (multi-tenancy / B2B)\n   - FGA / fine-grained authorization\n   - Social login / Enterprise SSO\n\n   The user can add others via \"Other.\" Follow up on anything selected — e.g., if\n   Organizations is selected, ask about tenant-scoped SSO, SCIM, and invitations. If\n   FGA is selected, ask about the authorization model.\n\n   Also surface in a follow-up `AskUserQuestion` if not yet covered:\n   - Token Vault / Connected Accounts usage\n   - M2M / client credentials apps\n   - Custom email templates, Log Streams, Attack Protection, custom domains\n\nAfter both calls, summarize findings and flag high-complexity items (CIBA, Token Vault, FGA)\nbefore proceeding to Step 0.5.\n\n---\n\n### Step 0.5: Engineer Review Checkpoint (BLOCKING — requires `AskUserQuestion`)\n\nThese questions surface blockers the framework doesn't expose. Ask even the ones you think\nyou know. Use `AskUserQuestion` before proceeding to codebase analysis.\n\nBatch into calls of up to 4 questions. Skip questions that are clearly inapplicable given\nStep 0 answers (e.g., skip user migration planning if they said they're starting fresh).\n\n**Access and credentials**\n- Do they have access to the Descope Console and a Project ID? (If not, see Step 1.5.)\n- Do they need a Management Key? (Required for user CRUD, role management, ReBAC, Outbound Apps.)\n\n**Codebase scope**\n- Are there places in the app that read claims directly from the session token (e.g. `token.email`, `req.auth.permissions`)? These need a JWT Template configured before they'll work.\n- Do they have Auth0 Actions, Rules, or Hooks? Each one needs to be recreated as a Descope Flow step or JWT Template.\n- Are there multiple services or microservices validating Auth0 tokens? Each needs to be updated to validate Descope JWTs.\n\n**Deployment and risk**\n- Do they have multiple environments (dev / staging / prod)? Each needs its own Descope project and Project ID.\n- Is there a maintenance window, or does this need to be zero-downtime?\n\n**User migration** (if they indicated existing users in Step 0)\n- How many users? Under 1,000 → the migration script can pull directly from the Auth0 API. Over 1,000 → Auth0 API pagination breaks; they'll need to export a JSON file via Auth0's User Import/Export extension first.\n- Do they use passwords? If yes, they need to open a support ticket with Auth0 to get password hash exports — this takes time, plan for it. Without hashes, users will need to reset passwords or switch to passwordless.\n- Big-bang cutover or phased? For zero-disruption, Descope supports session migration (beta) — active Auth0 sessions can be exchanged for Descope tokens without re-auth, but users must already exist in Descope. For phased, the `freshlyMigrated` custom attribute (set automatically by the migration script) can be used in Flow conditionals to give first-time post-migration users a special onboarding path.\n- Are they aware that Auth0 sessions will be invalidated on cutover if not using session migration? Plan for a forced re-login or phased rollout.\n- Point them to the `descope/descope-migration` script (Step 3) and recommend a dry run (`--dry-run`) before any live run.\n\n**Gaps to flag immediately** (don't ask — flag these proactively based on Step 0 answers)\n- If they're using CIBA or `@auth0/ai` wrappers: flag before going further — these have no Descope equivalent and require custom implementation (see Step 3).\n- If they're using Auth0 Token Vault in an AI agent: the migration is Medium complexity; no SDK wrapper exists.\n- If they're using Auth0 Log Streams: set up Descope's Audit Webhook Connector before cutover to avoid gaps in event logging.\n\n**Console/Flow/Widget opportunities** (flag before codebase analysis, then ask):\n- If the app has a custom SSO settings page: ask whether the SSO Setup Suite + Tenant Profile Widget replaces that code.\n- If the app has a profile edit page or user management UI: ask whether a Descope Widget covers the use case.\n- If the app has a separate MFA enrollment page: ask whether MFA should be integrated into the main sign-in Flow as a step or subflow instead (almost always cleaner in Descope).\n- If any server-side code generates emails, initiates SSO, or runs logic during the auth journey: ask whether that logic can be a Flow step or Connector instead of server code.\n\nSummarize any blockers and Console/Flow opportunities before proceeding to codebase analysis.\n\n---\n\n### Step 1: Codebase Analysis\n\nScan the codebase to map every auth touchpoint before writing the plan.\n\n**Run these searches (adapt file extensions to the user's language):**\n\n```bash\n# Find all Auth0 import sites\ngrep -rn \"auth0\\|@auth0\\|express-openid-connect\\|nextjs-auth0\\|auth0-fastapi\\|go-oidc\" \\\n  --include=\"*.ts\" --include=\"*.tsx\" --include=\"*.js\" --include=\"*.py\" --include=\"*.go\" \\\n  --exclude-dir=node_modules --exclude-dir=.next --exclude-dir=dist --exclude-dir=venv \\\n  . 2>/dev/null\n\n# Find all Auth0 env var references\ngrep -rn \"AUTH0_\\|auth0\\.\" \\\n  --include=\"*.ts\" --include=\"*.tsx\" --include=\"*.js\" --include=\"*.py\" --include=\"*.go\" \\\n  --include=\"*.env*\" --include=\"*.yml\" --include=\"*.yaml\" --include=\"Dockerfile\" \\\n  --exclude-dir=node_modules --exclude-dir=.next \\\n  . 2>/dev/null\n\n# Find claim / token access patterns (things that may need JWT Template)\ngrep -rn \"token\\.\\|claims\\.\\|req\\.auth\\.\\|req\\.oidc\\.\\|session()\\.\" \\\n  --include=\"*.ts\" --include=\"*.tsx\" --include=\"*.js\" --include=\"*.py\" --include=\"*.go\" \\\n  --exclude-dir=node_modules --exclude-dir=.next \\\n  . 2>/dev/null\n\n# Find protected route declarations\ngrep -rn \"requiresAuth\\|withPageAuthRequired\\|withApiAuthRequired\\|require_session\\|@login_required\\|authMiddleware\\|isAuthenticated\" \\\n  --include=\"*.ts\" --include=\"*.tsx\" --include=\"*.js\" --include=\"*.py\" --include=\"*.go\" \\\n  --exclude-dir=node_modules --exclude-dir=.next \\\n  . 2>/dev/null\n\n# Check package.json / go.mod / requirements.txt for Auth0 dependencies\nfind . -maxdepth 3 \\( -name \"package.json\" -o -name \"go.mod\" -o -name \"requirements.txt\" \\) \\\n  ! -path \"*/node_modules/*\" -exec grep -l \"auth0\" {} \\;\n```\n\nFor each hit, record:\n- **File path and line** — where the change happens\n- **What it does** — import, route protection, claim access, logout handler, etc.\n- **Complexity** — Low (drop-in replacement), Medium (logic rewrite), High (no equivalent)\n\nRead `package.json` (or equivalent) for the exact framework version — this affects async\nbehavior (Next.js 15 vs 14) and SDK compatibility.\n\nIf the Descope Docs MCP is available, use `search-descope-docs` or `ask-question-about-descope`\nto verify current SDK method names for anything you plan to reference in the plan.\n\n---\n\n### Step 2: Write MIGRATION-PLAN.md\n\nWrite `MIGRATION-PLAN.md` to the working directory using the triage answers and codebase\nanalysis.\n\nTwo audiences: the engineer needs enough technical detail to execute; the PM or tech lead\nneeds scope, risk, and timeline without decoding jargon. Use plain English. Explain\ntechnical terms on first use. Open each section with a sentence summarizing what it means\nbefore presenting tables or evidence. Say what breaks if a risk is missed, not just that it\nexists. Pair complexity labels with time estimates; skew toward the lower bound — SDK swaps and mechanical rewrites are usually faster than they look, and repetitive files in a group after the first go much faster. Group execution into phases so parallel vs. sequential work is clear.\n\nThe plan must include these sections, in this order:\n\n#### Overview\n\n2–3 sentences: what's being replaced, what replaces it, and the recommended approach with a\none-sentence rationale. Add one sentence on what doesn't change — user-facing login behavior,\nsessions, and existing accounts are preserved.\n\nInclude a **Migration at a Glance** table:\n\n| | |\n|---|---|\n| **Approach** | Full native migration / OIDC compatibility layer |\n| **Files changing** | N source files across N areas |\n| **Console setup** | N configuration steps before launch |\n| **User impact** | No re-login required / Users will need to log in once after cutover |\n| **Estimated engineering effort** | N–N hours |\n| **Biggest risk** | One sentence naming the highest-complexity item |\n\n---\n\n#### What's Changing and Why\n\nProse (not a table) describing what each part of the system does today and what it does\nafter. Example:\n\n> Today, Auth0 handles everything related to login: it shows the login UI, issues tokens,\n> and validates them on every API request. After this migration, Descope takes over all of\n> those responsibilities. The login UI becomes a Descope component embedded in the app.\n> Token validation moves to the Descope SDK. The five Auth0 environment variables are\n> replaced by a single Descope Project ID.\n>\n> Auth0 features in use that need to carry over: [list in plain English, one clause each].\n\nTailor to triage findings.\n\n---\n\n#### Auth Touchpoints: What the Code Analysis Found\n\nOpen with the scope count (e.g., \"11 files across 4 areas\"). Group by area, not file path.\nEach group gets a sentence on what it does and what changes.\n\n**Session handling (3 files)** — These files read and validate the current user's login\nstate. They'll be updated to use the Descope session SDK instead of Auth0's.\n\n| File | What it does today | What changes |\n|---|---|---|\n| `lib/auth.ts:34` | Returns Auth0 session with `isAuthenticated`, `user`, `claims` | Rewritten to return Descope `AuthenticationInfo`; a thin adapter layer preserves the shape callers expect |\n| `middleware.ts:12` | Blocks unauthenticated requests app-wide | Updated to call Descope session validation; logic is identical, SDK call changes |\n\n**Login / logout routes (2 files)** — These handle the Auth0 redirect-based login flow.\nDescope replaces this with an embedded UI component; no redirect cycle is needed.\n\n| File | What it does today | What changes |\n|---|---|---|\n| `pages/api/auth/[...auth0].ts` | Catch-all handler for OAuth callback, logout, session refresh | Deleted — Descope handles this client-side; no server route needed |\n\nCover all functional groupings. End with: \"Total: N files. Estimated code-change effort: N–N hours.\"\n\n---\n\n#### Feature Migration: Auth0 → Descope\n\nFor each Auth0 feature confirmed in triage, write a short paragraph: what it's trying to\naccomplish, the best Descope approach for that goal, what's different, and what action is\nrequired. The best approach may be a Flow, Widget, SSO Setup Suite, or Console configuration\nrather than a direct SDK equivalent — reason about the intent, not just the API surface. Only\nrecommend SDK code when programmatic control is genuinely required. Example:\n\n> **Multi-tenancy (Auth0 Organizations → Descope Tenants)**\n> Auth0 Organizations group users by company and scope their permissions. Descope has the\n> same concept, called Tenants, and the migration script transfers them automatically.\n> The main difference is how tenant membership appears in the session token — Auth0 uses a\n> flat `org_id` string, while Descope uses a nested `tenants` object that includes per-tenant\n> roles. Any backend code that reads `req.auth.org_id` will need to be updated to read\n> `token.tenants`. This is a predictable, mechanical change.\n> **Effort: Medium (1–2 hours).** The migration script handles the data; code changes are\n> localized to token-reading logic.\n\nOnly include confirmed features.\n\n---\n\n#### Before the Code Can Run: Required Configuration\n\nSome Descope behavior is configured in the console, not in code. List every item that must\nbe set up before the app works, as checkboxes with a plain description of what it is, why\nit's needed, and roughly how long it takes. Group into \"Required before any testing\" and\n\"Required before production\":\n\n**Required before any testing:**\n- [ ] **Create a Descope project** — Takes 2 minutes. Produces a Project ID that replaces\n  all Auth0 credentials in the app's environment variables.\n- [ ] **Create an authentication flow** — Descope uses a visual \"flow\" to define the login\n  experience (what methods are offered, in what order). The built-in `sign-up-or-in` flow\n  works for most apps and requires no customization to start.\n- [ ] **Configure a user profile token template** — By default, Descope session tokens don't\n  include the user's name, email, or profile photo. This template needs to be configured so\n  the app can display user profile information. Without it, any part of the UI that shows the\n  user's name or email will show nothing after login. (~10 minutes)\n\n**Required before production:**\n- [ ] **Create roles: `admin`, `member`** (or whatever the codebase references) — Descope\n  roles must exist in the console before code that assigns them will work.\n- [ ] **Configure social login providers** (Google, GitHub, etc.) — OAuth credentials for\n  each provider need to be entered in the console. (~15 minutes per provider)\n- [ ] (continue for each item found in analysis)\n\n---\n\n#### Environment Variables\n\nDiff table with plain-English notes for each removal and addition:\n\n| Remove | Add | Why |\n|---|---|---|\n| `AUTH0_CLIENT_ID` | — | Auth0 identifies apps by client ID. Descope uses a Project ID instead — simpler, and shared across all apps in a project. |\n| `AUTH0_CLIENT_SECRET` | — | Not needed. Descope's browser-side flow doesn't require a secret. |\n| `AUTH0_ISSUER_BASE_URL` | — | The Auth0 tenant URL. Replaced by the Project ID. |\n| `AUTH0_AUDIENCE` | — | Used by Auth0 for API access scoping. Can be replicated in Descope via token templates if needed. |\n| `SECRET` | — | Used by Auth0's SDK to encrypt server-side sessions. Descope doesn't use server-side sessions. |\n| — | `DESCOPE_PROJECT_ID` | The single identifier for the Descope project. Replaces all of the above. |\n| — | `NEXT_PUBLIC_DESCOPE_PROJECT_ID` | Same value, exposed to the browser for the login component. |\n| — | `DESCOPE_MANAGEMENT_KEY` | Only needed if the app manages users, roles, or tenants server-side. |\n\nFollow with: \"Net change: 5 variables removed, 1–3 added. No secrets need to be rotated\non the Auth0 side — those credentials stop being used.\"\n\n---\n\n#### User Migration (only if existing users need to be migrated)\n\nProse strategy first, then steps. Start with: \"X existing users need to be in Descope before cutover.\" Describe:\n\n- **The plan**: whether this is big-bang (all users moved before cutover) or phased, and why\n- **What users will experience**: will they need to log in again? Will anything look different?\n- **The biggest dependency**: if password migration requires an Auth0 support ticket, say so\n  plainly and call out that this ticket should be opened immediately — it can take several days\n\nEnd with a brief checklist of the migration steps at the level a PM can track:\n- [ ] Open Auth0 support ticket to request password hash export (if applicable) — **start immediately**\n- [ ] Export user list from Auth0 (if > 1,000 users)\n- [ ] Do a dry run of the migration script against the Descope dev project\n- [ ] Review dry-run output for errors\n- [ ] Run live migration against staging, then production\n\n---\n\n#### Trade-offs and considerations\n\nThings that could affect timeline, user experience, or scope. Write each in plain English\nwith three parts: **what it is**, **what breaks if it's ignored**, and **what to do**.\nFormat each as a named callout:\n\n> **Consideration: User profile data won't appear after login until a token template is configured**\n> Descope session tokens don't include name, email, or profile photo by default. Any part of\n> the app that displays user information — profile pages, nav bars, greeting text — will show\n> blank values after migration until the token template is set up in the Descope console. This\n> is a one-time configuration step, not a code change.\n> **Action:** Configure the token template in the Descope console before running any tests.\n> Estimated time: 10 minutes.\n\n> **Consideration: Password migration requires an Auth0 support request**\n> If the app supports password-based login, users' hashed passwords must be exported from\n> Auth0 and imported into Descope. Auth0 only releases these via a support ticket, which can\n> take several days to fulfill.\n> **Action:** Open the support ticket now, in parallel with development work. Without it,\n> password users will need to reset their passwords after cutover.\n\nInclude only applicable trade-offs and considerations.\n\n---\n\n#### Execution Plan\n\nOpen with one sentence: phases run in sequence; steps within a phase can run in parallel.\nThen labeled phases, each with a time estimate:\n\n---\n\n**Phase 1 — Console Setup** (~30–45 minutes, no code required)\nCan be done by any team member with Descope console access, in parallel with other work.\n\n- [ ] Create Descope project, copy Project ID\n- [ ] Create authentication flow (use the built-in `sign-up-or-in` to start)\n- [ ] Configure user profile token template\n- [ ] Create roles: (list actual roles found)\n- [ ] Configure social login providers: (list actual providers found)\n\n**Phase 2 — Code Changes** (~X–Y hours, 1 engineer)\nWork through files in the order listed. Run a compile check after each group.\n\n- [ ] Delete `pages/api/auth/[...auth0].ts` — no replacement needed (5 min)\n- [ ] Update environment variables in `.env.example` and CI config (15 min)\n- [ ] Rewrite `lib/auth.ts` session helper (30 min)\n- [ ] Update `_app.tsx` — swap `UserProvider` for Descope `AuthProvider` (15 min)\n- [ ] Update 8 protected route files to use new session check (45 min)\n- [ ] Update `lib/logout.ts` — two-step logout (15 min)\n- [ ] Compile check and fix any type errors before proceeding\n\n**Phase 3 — User Migration** (~1–2 hours, includes dry run)\nRun against dev/staging first. Do not run against production until Phase 4 passes.\n\n- [ ] (steps from user migration section above)\n\n**Phase 4 — Testing** (~30–45 minutes)\n- [ ] Compile passes with zero errors\n- [ ] Server starts, no crashes on startup\n- [ ] Unauthenticated routes redirect to login correctly\n- [ ] Login flow completes, user profile data appears (confirms token template is working)\n- [ ] Logout invalidates session\n\n**Phase 5 — Production Cutover**\n- [ ] (cutover-specific steps based on their strategy — maintenance window, phased rollout, etc.)\n\n---\n\nTotal estimated engineering effort: **N–N hours** across N engineers.\nBlocking dependencies: (list anything on the critical path — support tickets, console access, etc.)\n\n---\n\nAfter writing `MIGRATION-PLAN.md`, **stop and tell the user:**\n\n> `MIGRATION-PLAN.md` has been written to your working directory. It maps every auth\n> touchpoint found, lists what needs Console setup before the first test, and calls out\n> trade-offs and considerations that could affect the timeline.\n>\n> Take a look before we start making changes. When you're ready to proceed, say so.\n\nDo not proceed to Part 3 unless the user confirms.\n\n---\n\n## Part 3: Execution\n\nExecute the plan in `MIGRATION-PLAN.md` Section 8 order. Follow the detailed guidance below\nfor each step.\n\n---\n\n### Context Continuity Protocol\n\nContext can be lost between turns. These rules keep the migration coherent.\n\n**Step 3.0 — Create `MIGRATION-STATE.md` before touching any code.**\n\nWrite `MIGRATION-STATE.md` to the working directory from the template below. It's the\nsource of truth for migration state — keep it current throughout execution.\n\n```markdown\n# Migration State\n\n_Last updated: [timestamp of last completed step]_\n\n## Project Context\n- Framework: [e.g., Next.js 14, Express + React]\n- Language: [TypeScript / Python / Go]\n- Package manager: [npm / yarn / pnpm / pip / etc.]\n- Migration path: [Path A: OIDC compat / Path B: Full native]\n- Migration goal: [Full cutover / Phased / Evaluating]\n\n## Triage Answers\n- Existing users: [Yes — N users / No — greenfield]\n- Password migration needed: [Yes / No]\n- Auth0 features in use: [comma-separated list]\n- Multiple environments: [Yes: dev/staging/prod / No]\n- Zero-downtime required: [Yes / No]\n\n## Files Inventory\n_All files that need to change. Update status after each step._\n\n| File | Change | Status |\n|---|---|---|\n| `pages/api/auth/[...auth0].ts` | Delete | ⬜ Pending |\n| `lib/auth.ts` | Rewrite session helper | ⬜ Pending |\n| `middleware.ts` | Update session check | ⬜ Pending |\n\n## Console Setup Checklist\n- [ ] Descope project created — Project ID: (fill in when done)\n- [ ] JWT template configured\n- [ ] Roles created: (list roles)\n- [ ] Social providers configured: (list providers)\n\n## Decisions Log\n_Non-obvious decisions made during migration — preserves rationale if context is lost._\n\n_(none yet)_\n\n## Current Phase\nPhase 1 — Console Setup (not started)\n\n## Next Action\nComplete console setup per MIGRATION-PLAN.md before making any code changes.\n\n## Blockers\n_(none)_\n```\n\n---\n\n**Rule 1 — Re-read before every turn.**\n\nAt the start of every execution turn, re-read `MIGRATION-PLAN.md` and `MIGRATION-STATE.md`\nbefore writing any code or making any decision.\n\n**Rule 2 — Verify context before every code change.**\n\nIf the framework, migration path, triage answers, or next step aren't clear from the\nconversation, re-read both files before proceeding. Then output a context line:\n\n> `Migration context: Next.js 14 · Path B · Phase 2, step 3/8 · Next: rewrite lib/auth.ts`\n\nIf this line can't be filled in accurately, re-read the files first.\n\n**Rule 3 — Update `MIGRATION-STATE.md` immediately after each step.**\n\nMark the file done in the Files Inventory, update \"Current Phase\" and \"Next Action\", and\nappend any non-obvious decision to the Decisions Log. Do this before the next step.\n\n---\n\n## Pre-Generation Protocol (apply before writing any code)\n\nRun before generating any import, wrapper type, or helper. Skipping produces code that\ncompiles but fails at runtime.\n\n**1. Verify SDK exports before writing any import.**\nWhen the Docs MCP is available, use `ask-question-about-descope` to confirm the exact method name, option shape, and return type before writing any SDK call. This is faster and more reliable than reading type declarations. Do not write a method name and add a hedge like \"verify the exact name\" — just verify it.\n\nWhen the Docs MCP is unavailable: resolve the package's type declarations (`node_modules/<pkg>/dist/types/` or its `package.json` `types` field) and confirm the exact exported name and signature. For Go, run `go doc`. For Python, check the SDK stubs.\n\n**Prefer local `node_modules/` over GitHub** when reading type declarations. Installed packages reflect the exact version in use. If the Descope package isn't installed yet, install it first, then read local type declarations. Only fall back to GitHub if the package can't be installed in the current environment.\n\nThis applies to **every SDK call you write**, not just the first import. Field names on\noption objects (`sendMail` vs `sendEmail`), hook return shapes (`useDescope()` returns the\nSDK directly, not `{ sdk }`), and subpath exports (`/client` vs root) differ just as often.\n\n**1a. After rewriting any module, grep for remaining imports of the removed package.**\n```bash\ngrep -r \"from '@/lib/auth0'\\|from '@auth0/\" --include=\"*.ts\" --include=\"*.tsx\" .\n```\nAdd remaining hits to the work list.\n\n**2. Derive wrapper types from the actual return type.**\nRead the function's declared return type and build the wrapper to match. Auth0's field\nnames, nesting, and flags differ — don't infer from them.\n\n**3. Check dependency versions before generating framework-specific code.**\nFor Next.js: `cookies()` and `headers()` from `next/headers` are synchronous in v14 and\nasync in v15. Read `package.json` (or `go.mod`, `requirements.txt`) first.\n\n**4. When making a helper async, propagate to all callers immediately.**\nIn TypeScript, `async` on a shared utility silently breaks callers that omit `await`. Grep\nfor all call sites of the changed function and update them in the same pass. The cascade can\nspan 10–20 files.\n\n**5. Verify published package versions before writing to `package.json` or running `npm install`.**\nDon't reuse Auth0's version number or rely on training data for versions. Before writing any\ninstall command:\n```bash\nnpm view @descope/node-sdk version\nnpm view @descope/nextjs-sdk version\n```\nIf npm is unavailable, leave the version as `\"latest\"` and flag it.\n\n---\n\n## Step 1.5: Descope Project Setup & Console Configuration\n\nSeveral steps require Descope Console setup that can't be done in code. The app compiles\nwithout them but won't work at runtime.\n\nUse `AskUserQuestion` to ask whether they already have a Project ID and working Flow. If\nyes, skip to verifying items 5–7 — these are easy to miss even for existing projects.\n\n### 1. Create a project and get your Project ID\n- Sign in at [console.descope.com](https://console.descope.com)\n- Your **Project ID** appears in the top-left project selector and under **Project → Settings**. It starts with `P` (e.g. `P2abc123...`).\n- For Next.js client-side code, this becomes `NEXT_PUBLIC_DESCOPE_PROJECT_ID`. For all server-side SDKs, it's `DESCOPE_PROJECT_ID`.\n\n### 2. Get a Management Key (if needed)\nRequired for: user management API, role/permission management, tenant operations, ReBAC\n(FGA), Outbound Apps, SCIM configuration. If the app does any server-side user or tenant\nmanagement, they need this.\n- Console → **Company → Management Keys → Generate Key**\n- Store as `DESCOPE_MANAGEMENT_KEY`. Treat like a secret — never expose client-side.\n\n### 3. Choose or create a Flow\nA Flow is the auth UI sequence. Reference it by Flow ID in the web component.\n\n- Console → **Authentication → Flows**\n- The built-in **\"sign-up-or-in\"** flow handles email/password, OTP, and social login.\n  Use it for most migrations.\n- To customise: duplicate \"sign-up-or-in\", rename it, then edit in the visual builder.\n- The Flow ID is in the URL when editing and in the flow list.\n- There are 100+ Flow templates in the library — check for an existing template before building a custom flow. See `references/flows-and-widgets.md` → Flows.\n- MFA: add an MFA step to the Flow or embed MFA as a subflow. Descope manages MFA enrollment through Flows, not the Management SDK — no equivalent to Auth0 Guardian enrollment tickets. For factor-deletion SDK support by type, see `references/implementation-nuances.md` → MFA section.\n\n### 4. Configure authentication methods\n- Console → **Authentication** → select methods (Email OTP, Magic Link, Social, SSO, etc.)\n- For social providers (Google, GitHub, etc.): configure OAuth credentials here, then add\n  the provider step to your Flow.\n- For enterprise SSO (SAML/OIDC): Console → **SSO** → configure per tenant. For correct SSO callback and ACS URLs (social OAuth, SAML ACS, what NOT to use), see `references/implementation-nuances.md` → Social login / SSO section.\n\n### 5. Configure a JWT Template (almost always needed)\nAuth0 includes `email`, `name`, and `picture` in tokens by default. Descope does not.\n- Console → **Authorization → JWT Templates → New Template**\n- Add claims: `{\"email\": \"{{user.email}}\", \"name\": \"{{user.name}}\", \"picture\": \"{{user.picture}}\"}`\n- Apply the template to your project. Without this step, any code reading `token.email`\n  will get `undefined` after migration.\n\n### 6. Create roles in the Console (if using RBAC)\nDescope roles are referenced by **name**, not by ID. They must be created manually in the\nConsole before the code that assigns them will work.\n- Console → **Authorization → RBAC → + Role**\n- Create each role the app references (e.g. `admin`, `member`)\n\n### 7. Define custom attributes (if using tenant/user metadata)\nAuth0 Organization `metadata` and User `app_metadata` map to Descope `customAttributes`.\nPre-define them in the Console schema before setting them via the SDK.\n- Console → **Project → Custom Attributes**\n\n### 8. Env var summary\n| Variable | Where to get it | Used by |\n|---|---|---|\n| `DESCOPE_PROJECT_ID` | Console → Project Settings | All server-side SDKs |\n| `NEXT_PUBLIC_DESCOPE_PROJECT_ID` | Same value as above | Next.js `AuthProvider` (client-side) |\n| `DESCOPE_MANAGEMENT_KEY` | Console → Company → Management Keys | Management SDK, Outbound Apps API |\n\n### 9. Consider Widgets for management UI\n\nBefore migrating custom profile pages, user management pages, or role assignment UI, ask\nwhether a Descope Widget covers the use case. See `references/flows-and-widgets.md` → Widgets.\n\n**After completing console setup:** Update `MIGRATION-STATE.md` — check off each completed\nitem in the Console Setup Checklist, record the Project ID in the file, and set Next Action\nto the first code change step.\n\n---\n\n## Step 2: Framework-Specific Migration\n\nRead `references/implementation-nuances.md` in two passes before writing any code:\n\n1. **General Insights** (always) — covers architecture, feature mapping, and common gotchas that apply to every migration regardless of framework.\n2. **Framework section** (use the file's ToC and `offset` to jump directly) — read only the section matching the user's stack:\n   - Express.js → `## Express.js`\n   - Flask / Python, FastAPI → `## Flask / Python` (FastAPI notes are in the \"No drop-in middleware\" subsection under General Insights)\n   - Next.js (App Router, standalone) → `## Next.js (standalone)` + `## Next.js (B2B): Migration Bug Catalog`\n   - Next.js + separate Express API server → `## Next.js (with separate Express API server)`\n   - Go → `## Go + Encore`\n   - LangChain / LangGraph / Vercel AI with FGA or Token Vault → `## Agentic AI Stacks`\n\nWhen a new framework is added to the file, add it to this list.\n\n### Express.js\n- Remove `express-openid-connect`; add `@descope/node-sdk` + `cookie-parser`\n- Replace `app.use(auth(config))` with ~20-line custom middleware reading the `DS` cookie\n  and calling `descopeClient.validateSession()`\n- Add `/login` route rendering `<descope-wc>` web component (EJS, plain HTML, etc.)\n- Logout: POST to `descopeClient.logout(refreshToken)` + clear `DS`/`DSR` cookies\n\n**Key gotchas:**\n- `express-openid-connect` handled CSRF and cookie parsing internally. You need\n  `cookie-parser` explicitly.\n- `req.oidc.user` → `req.user` (set from validated JWT claims after `validateSession()`)\n- `requiresAuth()` is 3 lines of custom code, not an SDK import.\n\n### Flask / Python\n- Remove `authlib`; add `descope` Python SDK\n- Remove `/callback` route entirely — no code exchange needed\n- `/login` renders the Descope web component instead of calling `authorize_redirect()`\n- Logout: `descope_client.logout(refresh_token)` + delete cookies\n- Drop Flask `session`; state lives in `DS`/`DSR` cookies\n\n**Key gotchas:**\n- `authlib` stored `access_token`, `id_token`, `userinfo` in Flask server-side session.\n  Descope doesn't use Flask sessions. Drop `APP_SECRET_KEY` and `session` imports.\n- `validate_session()` returns a dict of JWT claims. Profile fields aren't there by\n  default — configure a JWT Template first.\n\n### Next.js\n- `@auth0/nextjs-auth0` → `@descope/nextjs-sdk` + `@descope/node-sdk`\n- `UserProvider` → `AuthProvider` (takes `projectId` prop; must use `NEXT_PUBLIC_` prefix)\n- `useUser()` → `useSession()` + `useUser()` (Descope separates session state from user data)\n- Remove `pages/api/auth/[...auth0].tsx` catch-all — no server-side OIDC handling\n- Add `/login` page with `<Descope>` component rendering `sign-up-or-in` flow; always wire `onSuccess` — the component does not auto-redirect (see `references/implementation-nuances.md` → Next.js section)\n- `withPageAuthRequired` → manual `useSession()` check + redirect\n- `withApiAuthRequired` → call `session()` at handler top, return 401 manually\n- Logout: `sdk.logout()` via `useDescope()` hook (not a link to `/api/auth/logout`)\n\n**Client vs. server session access — common source of errors:**\n- `session()` from `@descope/nextjs-sdk/server` — server components, server actions, API routes **only**\n- `useSession()` + `useUser()` from `@descope/nextjs-sdk/client` — React **client** components\n  - `useSession()` returns `{ isAuthenticated, sessionToken, ... }`\n  - `useUser()` returns the user object from the current session\n- Using `session()` in a client component compiles but throws at runtime (attempts to read cookies in a browser context). Scan for this pattern before finishing any Next.js migration.\n\n**Server-side session — exact SDK API (verify before generating):**\n\nThe `@descope/nextjs-sdk/server` entry exports exactly:\n- `session(config?)` — reads session from request headers/cookies in a server component or server action. No `req` argument. Returns `Promise<AuthenticationInfo | undefined>`.\n- `getSession(req, config?)` — reads from an explicit `NextApiRequest`. API routes only.\n- `authMiddleware(options)` — Next.js middleware factory.\n\n`getServerSession` **does not exist**. The name looks plausible but isn't exported. Before writing any import, open `node_modules/@descope/nextjs-sdk/dist/types/server/index.d.ts` and confirm the export list.\n\n**Session return type — `AuthenticationInfo`, not an Auth0-style session object:**\n\n`session()` returns `AuthenticationInfo | undefined` from `@descope/node-sdk`:\n```ts\ninterface AuthenticationInfo {\n  jwt: string    // raw session JWT\n  token: Token   // decoded claims: { sub?, exp?, iss?, [claim: string]: unknown }\n  cookies?: string[]\n}\n```\nThere is no `isAuthenticated`, no `claims` field, and no `user` wrapper. Write an adapter function instead:\n```ts\nimport { session as sdkSession } from \"@descope/nextjs-sdk/server\"\n\nexport async function getDescopeSession() {\n  const authInfo = await sdkSession()\n  if (!authInfo) return null\n  return { isAuthenticated: true as const, jwt: authInfo.jwt, token: authInfo.token }\n}\n```\nThen generate all server components using `getDescopeSession()` from this local file, not from the SDK directly.\n\n**For apps with a separate API server (Express):**\n- Remove `express-jwt` + `jwks-rsa`; replace with `descopeClient.validateSession()`\n- Forward the `DS` cookie as `Authorization: Bearer <DS>` from Next.js to the API\n- No separate access token — the session token is the bearer token\n\n### FastAPI / Python\n- Remove `auth0-fastapi` (AuthConfig, auto-mounted `/api/auth/*` routes, require_session)\n- Add custom `TokenVerifier` class: reads `Authorization` header, validates against\n  Descope JWKS, attaches claims as FastAPI `Security()` dependency\n- No auto-mounted routes; no session store\n\n### Path A: OIDC Compatibility (lower risk, incremental)\n\nDescope exposes standard OIDC endpoints. If the app uses an OIDC client library\n(`express-openid-connect`, `go-oidc`, `authlib`, `@auth0/nextjs-auth0` v4, etc.), it can\npoint at Descope's OIDC issuer instead of Auth0's with minimal code changes:\n\n| Endpoint | Auth0 | Descope |\n|---|---|---|\n| Issuer | `https://YOUR_DOMAIN.auth0.com` | `https://api.descope.com` |\n| Authorization | `https://YOUR_DOMAIN.auth0.com/authorize` | `https://api.descope.com/oauth2/v1/authorize` |\n| Token | `https://YOUR_DOMAIN.auth0.com/oauth/token` | `https://api.descope.com/oauth2/v1/token` |\n| UserInfo | `https://YOUR_DOMAIN.auth0.com/userinfo` | `https://api.descope.com/oauth2/v1/userinfo` |\n| JWKS | `https://YOUR_DOMAIN.auth0.com/.well-known/jwks.json` | `https://api.descope.com/__ProjectID__/.well-known/jwks.json` |\n\n**`@auth0/nextjs-auth0` v4 (`Auth0Client`) config for Descope:**\n\n```typescript\nnew Auth0Client({\n  domain: `https://api.descope.com/${DESCOPE_PROJECT_ID}`,\n  clientId: DESCOPE_OIDC_CLIENT_ID,       // from Descope Console → Applications → OIDC App\n  clientSecret: DESCOPE_OIDC_CLIENT_SECRET,\n  logoutStrategy: \"oidc\",                 // prevents calling Auth0's /v2/logout\n  secret: SESSION_ENCRYPTION_SECRET,      // still needed for server-side session encryption\n})\n```\n\nAuth0-specific params that **do not carry over** to Descope via OIDC:\n- `screen_hint: \"signup\"` — Auth0-specific, ignored by Descope\n- `organization: orgId` — Auth0 org-scoped login has no OIDC equivalent in Descope\n- `appClient.updateSession()` — no equivalent; session is read-only after login\n\n**Good for:** Teams that want to swap the IdP first, then refactor to Descope-native SDKs\nlater. Preserves existing OIDC client code.\n\n**Caveats:** Claim shapes differ, token lifetimes may differ, and Auth0 Actions must be\nrebuilt in Descope Flows regardless of path.\n\n> **For B2B apps using Auth0 Organizations:** Path A preserves roughly 20% of the work\n> (middleware, `getSession()`, session routes); the remaining 80% (management SDK, org-scoped\n> login, signup flow, claim mapping) requires full migration regardless. **Path A savings are\n> minimal for B2B workloads** — account for this when estimating effort.\n\n### Go\n- Remove `go-oidc` + `golang.org/x/oauth2`; add `descope/go-sdk`\n- Remove login/callback/logout backend endpoints (~150 lines) — only keep token validation\n- Session validation: `descopeClient.Auth.ValidateSessionWithToken(ctx, token)` returns\n  `(bool, *descope.Token, error)`. `Token.Claims` is `map[string]interface{}`\n- `sub` claim maps directly to your auth handler's user ID\n- Auth config: ClientID + ClientSecret + Domain + RedirectURL → ProjectID only\n\n**After completing framework code changes:** Update `MIGRATION-STATE.md` — mark each\nmodified file as Done in the Files Inventory, update Current Phase and Next Action, and\nlog any non-obvious decisions made (adapter types kept, async cascade scope, etc.).\n\n---\n\n## Step 2.5: Non-Code File Updates\n\nScan for Auth0 references in non-code files after updating source files.\n\n### `.env.example` / `.env.template` / `.env.sample`\n```\n# REMOVE\nAUTH0_CLIENT_ID=\nAUTH0_CLIENT_SECRET=\nAUTH0_ISSUER_BASE_URL=\nAUTH0_AUDIENCE=\nAUTH0_BASE_URL=\nSECRET=\n\n# ADD\nDESCOPE_PROJECT_ID=            # Console → Project Settings\nNEXT_PUBLIC_DESCOPE_PROJECT_ID= # Next.js only — same value as above\nDESCOPE_MANAGEMENT_KEY=        # Console → Company → Management Keys (only if using management SDK)\n```\nRun `grep -r \"AUTH0\"` to find all env var references — `.env.example`, Docker, CI, shell scripts.\n\n### README / docs\nSearch all `.md` files for Auth0 references. At minimum, update:\n- **Setup section** — replace \"create an Auth0 app\" instructions with Descope Console setup steps\n- **Environment variables section** — reflect the reduced env var set\n- **Run instructions** — replace Auth0 tenant steps with Descope Console steps\n- **Auth flow diagrams or descriptions** — update to reflect Descope's cookie-based approach\n\n### Docker / CI files\nCheck `Dockerfile`, `docker-compose.yml`, `.github/workflows/`, and any CI config for\n`AUTH0_*` env var declarations. Update them to `DESCOPE_*`.\n\n### Setup / bootstrap scripts\n\nAuth0 CLI commands (`auth0 tenants patch`, `auth0 actions create/deploy`, `auth0 roles create`, etc.) have **no Descope CLI equivalent**. When the migration includes a setup or seed script (e.g., `scripts/bootstrap.mjs`, `scripts/seed.ts`), split it into two parts:\n\n1. **Console setup** (cannot be scripted): Flows, email templates, MFA configuration, branding/Styles — configure these in the Descope Console. Represent them as a Phase 1 checklist in `MIGRATION-PLAN.md`.\n2. **SDK automation** (can be scripted): role creation (`management.role.create()`), tenant creation, access key provisioning. Preserve these as a Node.js/Python script using the Descope Management SDK.\n\nAuth0 Actions deployed by the script need to be re-evaluated: each Action's logic maps to a Flow step, Scriptlet, or Connector in the Console — not a code deployment. Use `AskUserQuestion` if the intent of any Action is ambiguous.\n\n**After completing non-code file updates:** Update `MIGRATION-STATE.md` — mark env files,\nREADME, and CI config done in the Files Inventory, and advance Next Action.\n\n---\n\n## Step 3: Feature Migration Mapping\n\n### Auth0 Actions / Rules → Descope Flows + JWT Templates\n\n| Auth0 pattern | Descope equivalent |\n|---|---|\n| Custom claims in tokens | [JWT Templates](https://docs.descope.com/management/jwt-templates) |\n| Custom logic during auth | [Descope Flows](https://docs.descope.com/flows) |\n| Post-login webhooks | Flows → [Connectors](https://docs.descope.com/customize/connectors) |\n| Role assignment at login | Flow actions → RBAC role assignment steps |\n\n### Social Login → Descope Social Auth\n- Configure providers in the Descope Console under Authentication → Social\n- Add them to a Flow (no code changes)\n- The Descope web component renders configured providers automatically\n\n### MFA Enrollment → Descope Flows\n\n**Before migrating MFA enrollment:** Many Auth0 apps have a separate MFA enrollment page because Auth0 Guardian works via a server-generated enrollment ticket URL. That pattern has no Descope equivalent — and a separate page is rarely the right approach in Descope.\n\nThe Descope approach: add an MFA step directly to the sign-up/sign-in Flow — enrollment happens inline during the auth journey. Or embed MFA as a **subflow** (triggered by a condition: user is admin, risk score exceeds a threshold, etc.). Or use the `step-up` Flow template to gate sensitive operations.\n\n**Action:** Before writing any MFA enrollment code, use `AskUserQuestion` to confirm whether MFA can be integrated into the main sign-in Flow. See `references/flows-and-widgets.md` → MFA enrollment section.\n\n### RBAC: Auth0 Roles/Permissions → Descope RBAC\n\n| Auth0 | Descope |\n|---|---|\n| `req.auth.permissions.includes('read:messages')` | `token.permissions.includes('read:messages')` |\n| Role claim via namespace in Actions | `roles` array in JWT (built-in) |\n| M2M token for Management API | `DESCOPE_MANAGEMENT_KEY` for management SDK |\n\nSDK: `descopeClient.management.role.create(name, description, permissionNames, tenantId)`\n\n### Multi-Tenancy: Auth0 Organizations → Descope Tenants\n- Auth0 `org_id` (flat string) → Descope `tenants` (nested object: `{ tenantId: { roles, permissions } }`)\n- Auth0 org-scoped login → Descope routes by email domain or tenant-specific URLs\n- Users are project-level in Descope; associated with tenants, not created per-tenant\n\n### Enterprise SSO → Descope Tenant SSO\n\n**Preferred approach — SSO Setup Suite:** Before migrating management SDK SSO calls, ask whether the SSO Setup Suite removes the need for that code. The SSO Setup Suite is a no-code Console wizard that guides tenant admins through per-tenant SAML/OIDC configuration with step-by-step IdP-specific instructions (Okta, Microsoft Entra ID (formerly Azure AD), Google Workspace, etc.) — no engineering involvement needed for new tenant SSO onboarding.\n\nUse `AskUserQuestion` to ask: does this app need **programmatic** SSO configuration (CI/CD provisioning, API-driven tenant onboarding), or do tenant admins configure SSO themselves through a settings page? If the latter, the SSO Setup Suite + Tenant Profile Widget may eliminate the need for `sso.configureSAMLByTenant()` / `configureOIDCByTenant()` calls entirely.\n\nSee `references/flows-and-widgets.md` → SSO Setup Suite.\n\n**SDK path (when programmatic SSO is needed):**\n\n| Auth0 | Descope |\n|---|---|\n| `connections.create({ strategy: \"samlp\" })` | `management.sso.configureSAMLByTenant(tenantId, settings)` |\n| `connections.create({ strategy: \"oidc\" })` | `management.sso.configureOIDCByTenant(tenantId, settings)` |\n| Per-org SAML connection | Per-tenant SSO (Console → SSO or Management SDK) |\n\n### Auth0 FGA (OpenFGA) → Descope ReBAC\n\nSchema translation example:\n```\n# Auth0/OpenFGA\ntype doc\n  relations\n    define owner: [user]\n    define viewer: [user, user:*]\n    define can_view: owner or viewer\n\n# Descope ReBAC DSL\ntype doc\n  relation owner: user\n  relation viewer: user\n  permission can_view: owner or viewer\n```\n\nAPI shape differences:\n- OpenFGA: `{ user, relation, object }` tuples\n- Descope: `{ target, targetType, relation, resource, resourceType }` — explicit typed fields\n\n| Operation | Auth0 FGA | Descope ReBAC |\n|---|---|---|\n| Write relation | `fgaClient.write({ writes: [...] })` | `descopeClient.management.fga.createRelations([...])` |\n| Check | `fgaClient.check({ user, relation, object })` | `descopeClient.management.fga.check([...])` |\n| List objects | `fgaClient.listObjects(...)` | `descopeClient.management.authz.whatCanTargetAccessWithRelation(...)` |\n\nNote: `FGARetriever` from `@auth0/ai-langchain` has no Descope equivalent. Build a custom\nretriever that calls `descopeClient.management.fga.check()` per candidate document.\n\n### Token Vault / Connected Accounts → Descope Outbound Apps\n\nUsers connect accounts via `sdk.outbound.connect(appId, { redirectURL, scopes })` on the client.\n\nFetch stored tokens server-side:\n```\nPOST https://api.descope.com/v1/mgmt/outbound/app/user/token\nAuthorization: Bearer {projectId}:{managementKey}\nBody: { \"appId\": \"google-calendar\", \"userId\": \"U2abc...\", \"scopes\": [...] }\n```\n\nNo AI-framework wrapper exists (`withTokenVault()` from `@auth0/ai` has no equivalent).\nBuild a custom tool wrapper that calls the Outbound Apps API directly.\n\n### CIBA / Async Authorization → Custom Implementation Required\n\nDescope has **no CIBA equivalent**. Recommended approach:\n1. Agent creates a pending approval record in your database\n2. Frontend polls for it (or uses WebSocket)\n3. User approves via Descope Flow or custom UI\n4. Agent receives approval signal and continues\n\nThis is the highest-complexity migration item.\n\n### M2M / Client Credentials → Descope Access Keys\nAuth0 M2M apps use the client credentials grant. Descope's equivalent is\n[Access Keys](https://docs.descope.com/management/m2m-access-keys) — create one in\nConsole → Access Keys, exchange it for a JWT via `descopeClient.auth.exchangeAccessKey()`,\nand validate the resulting token the same way as user tokens.\n\n### User Migration → Descope Migration Script\n\nDescope provides a Python CLI tool — [`descope/descope-migration`](https://github.com/descope/descope-migration) — that handles bulk import of users, roles, permissions, and Auth0 organizations (→ Descope tenants) in one run.\n\n**Two import modes:**\n- **Auth0 API** — use when fewer than 1,000 users\n- **JSON export** — use when 1,000+ users; export via Auth0's User Import/Export extension\n\n**Setup:**\n```bash\ngit clone git@github.com:descope/descope-migration.git\ncd descope-migration\npython3 -m venv venv && source venv/bin/activate\npip3 install -r requirements.txt\ncp .env.example .env\n```\n\nRequired `.env` variables:\n| Variable | Where to get it |\n|---|---|\n| `AUTH0_TOKEN` | Auth0 Management API → token explorer (24h token) |\n| `AUTH0_TENANT_ID` | Your Auth0 dashboard URL |\n| `DESCOPE_PROJECT_ID` | Descope Console → Project Settings |\n| `DESCOPE_MANAGEMENT_KEY` | Descope Console → Company → Management Keys |\n\n**Always dry-run first:**\n```bash\npython3 src/main.py auth0 --dry-run\npython3 src/main.py auth0 --dry-run --from-json ./export.json --with-passwords ./password_hashes.json\n```\n\n**What gets migrated:** users, roles, permissions, Auth0 organizations → Descope tenants.\n\n**Auto-created custom attributes:**\n- `connection` (text) — the Auth0 connection type for each user\n- `freshlyMigrated` (boolean) — set to `true` on import; use this in Flow conditionals to give newly migrated users a special first-login experience, then flip it to `false` once done\n\n**Session migration (beta):** for zero-disruption cutovers, active Auth0 sessions can be\nexchanged for Descope tokens without re-authenticating. Requires users to already exist in\nDescope (import first). See [session migration docs](https://docs.descope.com/migrate/session-migration).\n\n**Large user bases (10,000+) — just-in-time migration via Auth0 Action:**\nFor large populations where a bulk export/import would be disruptive, Auth0 supports creating an Action that forwards user data to Descope on each login during a cutover window — users migrate gradually, just-in-time, without a forced re-login. This approach requires coordination with the Descope Customer Success team. Flag this if the user wants zero-disruption cutover.\n\n### Email Templates → Descope Messaging Templates\nAuth0 email templates map to Descope [Messaging Templates](https://docs.descope.com/management/messaging-templates),\nconfigured per authentication method in the Console.\n\n### Log Streams → Descope Audit Webhook\nAuth0 Log Streams map to Descope's\n[Audit Webhook Connector](https://docs.descope.com/connectors/connector-configuration-guides/network/audit-webhook).\nSet this up before cutover to avoid gaps in event logging.\n\n### Custom Domains\nCNAME `auth.example.com` → `cname.descope.com`, verify in Console, then pass `baseUrl` to\nthe Descope SDK.\n\n### Attack Protection → Descope Flow Security\nAuth0 Attack Protection maps to Descope Flow steps using security connectors: Arkose Bot Manager,\nGoogle reCAPTCHA, Fingerprint, Have I Been Pwned, AbuseIPDB. These are composable (add\ndetection steps to Flows) rather than toggle-based — not configured by default.\n\n**After completing feature migration:** Update `MIGRATION-STATE.md` — record which features\nwere migrated, mark any that were deferred or require follow-up, and advance Next Action to\ntesting.\n\n---\n\n## Step 4: Critical Gotchas (Always Cover These)\n\n### JWT Claims Are Not the Same\nDescope session JWTs contain `sub`, `amr`, `drn`, `tenants`, `roles`, `permissions`, and `dct` by\ndefault. They do **not** contain `email`, `name`, or `picture`. Auth0 ID tokens include\nthese by default.\n\n`dct` (Descope Current Tenant) is a flat string holding the active tenant ID — the direct equivalent of Auth0's `org_id`. For apps where a user is always in a single tenant context, `token.dct` is simpler to read than iterating `token.tenants`. Use `token.tenants` when you need per-tenant roles or permissions (it is a keyed object: `{ [tenantId]: { roles, permissions } }`); use `token.dct` when you only need the tenant ID.\n\n**Action required:** Configure a JWT Template in the Descope Console to add `email`,\n`name`, and any other profile fields the app reads from the token.\n\n### Audience Validation Is Opt-In\nDescope session tokens have no `aud` claim by default. Apps using `AUTH0_AUDIENCE` for\nAPI access control must:\n1. Configure a custom `aud` claim in JWT Templates\n2. Pass `audience` to `validateSession()` on the backend\n\n### Logout Is Two Steps\n1. Call `descopeClient.logout(refreshToken)` to invalidate server-side\n2. Clear `DS` and `DSR` cookies\n\nSkipping either step leaves a broken state.\n\n### Server-Side Profile Updates Don't Immediately Reflect in the Session Token\n\nAuth0's `appClient.updateSession()` has no direct server-side equivalent in Descope. Profile changes via the Management SDK don't update the JWT already in the browser. Four options:\n\n1. **Wait for auto-refresh** (~5 min default) — no code required; tolerable for most apps.\n2. **`useDescope().refresh()` client-side** — triggers an immediate token refresh. Requires the profile form to be a client component with a `useDescope()` hook. Full code pattern in `references/implementation-nuances.md` → Session refresh section.\n3. **Update JWT endpoint** (`POST /v1/mgmt/user/jwt/update`) — server-side; updates stored JWT custom claims for a specific user. Verify current behavior against Descope docs before using — this updates stored claims, not the live session token.\n4. **User Profile Widget** — if the app is building a profile edit page, the Widget handles profile updates and session refresh automatically without custom code. See `references/flows-and-widgets.md` → Widgets.\n\n**Session change event listeners:** Instead of calling `refresh()` imperatively, the Descope client SDK exposes auth state change events — use these to react to session updates across components. See [docs.descope.com/client-sdk/auth-helpers#handling-authentication-state-changes](https://docs.descope.com/client-sdk/auth-helpers#handling-authentication-state-changes).\n\n### Cookie Names Are Configurable (And May Conflict)\nDefault: `DS` (session JWT), `DSR` (refresh JWT). Configure custom names in the Descope\nConsole under the Flow's End action when running multiple Descope projects on the same\nroot domain.\n\n### One Token, Not Two\nAuth0 issues separate ID tokens and access tokens. Descope has one token: the session JWT\n(`DS` cookie). Forward it as `Authorization: Bearer <DS>` to API servers.\n\n### No Drop-In Middleware\nDescope has no `express-openid-connect` equivalent package. The middleware is ~20 lines\nof custom code.\n\n### `cookies()` and `headers()` Are Async in Next.js 15\n`cookies()` and `headers()` from `next/headers` return a `Promise` in Next.js 15+. Before\ngenerating any server-side helper that reads cookies:\n\n1. Check the project's `package.json` for the Next.js version.\n2. If ≥ 15: write `await cookies()` and mark the containing function `async`.\n3. Trace upward — making a cookie-reading helper async cascades to every caller.\n\n### Async Cascade: Trace All Callers Before Finishing\nWhen a shared utility becomes async, TypeScript accepts `await` on non-Promises without\nerror — so callers that forget `await` silently return a Promise object. Always grep for\nall call sites of any utility you make async and update them in the same pass.\n\n### Env Var Reduction\nAuth0: `CLIENT_ID`, `CLIENT_SECRET`, `ISSUER_BASE_URL`, `SECRET`, `AUTH0_AUDIENCE` (5+).\nDescope: `DESCOPE_PROJECT_ID` only (+ `DESCOPE_MANAGEMENT_KEY` for management ops).\n\n---\n\n## Step 5: Automated Testing\n\nRun the app and verify it works — don't just hand over a checklist.\n\n### Phase 0: Final stale-import sweep (BLOCKING)\n\n```bash\ngrep -r \"@auth0\\|auth0\\|express-openid-connect\\|nextjs-auth0\" \\\n  --include=\"*.ts\" --include=\"*.tsx\" --include=\"*.js\" --include=\"*.py\" --include=\"*.go\" \\\n  --exclude-dir=node_modules --exclude-dir=.next --exclude-dir=dist \\\n  .\n```\n\nIf this returns any results, **stop and fix them before proceeding**.\n\n### Phase 1: Install, compile, and start\n\n```bash\nnpm install   # or: pip install -r requirements.txt / go mod tidy\n```\n\n```bash\nnpx tsc --noEmit    # TypeScript\ngo build ./...      # Go\nmvn compile -q      # Java/Maven\n./gradlew compileJava compileKotlin  # Java/Gradle\ndotnet build        # .NET\n```\n\n**Do not proceed until compilation exits with zero errors.**\n\n**If compilation fails, diagnose by error message:**\n- `Cannot find module '@auth0/...'` → stale import; re-run Phase 0\n- `Property 'X' does not exist on type 'AuthenticationInfo'` → wrapper built against Auth0 shape; re-derive\n- `'await' expression is not allowed in synchronous contexts` → async cascade gap\n- `Object is possibly 'undefined'` on session fields → add null check or early return\n\n```bash\nnpm run dev   # or: python main.py / go run . / flask run / etc.\n```\n\n### Phase 2: Run existing tests\n\n```bash\nnpm test   # or: pytest / go test ./... / etc.\n```\n\nAuth-related test failures usually mean: a mock or fixture still uses Auth0 shapes, or a\ntest validates JWT claims that are now missing (e.g., `email` without a JWT Template).\n\n### Phase 3: Smoke test the running app\n\n```bash\n# Root path\ncurl -s -o /dev/null -w \"%{http_code}\" http://localhost:<port>/\n\n# Unauthenticated protected route (expect 302 or 401)\ncurl -s -o /dev/null -w \"%{http_code}\" http://localhost:<port>/dashboard\n\n# Login page loads Descope component\ncurl -s http://localhost:<port>/login | grep -i \"descope\"\n\n# Invalid token → 401\ncurl -s -H \"Cookie: DS=invalid_token\" http://localhost:<port>/api/me\n```\n\n### Phase 4: Verify JWT claims (if JWT Template is configured)\n\n```bash\necho \"<DS_cookie_value>\" | cut -d'.' -f2 | base64 -d 2>/dev/null | python3 -m json.tool\n```\n\nCheck that `email`, `name`, and any other expected claims are present.\n\n### Phase 5: Report results\n\n```\n## Test Results\n\n**Server startup:** ✅ Started successfully on port 3000\n**Existing tests:** ✅ 12 passed / ❌ 2 failed (list failures)\n**Unauthenticated /dashboard:** ✅ 302 → /login\n**Unauthenticated /api/protected:** ✅ 401\n**Login page loads Descope component:** ✅\n**JWT claims (email, name):** ✅ Present / ❌ Missing — JWT Template not yet configured\n\n**Blockers before going live:**\n- [ ] (list anything that failed or needs manual action)\n```\n\n**Do not proceed to Step 6 until ALL of the following are true:**\n- [ ] Phase 0 grep returns zero Auth0 references\n- [ ] Phase 1 compilation passes with zero errors\n- [ ] Phase 1 server starts and stays running\n- [ ] Phase 3 root path returns 2xx or 3xx (not 5xx)\n- [ ] Phase 3 protected routes return 302 or 401 (not 500)\n\n---\n\n## Step 6: Post-Migration Summary (Required)\n\nEvery migration produces a `MIGRATION-SUMMARY.md` covering what was done, manual setup\nremaining, and behavioral differences that matter before production.\n\n### MIGRATION-SUMMARY.md\n\n1. **What was migrated** — a table mapping each Auth0 concept to its Descope replacement\n\n2. **Behavioral differences and open questions** — numbered list of significant differences\n   between the Auth0 and Descope implementations. For each item: Auth0 behavior, Descope\n   behavior, action required.\n\n3. **Pre-deploy checklist** — actionable checkbox items for everything that must happen\n   before the migrated app can run. Prominently include all Console setup tasks — these\n   are the things easiest to forget because the code compiles without them.\n\n---\n\n## Step 7: Output Format\n\nWrite a numbered migration guide in Markdown, scoped to the user's stack. Use code\nsnippets and direct doc links. Always include the MIGRATION-SUMMARY.md deliverable (Step 6).\n\nFor complex migrations (FGA, CIBA, AI tooling), flag the high-effort items explicitly\nwith estimated complexity (Low/Medium/High) so the user can plan.\n\n---\n\n## Reference Files\n\n- `references/implementation-nuances.md` — Verified migration patterns, code-level diffs, and edge\n  cases for several frameworks.\n- Descope Docs: https://docs.descope.com\n- Auth0 Migration Guide: https://docs.descope.com/migrate/auth0\n- User Import (Custom): https://docs.descope.com/migrate/custom\n- Descope OIDC Endpoints: https://docs.descope.com/getting-started/oidc-endpoints\n- Descope Flows: https://docs.descope.com/flows\n- JWT Templates: https://docs.descope.com/management/jwt-templates\n- Access Keys (M2M): https://docs.descope.com/management/m2m-access-keys\n- Messaging Templates: https://docs.descope.com/management/messaging-templates\n- Audit Webhook: https://docs.descope.com/connectors/connector-configuration-guides/network/audit-webhook\n- Custom Domains: https://docs.descope.com/how-to-deploy-to-production/custom-domain\n- ReBAC: https://docs.descope.com/authorization/rebac\n- Outbound Apps: https://docs.descope.com/identity-federation/outbound-apps\n\n### Session Validation by Language\n- Node.js: https://docs.descope.com/getting-started/nodejs#implement-session-validation\n- Python: https://docs.descope.com/getting-started/python#implement-session-validation\n- Go: https://docs.descope.com/getting-started/golang#implement-session-validation\n- Ruby: https://docs.descope.com/getting-started/ruby#implement-session-validation\n- Java / Kotlin: https://docs.descope.com/getting-started/java#implement-session-validation\n- .NET / C#: https://docs.descope.com/getting-started/dotnet#implement-session-validation\n- Next.js: https://docs.descope.com/getting-started/nextjs#implement-session-validation\n- React: https://docs.descope.com/getting-started/react#implement-session-validation\n- Angular: https://docs.descope.com/getting-started/angular#implement-session-validation\n- Vue: https://docs.descope.com/getting-started/vue#implement-session-validation\n- Swift / iOS: https://docs.descope.com/getting-started/swift#implement-session-validation\n- Kotlin / Android: https://docs.descope.com/getting-started/android#implement-session-validation\n- Flutter: https://docs.descope.com/getting-started/flutter#implement-session-validation\n\n### SDKs (GitHub)\n- Node SDK: https://github.com/descope/node-sdk\n- Python SDK: https://github.com/descope/python-sdk\n- Go SDK: https://github.com/descope/go-sdk\n- Ruby SDK: https://github.com/descope/descope-ruby-sdk\n- Java SDK: https://github.com/descope/descope-java\n- .NET SDK: https://github.com/descope/descope-dotnet\n- Swift SDK: https://github.com/descope/swift-sdk\n- Kotlin SDK: https://github.com/descope/descope-kotlin\n- Flutter SDK: https://github.com/descope/descope-flutter\n- JS/TS monorepo (React, Angular, Vue, Next.js, Web Component, Web JS): https://github.com/descope/descope-js\n"},"changes":[],"summary":"First saved snapshot. No earlier version is available for comparison.","summary_kind":"deterministic","summary_metadata":{}}