← DescopeCONTENT HISTORYWHAT CHANGED · RULE-BASED ANALYSIS
Update to Descope
Snapshot Sep 30, 2026 · 22:52 UTC · version 1.0.0
Collection source: not recorded for this historical snapshot.
First saved snapshot
No earlier snapshot is available to establish a change.
Compare saved observations
Download comparison JSONFull technical diff · 0 changed fields
Full snapshot data
{
"name": "stytch-to-descope",
"description": "Use this skill whenever anyone asks about migrating from Stytch to Descope — whether they're a developer doing it themselves or a technical lead evaluating the move. Triggers on: \"how do I migrate from Stytch\", \"replace Stytch with Descope\", \"we're moving off Stytch\", \"Stytch to Descope\", \"switch from Stytch\", \"our app uses stytch / @stytch/nextjs / Stytch UI / Stytch SSO / Connected Apps / SCIM and we want to use Descope instead\", or any question about Stytch features (Consumer authentication, Multi-tenant / B2B Authentication, Enterprise SSO, SCIM, Admin Portal, M2M Authentication, Connected Apps, Session Management, Fraud & Risk) 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": 12343
},
{
"relative_path": "references/implementation-nuances.md",
"size_in_bytes": 42910
}
],
"skill_md_contents": "---\nname: stytch-to-descope\ndescription: >\n Use this skill whenever anyone asks about migrating from Stytch 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 Stytch\", \"replace Stytch with Descope\", \"we're moving off Stytch\", \"Stytch to\n Descope\", \"switch from Stytch\", \"our app uses stytch / @stytch/nextjs / Stytch UI / Stytch SSO / Connected Apps / SCIM and we want to use Descope instead\",\n or any question about Stytch features (Consumer authentication, Multi-tenant / B2B Authentication, Enterprise SSO, SCIM, Admin Portal, M2M Authentication, Connected Apps, Session Management, Fraud & Risk) in the context of Descope. Works for any\n language or framework with a Descope SDK. Always use this skill before producing migration\n guidance — do not rely on memory alone.\n---\n\n# Stytch → Descope Migration Skill\n\nThis skill guides self-service migrations from Stytch to Descope. It runs in three parts:\n\n1. **MCP Check** — confirm whether the Descope MCP Server 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. If the file view is truncated, partial, or cut off, continue reading with the appropriate offset until all lines have been loaded; do not proceed based on a partial read.\n\nStytch is not only an authentication provider — it is a broader identity platform spanning consumer authentication, multi-tenant/B2B authentication, organizations and members, enterprise SSO, SCIM, RBAC, JIT provisioning, MFA, session management, Admin Portal flows, fraud and risk protection, device fingerprinting, Protected Auth, machine-to-machine authentication, trusted auth tokens, and Connected Apps for OAuth/OIDC-based integrations and AI-agent access. A good migration first identifies which Stytch product surfaces are in use, then maps each one to the closest target feature or migration pattern. Expect Stytch migrations to vary more widely than a purely B2B auth migration, since a Stytch implementation may include consumer passwordless auth, enterprise-readiness features, fraud/risk infrastructure, and OAuth/OIDC connected-app workflows.\n\n**Primary references** (both in this skill's directory):\n\n- `references/implementation-nuances.md` — verified migration patterns for each framework, Stytch 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, a Widget, or the SSO Setup Suite covers the use case. Engineers integrate once (SDK setup + session validation). All subsequent auth evolution — new methods, MFA changes, UI updates, tenant SSO onboarding — 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 — Flow vs. custom code, Widget vs. custom page, MFA inline vs. separate enrollment, programmatic SSO vs. SSO Setup Suite, one-Organization-to-one-Tenant mapping — use `AskUserQuestion` rather than proceeding with an assumption. The cost of a wrong assumption compounds across 20+ files, and the Stytch Organization → Descope Tenant mapping in particular ripples into SSO, SCIM, RBAC, and domain routing. Uncertainty about architecture or intent is always worth a question.\n\n**MCP over memory.** When the Descope MCP Server is available (confirmed in Part 1), use `docs_ask_question` 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., Node.js, Go, Ruby, Python, Java). The user can always\n pick \"Other.\"\n2. **Migration goal** — Full cut-over, incremental/phased migration, or just evaluating.\n3. **Existing users and organizations** — Are they migrating an app with active users and\n organizations in Stytch, staging/dev only, or starting fresh? This determines whether user\n and organization migration planning is needed (user export, org-to-tenant mapping, SCIM\n continuity, phased vs. big-bang cutover, forced re-login on cutover). \n\n**Second `AskUserQuestion` call — Stytch feature usage (use `multiSelect: true`):**\n\n1. **Which Stytch features are in use?** Present the highest-impact categories:\n\n* **Consumer Authentication** — which sign-in methods are enabled, such as OAuth/social login, email magic links, OTPs, passwords, passkeys, WebAuthn, mobile biometrics, MFA, TOTP, crypto wallet auth, or new-device notifications; whether the app uses Stytch UI, frontend SDKs, backend SDKs, or direct API calls.\n* **Multi-tenant / B2B Authentication** — whether the Stytch B2B model is used; how Organizations and Members are modeled; whether members can belong to multiple organizations; whether org discovery, org-specific login, or organization switching/session exchange is used.\n* **Organizations and Members** — organization metadata, member metadata, membership lifecycle, invitations, member search/update flows, deactivation behavior, and whether organization-specific auth policies are configured.\n* **Enterprise SSO** — whether SAML, OIDC, or both are used; which identity providers are connected; whether setup is handled internally or by customer admins; whether SSO is organization-specific, multi-organization, or standalone; whether role assignment or JIT provisioning depends on SSO claims.\n* **SCIM** — which workforce directories are connected; whether member provisioning, deprovisioning, group sync, group-to-role mapping, session revocation, or webhook handlers depend on SCIM behavior. Flag as high complexity.\n* **RBAC** — how Stytch resources, actions, permissions, and roles are defined; whether roles are consumer-level or organization/member-level; where permission checks happen in code; whether roles or permissions are included in session tokens; whether SSO or SCIM maps groups/claims to roles.\n* **JIT Provisioning** — which provisioning sources are allowed; whether users/members are automatically added to organizations after SSO, email-domain matching, discovery, invitations, or trusted token flows.\n* **MFA and Step-up Authentication** — which second factors are used, such as SMS OTP, email OTP, TOTP, passkeys, WebAuthn, or other factors; whether MFA is globally required, organization-specific, risk-based, or used only for sensitive actions.\n* **Sessions and Tokens** — how session tokens, session JWTs, intermediate sessions, custom claims, cookies, expiration, refresh, revocation, and frontend/backend session validation are implemented.\n* **Admin Portal UI** — which customer-admin workflows are handled by Stytch today, such as member management, organization settings, SSO setup, SCIM setup, or RBAC management; whether the application generates Admin Portal links or embeds Stytch-provided admin flows.\n* **Fraud & Risk / Device Fingerprinting (including Protected Auth)** — whether Device Fingerprinting is used for bot detection, credential stuffing protection, account takeover prevention, toll fraud prevention, free-trial abuse, remembered devices, trusted/unrecognized device detection, IP-geo restrictions, new-device notifications, or to enable **Protected Auth** mechanisms (which can block, challenge, add friction, or monitor suspicious attempts). Flag as high complexity if Stytch verdicts or Protected Auth flows influence authentication decisions or require custom enforcement logic.\n* **Connected Apps** — whether the application uses Stytch to act as an OAuth/OIDC authorization server; which first-party, third-party, public, or confidential clients exist; which authorization code, PKCE, consent, token, refresh token, revocation, custom scope, or RBAC-backed scope flows are implemented. Flag as high complexity. **First-party → Descope Federated Apps; third-party → Descope Inbound Apps** (public vs. confidential applies only to Inbound Apps).\n* **AI Agent / MCP Authentication** — whether Connected Apps are used for AI agents, MCP clients, CLI tools, external integrations, or agentic access to application data; review scopes, consent, dynamic client registration, token lifetimes, and organization-level controls before implementation. Flag for deeper review.\n* **Machine-to-Machine Authentication** — whether M2M clients, client credentials, client secrets, JWT access tokens, scopes, custom claims, or secret rotation are used for service-to-service authentication.\n* **Trusted Auth Tokens** — Stytch Trusted Auth Tokens let the application exchange an externally issued signed JWT for a Stytch\nsession. Stytch validates the JWT against a Trusted Auth Token Profile configured with issuer (`iss`), audience (`aud`), public keys or JWKS URL, and claim mappings. Flag as high complexity.\n* **Webhooks, Event Logs, and Event Streaming** — which Stytch events are consumed by the application; whether event logs are shown to customers, streamed to external systems, used for compliance, or used to trigger internal user/org synchronization.\n* The user can add others via **“Other.”**\n\nAfter both calls, summarize findings and flag high-complexity items before proceeding to Step 0.5. The main high-complexity Stytch areas are typically **SCIM, Enterprise SSO with JIT provisioning, RBAC tied to SSO or SCIM, Fraud & Risk/Device Fingerprinting, Protected Auth, Connected Apps, AI Agent/MCP authentication, Machine-to-Machine authentication, and Trusted Auth Tokens**.\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\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, tenant management, RBAC, SSO/SCIM configuration, access keys, Inbound Apps, Outbound Apps, and other management operations.\n* Do they have access to the Stytch Dashboard/API keys needed to inspect or export the current configuration, including Consumer Auth, B2B Organizations/Members, SSO, SCIM, RBAC, Connected Apps, Fraud & Risk, and Admin Portal settings?\n\n**Codebase scope**\n\n* Is this a Stytch Consumer Auth app, a Stytch Multi-tenant/B2B Auth app, or a hybrid app using both? This determines whether the migration centers on Users only or on Organizations/Members → Tenants/Users.\n* Are there places in the app that read claims or session fields directly from Stytch tokens or session responses, such as `user_id`, `member_id`, `organization_id`, `organization_slug`, `roles`, `permissions`, `trusted_metadata`, `untrusted_metadata`, or custom claims? These need a Descope JWT Template or Flow Custom Claims configured before equivalent reads will work.\n* Does the app read Stytch `organization_id`, `member_id`, `sso_connection_id`, `scim_group_id`, Connected App client IDs, or RBAC `role_id` / `resource_id` / `action` values in many places? The Stytch Organization → Descope Tenant remap ripples through SSO, SCIM, RBAC, JIT provisioning, Admin Portal replacement, Connected Apps, and membership checks — confirm the organization model before writing code.\n* Are there multiple services or microservices validating Stytch session tokens, session JWTs, access tokens, or Connected Apps tokens? Each service needs to be updated to validate the correct Descope-issued JWTs or OAuth/OIDC tokens.\n* Does the application use Stytch frontend SDK helpers, backend API calls, direct REST calls, Stytch UI components, or all of the above? This determines whether the migration is mostly Flow/UI replacement, backend SDK replacement, or both.\n* Does the app depend on Stytch webhooks to keep its own database in sync? Search for webhook handlers before changing user, organization, member, SCIM, RBAC, fraud, or Connected Apps behavior.\n\n**Deployment and risk**\n\n* Do they have multiple environments (dev / staging / prod)? Each needs its own Descope project and Project ID, with matching redirect URLs, auth domains, SSO/SCIM configuration, Connected Apps, and environment-specific secrets.\n* Is there a maintenance window, or does this need to be zero-downtime?\n* Are any external customers, enterprise IdPs, SCIM directories, OAuth clients, MCP clients, or machine-to-machine clients already integrated with the Stytch production project? If yes, plan customer-facing cutover steps, not just code changes.\n* Are login URLs, callback URLs, custom auth domains, email domains, OAuth issuer URLs, or JWKS URLs contractually or technically expected to stay stable? If yes, flag early because they affect SSO, sessions, Connected Apps, and token validation.\n\n**User, organization, and member migration** (if they indicated existing users/orgs in Step 0)\n\n* How many users, Organizations, and Members exist? This determines export approach and whether a phased cutover is warranted.\n* Are they using Stytch Consumer Auth users, Stytch B2B Members, or both? Consumer users and B2B Members have different object shapes and should not be collapsed without confirming the target model.\n* Do Stytch Members belong to multiple Organizations? If yes, preserve tenant membership and role assignment per organization when mapping to Descope Tenants.\n* Do they use passwords in Stytch? Plan how password credentials carry over: import if supported, force reset, staged password migration, or replacement with passwordless methods. Verify the current Stytch export capability and Descope import path before committing to an approach.\n* Which Stytch authentication methods are in use: OAuth/social login, OTP, magic links, passwords, passkeys/WebAuthn, mobile biometrics, TOTP, MFA, crypto wallets, or custom auth factors? Confirm migration feasibility for each method before writing implementation instructions.\n* Big-bang cutover or phased? Map each Stytch Organization to a Descope Tenant first; user/member migration, tenant membership, SSO, SCIM, JIT provisioning, and tenant-scoped roles depend on it.\n* **SCIM is a lifecycle system, not a one-time import.** If Stytch SCIM is enabled, enterprise directories will keep pushing create/update/deactivate/group events after cutover. A single user import is not enough — every SCIM workflow must be re-pointed at Descope before cutover, or provisioning silently breaks.\n* Are they aware that active Stytch sessions will be invalidated on cutover unless a session-bridging approach is used? Plan for forced re-login, phased rollout, or a temporary compatibility layer.\n* Does the app store Stytch IDs in its own database? If yes, plan an ID mapping table for Stytch `user_id`, `member_id`, `organization_id`, `role_id`, Connected App client IDs, and any other persisted identifiers.\n\n**Gaps to flag immediately** (don't ask — flag these proactively based on Step 0 answers)\n\n* If they're using **Fraud & Risk / Device Fingerprinting**: flag for security-flow review. Stytch verdicts, device IDs, trusted device logic, IP-geo restrictions, new-device notifications, and abuse-prevention decisions may need to be recreated with Descope Fingerprinting, Flow conditions, connectors, audit events, or app-side policy.\n* If they're using **Protected Auth**: flag that this is not a direct SDK toggle migration. Protected Auth behavior should be redesigned as Descope Flow-based risk handling: allow, challenge, block, or notify based on risk signals and policy.\n* If they're using **Connected Apps**: flag for deeper OAuth/OIDC review before implementation. Inventory clients, redirect URIs, public vs. confidential clients, PKCE, scopes, consent records, access token lifetimes, refresh token behavior, issuer/JWKS dependencies, and resource-server validation. **First-party Stytch clients map to Descope Federated Apps; third-party clients map to Descope Inbound Apps** (public vs. confidential applies only to Inbound Apps).\n* If they're using **AI agent / MCP authentication** through Stytch Connected Apps: flag for dedicated review. This may map to Descope Inbound Apps, Agentic Identity Hub, MCP server authorization, DCR/CIMD, resource scopes, or tenant-aware policies.\n* If they're using **Machine-to-Machine authentication**: identify all M2M clients, secrets, scopes, token audiences, and rotation requirements. Default to **Resources + Inbound Apps + Policies**; use Access Keys only for simple internal JWT exchange without OAuth scope/`aud` enforcement.\n* If they're using **Trusted Auth Tokens** or external JWT exchange: flag as high complexity. Confirm issuers, JWKS URLs, audiences, subject mapping, claim mapping, JIT behavior, and whether the exchange creates a user session or only API access. In Descope, this maps most closely to **Inbound Apps with the JWT Bearer grant**\n* If they're using **SCIM**: set up Descope SCIM and customer IdP cutover before production migration. Missing this can break provisioning/deprovisioning even though interactive login may still appear to work.\n* If they're using **webhooks or event logs**: identify business-critical handlers and configure Descope webhooks, audit connectors, or event streaming before cutover to avoid gaps in compliance, sync, or customer-visible activity.\n\n**Console/Flow/Widget opportunities** (flag before codebase analysis, then ask):\n\n* If the app uses the **Stytch Admin Portal UI** or Stytch Admin Portal components for member management, organization settings, SSO setup, or SCIM setup: ask whether Descope SSO Setup Suite and Admin Widgets can replace that workflow instead of rebuilding it as custom code. Do not default to building custom admin setup screens.\n* If the app has a profile edit page, member management page, organization settings page, or tenant-admin 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.\n* If any server-side code initiates SSO, generates emails, runs custom checks, calls fraud APIs, or makes decisions during the auth journey: ask whether that logic can become a Descope Flow step, condition, or Connector instead of server code.\n* If the app uses custom Stytch UI built with frontend SDKs: ask whether Descope Flows can replace the custom UI or whether the customer requires a headless SDK migration.\n* If the app uses Stytch Connected Apps and hosts its own OAuth authorization endpoint UI: ask whether Descope Inbound Apps can own more of the OAuth/OIDC authorization-server behavior, consent, token issuance, and client configuration.\n* If the app has risk-based auth, remembered-device behavior, new-device notifications, or custom fraud challenges: ask whether these should be modeled as Flow branches using Descope risk signals and messaging/connectors.\n\nSummarize any blockers and Console/Flow/Widget 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\nStytch ships **backend SDKs** (Python, Go, Node, Ruby, Java/Kotlin/JVM), **frontend SDKs**\n(React, Next.js, Vanilla JS), and **mobile SDKs** (React Native, iOS Swift, Android Consumer SDK —\na headless Kotlin Multiplatform library targeting Android). Adapt the file extensions below to\nwhichever surfaces appear in the project.\n\n**Run these searches (adapt file extensions to the user's language and platform):**\n\n```bash\n# Find all Stytch import / package sites (backend, frontend, mobile)\ngrep -rni \"stytch\\|@stytch\\|stytchauth\\|com\\.stytch\" \\\n --include=\"*.ts\" --include=\"*.tsx\" --include=\"*.js\" --include=\"*.jsx\" \\\n --include=\"*.mjs\" --include=\"*.cjs\" --include=\"*.py\" --include=\"*.go\" \\\n --include=\"*.rb\" --include=\"*.java\" --include=\"*.kt\" --include=\"*.kts\" \\\n --include=\"*.swift\" --include=\"*.gradle\" --include=\"*.gradle.kts\" \\\n --include=\"Podfile\" --include=\"Gemfile\" \\\n --exclude-dir=node_modules --exclude-dir=.next --exclude-dir=dist --exclude-dir=venv \\\n --exclude-dir=build --exclude-dir=.gradle \\\n . 2>/dev/null\n\n# Find all Stytch env var references\ngrep -rn \"STYTCH_\\|stytch\\.\" \\\n --include=\"*.ts\" --include=\"*.tsx\" --include=\"*.js\" --include=\"*.jsx\" \\\n --include=\"*.py\" --include=\"*.go\" --include=\"*.rb\" --include=\"*.java\" --include=\"*.kt\" \\\n --include=\"*.swift\" --include=\"*.env*\" --include=\"*.yml\" --include=\"*.yaml\" \\\n --include=\"Dockerfile\" \\\n --exclude-dir=node_modules --exclude-dir=.next --exclude-dir=build \\\n . 2>/dev/null\n\n# Find Stytch SDK surface + session / claim / org access patterns\n# (things that may need a JWT Template or org→tenant remap)\n# We match on \".sessions\" etc to catch any variable name (e.g. stytch.sessions, stytchClient.sessions)\ngrep -rni \"\\.sessions\\|\\.b2b\\|\\.b2c_client\\|\\.otps\\|\\.magicLinks\\|\\.magic_links\\|\\.passwords\\|\\.oauth\\|\\.webauthn\\|\\.totps\\|\\.mfa\\|\\.m2m\\|\\.scim\\|\\.connected\\|\\.idp\\|\\.rbac\\|\\.discovery\\|\\.impersonation\\|\\.users\\|\\.organizations\\|session_token\\|session_jwt\\|intermediate_session_token\\|organization_id\\|organization_slug\\|member_id\\|trusted_auth\\|external_token\\|custom_claims\\|authenticateJwt\\|authenticate(\" \\\n --include=\"*.ts\" --include=\"*.tsx\" --include=\"*.js\" --include=\"*.jsx\" \\\n --include=\"*.py\" --include=\"*.go\" --include=\"*.rb\" --include=\"*.java\" --include=\"*.kt\" \\\n --include=\"*.swift\" \\\n --exclude-dir=node_modules --exclude-dir=.next --exclude-dir=build \\\n . 2>/dev/null\n\n# Find frontend / mobile session hooks and providers\ngrep -rn \"StytchProvider\\|StytchB2BProvider\\|Products\\|StytchB2B\\|StytchLogin\\|StytchHeadlessClient\\|useStytch\\|useStytchUser\\|useStytchSession\\|createStytchUIClient\\|StytchConsumerSDK\\|StytchClient\\|StytchUI\\|@stytch/nextjs\\|@stytch/react\\|@stytch/vanilla-js\\|@stytch/react-native\" \\\n --include=\"*.ts\" --include=\"*.tsx\" --include=\"*.js\" --include=\"*.jsx\" \\\n --include=\"*.swift\" --include=\"*.kt\" \\\n --exclude-dir=node_modules --exclude-dir=.next --exclude-dir=build \\\n . 2>/dev/null\n\n# Find B2B / enterprise / fraud / connected-app feature usage\ngrep -rn \"scim\\|saml\\|sso\\|adminPortal\\|admin_portal\\|discovery\\|jit_provision\\|connectedApp\\|connected_app\\|m2m\\|client_credentials\\|deviceFingerprint\\|device_fingerprint\\|protectedAuth\\|protected_auth\\|dfp\\|webhook\" \\\n --include=\"*.ts\" --include=\"*.tsx\" --include=\"*.js\" --include=\"*.jsx\" \\\n --include=\"*.py\" --include=\"*.go\" --include=\"*.rb\" --include=\"*.java\" --include=\"*.kt\" \\\n --include=\"*.swift\" \\\n --exclude-dir=node_modules --exclude-dir=.next --exclude-dir=build \\\n . 2>/dev/null\n\n# Check dependency manifests for Stytch packages\nfind . -maxdepth 4 \\( \\\n -name \"package.json\" -o -name \"go.mod\" -o -name \"requirements.txt\" -o \\\n -name \"Gemfile\" -o -name \"pom.xml\" -o -name \"build.gradle\" -o -name \"build.gradle.kts\" -o \\\n -name \"Podfile\" -o -name \"Podfile.lock\" \\\n\\) ! -path \"*/node_modules/*\" ! -path \"*/build/*\" \\\n -exec grep -l \"stytch\\|@stytch\\|stytchauth\\|com\\.stytch\" {} \\;\n```\n\nFor each hit, record:\n\n- **File path and line** — where the change happens\n- **What it does** — import, route protection, claim access, org/tenant read, SSO/SCIM config, webhook handler, 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, organizations, and existing accounts are preserved.\n\nInclude a **Migration at a Glance** table:\n\n\n| | |\n| -------------------------------- | ------------------------------------------------------------------- |\n| **Approach** | Full native migration |\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\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, Stytch handles everything related to login: Stytch UI or frontend/mobile SDKs render\n> the login experience, issue `session_token` / `session_jwt`, and the backend SDK validates\n> sessions on every request — routing B2B users to the right organization SSO connection when\n> applicable. After this migration, Descope takes over all of those responsibilities. The login\n> UI becomes a Descope Flow embedded in the app. Session validation moves to the Descope SDK.\n> Stytch Organizations become Descope Tenants. `STYTCH_PROJECT_ID`, `STYTCH_SECRET`, and the\n> public token are replaced by `DESCOPE_PROJECT_ID` (and `NEXT_PUBLIC_DESCOPE_PROJECT_ID` for\n> the browser).\n>\n> Stytch features in use that need to carry over: [list in plain English, one clause each].\n\nTailor to triage findings.\n\n---\n\n#### Client SDK vs. Backend SDK: A Specific 1-to-1 Mapping\n\nFor every Stytch touchpoint found in triage, produce a concrete, one-to-one mapping — Stytch construct → the exact Descope SDK and method that replaces it —\nand state explicitly whether that replacement runs in the **client SDK** or the **backend SDK**, and\nwhy. Use this division of responsibility:\n\n- **Client SDK** (`@descope/web-js-sdk`, `@descope/react-sdk`, `@descope/nextjs-sdk` client\n components, or the `<descope-wc>` web component) — everything the user's browser or mobile app\n does: rendering the login/sign-up UI (a Descope Flow replaces Stytch UI, `@stytch/react`,\n `@stytch/nextjs`, `@stytch/vanilla-js`, or mobile SDK login flows), initiating authentication,\n holding the session on the client, refreshing the token, and reading the current user for UI\n purposes. This replaces Stytch frontend/mobile providers (`StytchProvider`, `useStytch`,\n `useStytchUser`, `useStytchSession`), headless client calls, and any client-side session access.\n It uses only the public Project ID — never a Management Key.\n- **Backend SDK** (`@descope/node-sdk`, `descope` (Python), `github.com/descope/go-sdk`, etc.) —\n everything the server does: validating the session JWT on every request (replacing Stytch\n server-side `sessions.authenticate()` / `sessions.authenticateJwt()` and route middleware),\n checking roles and permissions, and — with a Management Key — all administrative operations done by\n ID (user and tenant CRUD, role/permission definitions, SSO/SCIM configuration, ReBAC). This\n replaces Stytch backend SDK calls (`stytch.sessions`, `stytch.b2b.*`, `stytch.m2m`, etc.) and\n every Stytch Management API call.\n\nFor each file or area, name the Stytch call, the Descope SDK that replaces it, which side it runs on,\nand the reason (e.g. \"session validation must stay server-side because the validation/Management key\ncannot ship to the browser\"). When one Stytch feature spans both sides — for example a Stytch UI or\nmobile login flow (now a client Flow) plus per-request `sessions.authenticateJwt()` validation (now\nthe backend SDK) — split it into its client half and its backend half so the reader sees exactly what\nmoves where, and why each piece belongs on that side.\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 Stytch session\nauthentication (`sessions.authenticateJwt()`, `sessions.authenticate()`, or frontend\n`useStytchSession()`).\n\n\n| File | What it does today | What changes |\n| ------------------ | ----------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |\n| `lib/auth.ts:34` | Validates `session_jwt` via `stytch.sessions.authenticateJwt()`; returns `user_id`, `organization_id`, roles | Rewritten to return Descope `authInfo`; a thin adapter layer preserves the shape callers expect |\n| `middleware.ts:12` | Reads `stytch_session` cookie and blocks unauthenticated requests app-wide | Updated to validate Descope `DS`/`DSR` cookies via Descope session validation; logic is identical, SDK call changes |\n\n\n**Login / auth UI (2 files)** — These render Stytch UI or run headless Stytch client\nflows (magic links, OTP, OAuth, passkeys, B2B discovery). Descope replaces this with an\nembedded Flow component (or hosted Flow); token exchange and callback routes change shape.\n\n\n| File | What it does today | What changes |\n| ---------------------------- | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |\n| `app/login/page.tsx` | Renders `<StytchLogin>` or `useStytch()` headless flow | Replaced with `<Descope flowId=\"...\">` (or hosted Flow); `onSuccess` wires the Descope session client-side |\n| `app/api/authenticate/route.ts` | Exchanges `token` / `session_token` from Stytch callback | Deleted or rewritten — most flows complete client-side in Descope; verify any server-side exchange against the framework section |\n\n\nCover all functional groupings (B2B org/member management, SCIM webhooks, M2M token issuance,\nConnected Apps, mobile SDK auth, etc., when present). End with: \"Total: N files. Estimated\ncode-change effort: N–N hours.\"\n\n---\n\n#### Feature Migration: Stytch → Descope\n\nFor each Stytch 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 (Stytch Organizations → Descope Tenants)**\n> Stytch multi-tenant auth is built around **Organizations** and **Members**. A Stytch\n> Organization represents a tenant/customer in the application, and a Member is a user's account\n> within that Organization. Organization-scoped configuration can include SSO connections, SCIM,\n> JIT provisioning, approved auth methods, MFA policies, RBAC behavior, custom metadata, and\n> Connected Apps settings. Descope has the same core concept, called **Tenants**. In most migrations,\n> map one Stytch `organization_id` to one Descope **tenant ID**.\n>\n> Most code that handles Stytch Organizations is management/admin code that passes a Stytch\n> `organization_id` to B2B APIs — for example, loading an organization, updating organization\n> settings, managing members, assigning roles, configuring SSO, or configuring SCIM. That becomes\n> Descope tenant/user management code that passes a Descope **tenant ID** to the relevant tenant,\n> user, SSO, SCIM, or RBAC operation. This is mostly by-ID management work, not token parsing.\n>\n> The main request-time difference is the session shape. In Stytch B2B, the authenticated session is\n> tied to a specific Organization and returns fields such as `member_session.organization_id`,\n> `member_session.organization_slug`, `member_session.roles`, the `member` object, and the\n> `organization` object. In Descope, tenant membership and tenant-scoped roles/permissions are read\n> from the validated session/JWT and should ideally be checked with SDK helpers such as\n> `validateTenantRoles(...)` or `validateTenantPermissions(...)` rather than by manually parsing\n> claims.\n>\n> Confirm the Organization→Tenant mapping first, since it ripples into SSO, SCIM, JIT provisioning,\n> RBAC, Admin Portal replacement, Connected Apps, and any application database tables that store\n> `organization_id`. Also confirm whether Stytch Members can belong to multiple Organizations and\n> whether the app supports organization switching, because that determines whether the Descope\n> migration needs tenant selection, active-tenant handling, or separate tenant-scoped login routes.\n\n> **Effort: Medium (1–2 hours of code changes).** Confirm the data migration path for orgs first.\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\n- **Create a Descope project** — Takes 2 minutes. Produces a Project ID that replaces\nthe STYTCH_PROJECT_ID in the app's environment variables.\n- **Create an authentication flow** — Descope uses a visual \"flow\" to define the login\nexperience (what methods are offered, in what order). The built-in `sign-up-or-in` flow\nworks for most apps and requires no customization to start.\n- **Configure a user profile token template** — By default, Descope session tokens don't\ninclude the user's name, email, or profile photo. This template needs to be configured so\nthe app can display user profile information. Without it, any part of the UI that shows the\nuser's name or email will show nothing after login. (~10 minutes)\n\n**Required before production:**\n\n- **Create tenants for each Stytch Organization** — Descope Tenants must exist before\ntenant-scoped code (SSO, roles, membership) will work.\n- **Create roles** (or whatever the codebase references) — Descope roles must exist in the\nconsole before code that assigns them will work.\n- **Configure SSO connections per tenant** (or enable the SSO Setup Suite for self-serve) — SAML/OIDC connections need to be recreated.\n- **Configure social login providers** (Google, GitHub, etc.) — OAuth credentials for\neach 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\n| Remove | Add | Why |\n| ----------------------------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |\n| `STYTCH_PROJECT_ID` | `DESCOPE_PROJECT_ID` | Your unique Stytch project ID. Descope uses a Project ID for the same purpose. |\n| `STYTCH_SECRET` | `DESCOPE_MANAGEMENT_KEY` | Backend secret used to securely authenticate Stytch API requests. Descope session validation uses only the Project ID; a Management Key is needed only for server-side user/tenant/SSO/SCIM administration. |\n| `NEXT_PUBLIC_STYTCH_PUBLIC_TOKEN` | `NEXT_PUBLIC_DESCOPE_PROJECT_ID` | Stytch's frontend-facing token for initializing client SDKs. Descope uses the same value as `DESCOPE_PROJECT_ID`, exposed to the browser for the login Flow component (Next.js and other frontend frameworks). |\n| Connected Apps `client_id` (e.g. `STYTCH_CONNECTED_APP_CLIENT_ID` or similar) | `DESCOPE_INBOUND_APP_CLIENT_ID` | Only if the app uses Stytch Connected Apps as an OAuth/OIDC client — replace with the Descope **Inbound App** client ID (Console → Inbound Apps). First-party Connected Apps clients map to **Federated Apps** instead and do not use this variable. For confidential Inbound Apps, also add `DESCOPE_INBOUND_APP_CLIENT_SECRET`. |\n\n\nFollow with: \"Net change: 2-3 variables removed, 1–4 added (Connected Apps env vars only if applicable). No secrets need to be rotated\non the Stytch side — those credentials stop being used.\"\n\n---\n\n#### User & Organization Migration (only if existing users/orgs need to be migrated)\n\nProse strategy first, then steps. Start with: \"X existing users across Y organizations need to be in Descope before cutover.\" Describe:\n\n- **The plan**: whether this is big-bang (all users/orgs moved before cutover) or phased, and why\n- **Org→tenant mapping**: each Stytch Organization becomes a Descope Tenant; \n- **What users will experience**: will they need to log in again? Will anything look different?\n- **The biggest dependency**: how password credentials carry over, and whether SCIM directories must be re-pointed at Descope (a continuing pipeline, not a one-time import)\n\nEnd with a brief checklist of the migration steps at the level a PM can track:\n\n- Export users and organizations from Stytch\n- Map each Stytch Organization to a Descope Tenant\n- Re-point SCIM\\ at Descope (if Stytch SCIM is in use)\n- Do a dry run of the import 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: Organization-to-tenant mapping affects almost every B2B feature**\n> Stytch Organizations should usually map to Descope Tenants. If this mapping is wrong, SSO, SCIM,\n> roles, permissions, domain routing, and user membership checks may all break.\n> **Action:** Confirm the organization model before writing migration code.\n\n> **Consideration: SCIM is a lifecycle system, not just a user import**\n> Stytch's SCIM may create, update, suspend, and delete users or group memberships continuously.\n> A one-time import is not enough if enterprise directories keep syncing after cutover.\n> **Action:** Identify every SCIM workflow and re-point it at Descope before cutover.\n\n> **Consideration: Admin Portal UI should not automatically become custom code**\n> If the app uses the Stytch Admin Portal UI, the Descope equivalent may be the SSO Setup Suite or a\n> Widget rather than a custom settings page.\n> **Action:** Ask whether tenant admins currently self-configure SSO/SCIM/domain verification.\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 UI that\n> displays user information will show blank values after migration until the token template is set\n> up in the Descope console. This is a one-time configuration step, not a code change.\n> **Action:** Configure the token template before running any tests. Estimated time: 10 minutes.\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–60 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- Configure Approved Domains (domain only — e.g. `localhost:3000`, not `http://localhost:3000/authenticate`)\n- Create authentication flow (use the built-in `sign-up-or-in` to start)\n- Configure user profile token template\n- Create tenants for each Stytch Organization (list actual orgs found)\n- Create roles: (list actual roles found)\n- Configure SSO connections per tenant or enable the SSO Setup Suite (if SSO in use)\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- Update environment variables in `.env.example` and CI config (15 min)\n- Rewrite session helper / `withAuth()` usage (30 min)\n- Swap AuthKit provider/middleware for Descope equivalents (15 min)\n- Update protected route files to use new session check (45 min)\n- Repoint org handling to tenant IDs — management calls pass a `tenantId`; request-time session reads use `tenants`/`dct` (varies)\n- Update logout — two-step logout (15 min)\n- Compile check and fix any type errors before proceeding\n\n**Phase 3 — User & Organization 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 & organization migration section above)\n\n**Phase 4 — Testing** (~30–45 minutes)\n\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- Tenant/SSO routing works for at least one organization\n- Logout invalidates session\n\n**Phase 5 — Production Cutover**\n\n- (cutover-specific steps based on their strategy — maintenance window, phased rollout, SCIM re-point, etc.)\n\n---\n\nTotal estimated engineering effort: **N–N hours** across N engineers.\nBlocking dependencies: (list anything on the critical path — console access, SCIM re-point, 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` Execution Plan 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 goal: [Full cutover / Phased / Evaluating]\n\n## Triage Answers\n- Existing users: [Yes — N users / No — greenfield]\n- Existing organizations: [Yes — N orgs → tenants / No]\n- Password migration needed: [Yes / No]\n- Stytch 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| `app/callback/route.ts` | Delete/rewrite | ⬜ 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- [ ] Approved Domains configured (domain only — e.g. `localhost:3000`, not `http://localhost:3000/authenticate`)\n- [ ] JWT template configured\n- [ ] Tenants created for each Stytch Organization: (list)\n- [ ] Roles created: (list roles)\n- [ ] SSO connections / SSO Setup Suite configured: (list)\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 · 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 Descope MCP server 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, hook return shapes (`useDescope()` returns the SDK directly, not `{ sdk }`),\nand 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\n```bash\ngrep -r \"from '@stytch/\\|from 'stytch'\\|from \\\"stytch\\\"\" --include=\"*.ts\" --include=\"*.tsx\" --include=\"*.js\" --include=\"*.jsx\" .\n```\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. Stytch'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 Stytch's version number or rely on training data for versions. Before writing any\ninstall command:\n\n```bash\nnpm view @descope/node-sdk version\nnpm view @descope/nextjs-sdk version\n```\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–8 — these are easy to miss even for existing projects.\n\n### 1. Create a project and get your Project ID\n\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 → General**. 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)\n\nRequired for: user management API, role/permission management, tenant operations, SSO/SCIM\nconfiguration, ReBAC (FGA), Outbound Apps. If the app does any server-side user, tenant, SSO,\nor SCIM management, they need this.\n\n- Console → **Company → Management Keys → + Management Key**\n- Store as `DESCOPE_MANAGEMENT_KEY`. Treat like a secret — never expose client-side.\n\n### 3. Choose or create a Flow\n\nA Flow is the auth UI sequence. Reference it by Flow ID in the web component.\n\n- Console → **Flows**\n- The built-in **\"sign-up-or-in\"** flow handles email/password, OTP, and social login.\nUse 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. For factor-deletion SDK support by type, see `references/implementation-nuances.md` → MFA section.\n\n### 4. Configure authentication methods\n\n- Console → **Authentication** → select methods (Email OTP, Magic Link, Social, SSO, Passkeys, etc.)\n- For social providers (Google, GitHub, etc.): configure OAuth credentials here, then add\nthe provider step to your Flow.\n- For enterprise SSO (SAML/OIDC): To configure SSO for a specific tenant or to enable the SSO Setup Suite for tenant-admin self-serve, go to Console → **Tenants**, select the desired tenant, and click **Tenant Settings**. 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 Approved Domains (local dev and production)\n\nConsole → **Project Settings → Security → Approved Domains**.\n\nDescope validates redirect URLs against this domain list — **not** full redirect URIs like Stytch.\nEnter **domain only**: no `http://`/`https://`, no path.\n\n- Local dev: `localhost:3000` (include port)\n- Production: `myapp.com` or `app.myapp.com`\n\n**Do not** carry over Stytch callback URLs like `http://localhost:3000/authenticate`. Descope\nembedded Flows complete auth client-side; there is no `/authenticate` route to whitelist. See\n`references/implementation-nuances.md` → Approved Domains gotcha.\n\n### 6. Configure a JWT Template (almost always needed)\n\nStytch tokens may include profile fields; Descope tokens do not by default.\n\n- Console → **Project → JWT Templates**\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`\nwill get `undefined` after migration.\n\n### 7. Create roles in the Console (if using RBAC)\n\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\n- Console → **Authorization → RBAC → + Role**\n- Create each role the app references (e.g. `admin`, `member`)\n\n### 8. Define custom attributes (if using Stytch metadata)\n\nStytch metadata maps to Descope customAttributes, but the models are slightly different. Stytch\nstores arbitrary JSON in metadata fields, while Descope custom attributes should be pre-defined in the\nConsole schema before setting them via the SDK.\n\nTenant custom attributes: map Stytch Organization trusted_metadata to Descope tenant\ncustomAttributes. Configure these in Console → Tenants → Custom Attributes tab → Create\nAttribute. \nUser custom attributes: map Stytch Consumer User trusted_metadata and safe B2B Member\ntrusted_metadata to Descope user custom attributes. Configure these in Console → Project →\nCustom Attributes.\n\n### 9. Env var summary\n\n\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, SSO/SCIM, Outbound Apps API |\n\n\n### 10. Consider Widgets for management UI\n\nBefore migrating custom profile pages, user management pages, role assignment UI, or admin\nSSO/SCIM setup pages, ask whether a Descope Widget or the SSO Setup Suite covers the use case.\nSee `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\nStytch publishes three SDK families:\n\n- **Backend SDKs** (one per language): `stytch-node` (`stytch`), `stytch-python` (`stytch`), `stytch-go`, `stytch-ruby`, `stytch-java` (Java/Kotlin/JVM). These call the Stytch API and authenticate sessions server-side.\n- **Frontend SDKs** (per JS framework): `@stytch/react`, `@stytch/nextjs`, `@stytch/vanilla-js`. These render the login UI (Stytch UI or headless) and hold the client session.\n- **Mobile SDKs**: `@stytch/react-native`, the iOS Swift SDK, and the Android Consumer SDK (a headless Kotlin Multiplatform library targeting Android).\n\nThe recipes below map each Stytch SDK to its Descope target — one section each. A Stytch app on a server framework not listed (Express, Flask, FastAPI, Rails, Spring) is using the underlying language Backend SDK (`stytch-node`, `stytch-python`, etc.), so map it via that SDK's section.\n\n> The framework recipes below are stubs listing the Stytch idioms that need mapping. Confirm the exact Stytch SDK surface for the user's stack and the matching Descope SDK calls via the Descope MCP or local type declarations before generating any code. Do not ship code from these stubs without verification.\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\nWhen a new framework is added to the file, add it to this list.\n\n### Common Stytch idioms to map (all frameworks)\n\nFrontend Stytch SDKs expose UI components and client session hooks; backend SDKs authenticate the\nsession server-side. The mappings below apply across stacks:\n\n- Stytch UI (`<StytchLogin>` / `<StytchB2B>`) or headless `useStytch()` login → embedded Descope Flow (`<Descope flowId>` / `<descope-wc>`) or hosted Flow, wiring `onSuccess`\n- `useStytchSession()` / `useStytchUser()` (client session access) → Descope `useSession()` / `useUser()` hooks, with `useDescope()` for actions\n- Backend `client.sessions.authenticate()` / `client.sessions.authenticateJwt()` → Descope backend `validateSession()` + an adapter returning the shape callers expect\n- Stytch session cookies (`stytch_session`, `stytch_session_jwt`) → Descope signed session JWT in `DS` / `DSR` cookies\n- Stytch magic-link / OAuth callback route (`client.magicLinks.authenticate()` / `client.oauth.authenticate()`) → removed/rewritten; Descope completes auth client-side\n\n### Backend SDKs\n\n#### Node.js\n\n*Stytch SDK: `stytch` (`stytch-node`) → Descope `@descope/node-sdk`*\n\n- Remove `stytch` auth/session usage; add `@descope/node-sdk`\n- Replace `client.sessions.authenticateJwt()` / `client.sessions.authenticate()` with custom middleware calling `descopeClient.validateSession(sessionToken)` against the `DS` cookie (parse the cookie yourself)\n\n#### Python\n\n*Stytch SDK: `stytch` (`stytch-python`) → Descope `descope` Python SDK*\n\n- Remove the Stytch Python SDK auth/session usage; add the `descope` Python SDK\n- Validate the `DS` session token with `descope_client.validate_session(session_token=session_token)` (or validate against Descope's JWKS for a custom authorizer)\n\n#### Go\n\n*Stytch SDK: `stytch-go` → Descope Go SDK `github.com/descope/go-sdk`*\n\n- Remove the Stytch Go SDK; add `descope/go-sdk`\n- Session validation: `descopeClient.Auth.ValidateSessionWithToken(ctx, token)` returns `(bool, *descope.Token, error)`\n- Stytch `organization_id` → a Descope **tenant ID**: pass it to management calls (`descopeClient.Management.Tenant()` / user-tenant association); at request time read tenant context off the returned `*descope.Token` (`token.GetTenants()`, or the `dct` claim for the active tenant)\n\n#### Ruby\n\n*Stytch SDK: `stytch-ruby` → Descope Ruby SDK*\n\n- Remove the Stytch Ruby SDK; add the Descope Ruby SDK\n- Validate the `DS` session token with `descope_client.validate_session(session_token: session_token)` in your request lifecycle\n- No dedicated recipe in `implementation-nuances.md` yet — follow the Node.js / Python backend patterns and verify against the [Descope Ruby SDK](https://github.com/descope/descope-ruby-sdk).\n\n#### Java / Kotlin (JVM)\n\n*Stytch SDK: `stytch-java` (Java/Kotlin/JVM) → Descope `descope-java`*\n\n- Remove the Stytch Java SDK; add `descope-java`\n- Validate the `DS` token via a filter/interceptor: `authenticationService.validateSessionWithToken(sessionToken)` returns a `Token`\n- No dedicated recipe yet — follow the backend patterns and verify against the [Descope Java SDK](https://github.com/descope/descope-java).\n\n\n\n### Frontend SDKs\n\n> **Read the session the framework-native way — never hand-parse the JWT on the client.** On\n> front-end pages and components, get auth state from the Descope hooks: `useSession()` for the\n> session token and auth status, `useUser()` for the user profile, and `useDescope()` for actions\n> like `logout()`. Do **not** manually decode the session token or pull claims out of it in client\n> code. Server-side session *validation* — `session()` in `@descope/nextjs-sdk/server`,\n> `validateSession()` in `@descope/node-sdk` (or the other backend SDKs) — belongs only in backend\n> routes, middleware, and API handlers, never in a rendered client component. This matters\n> most with the **React SDK**, where it's tempting to crack open the raw token in a component instead\n> of calling `useUser()` / `useSession()`.\n\n#### Vanilla JS\n\n*Stytch SDK: `@stytch/vanilla-js` → Descope `@descope/web-js-sdk` + `@descope/web-component`*\n\n- Stytch headless client (`createStytchUIClient` / `StytchHeadlessClient`) session access → `@descope/web-js-sdk` (`getSessionToken()`, `isJwtExpired()`, `refresh()`)\n- Stytch UI login → `<descope-wc project-id flow-id>` web component, listening for `success` / `error` events\n- Logout: `sdk.logout()` + clear stored tokens/cookies\n\n#### React\n\n*Stytch SDK: `@stytch/react` → Descope `@descope/react-sdk`*\n\n- `<StytchProvider>` → Descope `<AuthProvider projectId>`\n- Stytch UI (`<StytchLogin>`) / headless `useStytch()` login → embedded `<Descope flowId>` component, wiring `onSuccess`\n- `useStytchSession()` / `useStytchUser()` → Descope `useSession()` + `useUser()` hooks, with `useDescope()` for actions\n- **Always read auth state through the hooks** — never decode the session token by hand in a component, and never call backend `validateSession()` from client code; that runs only on the server.\n- Logout: `sdk.logout()` via `useDescope()` hook\n- No dedicated recipe yet — follow the Next.js client-side patterns and verify each method against docs.\n\n#### Next.js\n\n*Stytch SDK: `@stytch/nextjs` → Descope `@descope/nextjs-sdk` + `@descope/node-sdk`*\n\n- `@stytch/nextjs` → `@descope/nextjs-sdk` + `@descope/node-sdk`\n- `<StytchProvider>` → Descope `AuthProvider` (takes `projectId`; must use `NEXT_PUBLIC_` prefix)\n- Server `client.sessions.authenticateJwt()` → `session()` (server); client `useStytchSession()` / `useStytchUser()` → `useSession()` / `useUser()`\n- Remove the Stytch magic-link / OAuth callback route — verify Descope's client-side handling\n- Stytch session middleware → Descope `authMiddleware(options)`\n- Logout: `sdk.logout()` via `useDescope()` hook + clear cookies (two-step)\n- **Client vs. server session access** — `session()` from `@descope/nextjs-sdk/server` is server-only; `useSession()`/`useUser()` from `@descope/nextjs-sdk/client` are client-only. Using `session()` in a client component compiles but throws at runtime. Verify exact exports before writing imports.\n\n### Mobile SDKs\n\n#### React Native\n\n*Stytch SDK: `@stytch/react-native` → Descope `@descope/react-native-sdk`*\n\n- Stytch UI / headless client login → run a Descope Flow via the React Native SDK (or hosted Flow)\n- Stytch session access/storage → Descope React Native SDK session management\n- Logout: Descope SDK logout + clear the stored session\n- No dedicated recipe yet — verify methods against the [Descope React Native SDK](https://github.com/descope/descope-react-native-sdk).\n\n#### iOS (Swift)\n\n*Stytch iOS Swift SDK → Descope `descope-swift`*\n\n- Stytch login (UI or headless) → run a Descope Flow via the Swift SDK, or hosted Flow\n- Stytch session validation/refresh → Descope Swift SDK session APIs\n- No dedicated recipe yet — verify against the [Descope Swift SDK](https://github.com/descope/swift-sdk).\n\n#### Android (Kotlin)\n\n*Stytch Android Consumer SDK (headless Kotlin Multiplatform) → Descope `descope-kotlin`*\n\n- Headless Stytch client login → run a Descope Flow via the Android/Kotlin SDK, or hosted Flow\n- Stytch session validation/refresh → Descope Kotlin SDK session APIs\n- No dedicated recipe yet — verify against the [Descope Android/Kotlin SDK](https://github.com/descope/descope-kotlin).\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 Stytch references in non-code files after updating source files.\n\n### `.env.example` / `.env.template` / `.env.sample`\n\n```\n# REMOVE\nSTYTCH_PROJECT_ID=\nSTYTCH_SECRET=\nSTYTCH_PUBLIC_TOKEN=\nNEXT_PUBLIC_STYTCH_PUBLIC_TOKEN=\n\n# ADD\nDESCOPE_PROJECT_ID= # Console → Project Settings\nNEXT_PUBLIC_DESCOPE_PROJECT_ID= # Next.js / frontend — same value as above\nDESCOPE_MANAGEMENT_KEY= # Console → Company → Management Keys (replaces STYTCH_SECRET for admin APIs)\n```\n\nRun `grep -ir \"STYTCH\"` to find all env var references — `.env.example`, Docker, CI, shell scripts.\n\n### README / docs\n\nSearch all `.md` files for Stytch references. At minimum, update:\n\n- **Setup section** — replace \"create a Stytch app\" instructions with Descope Console setup steps\n- **Environment variables section** — reflect the reduced env var set\n- **Run instructions** — replace Stytch dashboard steps with Descope Console steps\n- **Auth flow diagrams or descriptions** — update to reflect Descope's cookie-based approach\n\n### Docker / CI files\n\nCheck `Dockerfile`, `docker-compose.yml`, `.github/workflows/`, and any CI config for\n`STYTCH_`* env var declarations. Update them to `DESCOPE_`*.\n\n### Setup / bootstrap scripts\n\nWhen 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, SSO Setup Suite — 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, SSO/SCIM config. Preserve these as a Node.js/Python script using the Descope Management SDK.\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\nFor each Stytch feature confirmed in triage, write a short paragraph: what it accomplishes, the\nbest Descope approach for that goal, what's different, and what action is required. Reason about\nintent, not just the API surface — the best approach may be a Flow, Widget, SSO Setup Suite, Inbound\nApp, Console configuration, or tenant configuration rather than a direct SDK equivalent.\nOnly recommend SDK/API code when programmatic control is genuinely required, and verify every method\nname against the Descope MCP server before writing it. Include only confirmed features.\n\n### Consumer Authentication → Descope Flows + Auth Methods + JWT Templates\n\nStytch Consumer Auth handles B2C sign-in — hosted/prebuilt UI, frontend SDK flows, backend API\nflows, users, sessions, and methods (OAuth/social, magic links, OTP, passwords, passkeys/WebAuthn,\nmobile biometrics, MFA/TOTP, crypto wallet). Descope maps these to **Flows**, authentication methods,\nUsers, session validation, and **JWT Templates** / custom claims.\n\n| Stytch | Descope |\n| --------------------------------------- | -------------------------------------------------------------------- |\n| Stytch UI / prebuilt login UI | [Descope Flows](https://docs.descope.com/flows) |\n| Frontend SDK auth flows | Descope frontend SDK + Flow component |\n| Backend API-driven auth | Descope backend SDK / API auth methods when Flows are not sufficient |\n| OAuth/social login | Descope OAuth/social login methods |\n| Email magic links | Descope Magic Link / Enchanted Link |\n| Email/SMS/WhatsApp OTP | Descope OTP methods |\n| Passwords | Descope Passwords |\n| Passkeys / WebAuthn | Descope Passkeys |\n| TOTP / MFA | Descope MFA / TOTP / Flow conditions |\n| Stytch User object | Descope User |\n| Stytch session token / session JWT | Descope session token / JWT + backend session validation |\n| Stytch custom claims / session metadata | Descope JWT Templates or Custom Claims action |\n\nPrefer Flows for the user journey; use custom SDK/API calls only when Flows cannot express the\nrequirement. Confirm which Stytch methods are enabled, whether Stytch or custom UI is used, and\nwhether backend routes call Stytch APIs directly. **Effort: Low–Medium** for straightforward B2C\nauth; higher with custom session claims, MFA branching, or nonstandard factors.\n\n### Multi-tenant / B2B Authentication → Descope Tenants + Users\n\nStytch B2B authentication is built around **Organizations** and **Members**. Descope maps this model\nmost closely to **Tenants** and **Users associated with tenants**. A Stytch Organization usually\nbecomes a Descope Tenant, while a Stytch Member usually becomes a Descope User with tenant membership,\nroles, permissions, and tenant-specific attributes.\n\n| Stytch | Descope |\n| --------------------------------------------- | -------------------------------------------------------------- |\n| Organization | Tenant |\n| Member | User associated with a tenant |\n| Organization ID | Tenant ID |\n| Organization metadata | Tenant `customAttributes` |\n| Member metadata | User custom attributes or tenant-specific user metadata |\n| Organization-specific auth settings | Tenant settings + Flow logic + SSO configuration |\n| Member invitations | Invitation Flow / management SDK flow |\n| Organization discovery | Tenant discovery / tenant selection / domain-based routing |\n| Org-specific login | Tenant-specific login route, tenant slug, or tenant Flow input |\n| Organization session exchange / org switching | Active tenant selection and tenant-aware session claims |\n| Members belonging to multiple Organizations | Users belonging to multiple tenants |\n\nConfirm the one-Stytch-Organization-to-one-Descope-Tenant mapping before writing code. This mapping\nripples into SSO, SCIM, RBAC, JIT provisioning, sessions, custom claims, and domain routing. Also\ncheck whether the application treats `organization_id` as an authorization boundary, a billing\nboundary, a data partition key, or all three. **Effort: Medium** — conceptually clean, but application\ncode often assumes Stytch's Organization/Member object shapes.\n\n### Organizations and Members → Descope Tenant and Users\n\nStytch Organizations and Members are not just data objects; they may drive onboarding, invitations,\nmembership updates, deactivation, organization switching, metadata, and tenant-specific access\ncontrols. In Descope, model these workflows using Tenants, Users, tenant membership, roles,\npermissions, and optionally Flows or management SDK calls for lifecycle operations.\n\n| Stytch | Descope |\n| --------------------------------- | --------------------------------------------------------- |\n| Create/update Organization | Create/update Tenant |\n| Create/update Member | Create/update User and tenant association |\n| Organization metadata | Tenant custom attributes |\n| Member metadata | User custom attributes / tenant-specific user attributes |\n| Member invite | Invite/onboarding Flow or management SDK |\n| Member deactivate/delete | User deactivation, tenant removal, or tenant-role removal |\n| Organization allowed auth methods | Tenant settings + Flow conditions |\n| Organization-specific MFA policy | Tenant-aware MFA logic in Flows |\n\nAsk whether Organization and Member data is synchronized into the app database, whether the app reads\nStytch as the source of truth, and whether lifecycle changes trigger webhooks. **Effort: Medium** —\nespecially if membership state is mirrored in the application database.\n\n### Enterprise SSO → Descope Tenant SSO\n\nStytch Enterprise SSO maps to Descope tenant-level SSO. In Stytch, SSO connections are associated\nwith Organizations. In Descope, SSO is configured per Tenant, with support for SAML/OIDC providers,\ndomain-based routing, SSO Setup Suite, and multiple SSO providers per tenant when needed.\n\n**Preferred approach — SSO Setup Suite:** before migrating any Stytch SSO management code, ask whether\nthe no-code SSO Setup Suite removes the need for that code. It guides tenant admins through per-tenant\nSAML/OIDC setup with IdP-specific instructions (Okta, Microsoft Entra ID, Google Workspace, etc.) and can reduce engineering involvement for new\nenterprise customer onboarding.\n\n**Multiple SSO configurations per tenant.** If a single Stytch customer has multiple SSO connections,\nor if the old Stytch model used multiple Organizations to represent one customer with multiple IdPs,\ndo not blindly create multiple Descope Tenants. First decide whether the customer should become one\nDescope Tenant with multiple SSO configurations.\n\n| Stytch | Descope |\n| -------------------------------------- | --------------------------------------------------------- |\n| Organization SSO connection | Tenant SSO configuration |\n| SAML SSO | Descope SAML SSO |\n| OIDC SSO | Descope OIDC SSO |\n| Organization-specific SSO routing | Tenant routing / SSO domain routing |\n| Multi-Organization SSO behavior | Tenant design + active tenant/session model review |\n| Customer-admin SSO setup | SSO Setup Suite |\n| SSO claim/group role assignment | SSO attribute mapping / group-to-role mapping |\n| Programmatic SSO connection management | Descope Management API / SDK, if self-service is not used |\n\nUse `AskUserQuestion` to ask **two** things here:\n\n1. Does any single customer use **multiple IdPs** or multiple Stytch Organizations to represent the\n same real-world customer?\n2. Does the app need **programmatic** SSO configuration, or do customer admins configure SSO\n themselves?\n\nFor runtime login, prefer Descope's SSO-specific login path rather than generic social OAuth logic.\nThe exact SDK method names differ by language/framework, so verify against the Descope MCP server\nbefore writing implementation code. Rule of thumb: tenant/enterprise SSO should use Descope's\ntenant-level SSO configuration; social login should use OAuth/social auth methods. **Effort: Medium**\n\n### SCIM → Descope SCIM / Tenant Provisioning\n\nStytch SCIM maps to Descope SCIM provisioning. **Treat this as a continuing\nprovisioning pipeline, not a one-time import** — enterprise directories keep pushing create, update,\ngroup, and deprovisioning events after cutover.\n\n| Stytch SCIM | Descope |\n| ---------------------------------------- | ------------------------------------------------------ |\n| SCIM endpoint per Organization | Descope SCIM endpoint / token per tenant |\n| User create/update/deactivate | Tenant user provisioning lifecycle |\n| Groups | External groups / group-to-role mapping |\n| SCIM group-to-role assignment | SCIM or SSO group mapping to Descope roles |\n| Deprovisioning | User deactivation / tenant access removal behavior |\n| SCIM tokens | Tenant-scoped SCIM-compatible access keys |\n| SCIM webhooks / downstream sync handlers | Descope events, audit logs, webhooks, or app sync code |\n\nIdentify every connected directory, which IdPs are used, whether groups are synced, whether groups map\nto roles, and what happens when a user is removed from a group. Pay special attention to\nwhether Stytch deprovisioning revoked sessions immediately, removed membership, changed roles, or only\nupdated status. **Effort: Medium–High** — lifecycle, groups, deprovisioning, and role mapping can be\nsubtle.\n\n### Admin Portal → Descope SSO Setup Suite / Admin Widgets\n\nStytch Admin Portal provides customer-admin workflows for managing enterprise configuration such as\nSSO, SCIM, organization settings, members, and related admin tasks. Do not default to rebuilding these\nscreens as custom code.\n\n| Stytch Admin Portal area | Descope replacement |\n| ------------------------ | ------------------- |\n| `AdminPortalMemberManagement` | User Management Widget |\n| Member search/update/invite | User Management Widget or Management SDK/API |\n| Member role assignment | User Management Widget; Role Management Widget if tenant admins manage roles |\n| `AdminPortalOrgSettings` | Tenant Profile Widget for tenant name, custom attributes, domains, and SSO enforcement |\n| Organization auth method/JIT settings | Flow logic, tenant settings/custom attributes, or custom Management SDK/API UI |\n| `AdminPortalSSO` | SSO Setup Suite |\n| `AdminPortalSCIM` | SSO Setup Suite SCIM configuration |\n| Custom member management UI | Prefer User Management Widget; otherwise Management SDK/API |\n| Custom organization management UI | Prefer Tenant Profile Widget; otherwise Management SDK/API |\n\nAsk which Stytch Admin Portal workflows are actually used today. If a Descope Widget or SSO Setup\nSuite covers the workflow, prefer that over custom migration code. **Effort: Medium** — may remove\ncustom code, but generated portal-link workflows need replacement.\n\n### RBAC → Descope RBAC\n\nStytch RBAC combines Resources, Actions, Permissions, and Roles — a Permission is a `resource_id` +\n`action` pair (e.g. `documents:read`, `employees:update`), grouped into Roles assigned to Members. Stytch evaluates via\nfrontend SDK resource/action checks or backend session/JWT calls with `organization_id`,\n`resource_id`, and `action`. Descope has Roles and Permissions too, but permissions are strings, not\nfirst-class Resource + Action objects — encode each Stytch pair as a consistent permission string\n(`resource.action` or `resource:action`).\n\nDescope supports project- and tenant-level roles and permissions. Stytch defines its RBAC Policy once\nat the project level (shared role/resource catalog); roles are assigned per Organization with no\nper-org policy divergence. Stytch's only org-scoped feature is implicit assignment (auto-grant a\nproject role by email domain) — tenant-specific *assignment*, not *definition*. Default migration:\nmap Stytch role definitions to Descope project-level roles, then assign users in the relevant tenant.\n\n| Stytch | Descope |\n| --- | --- |\n| Resource | Encoded in permission string |\n| Action | Encoded in permission string |\n| Permission = Resource + Action | Permission |\n| Role | Role |\n| Project-level RBAC policy | Project-level role/permission catalog |\n| Role definition in RBAC policy | Usually project-level role |\n| Member role assignment inside an Organization | User role assignment in a tenant |\n| Same user has different roles in different Organizations | Same user has different roles in different tenants |\n| Tenant-specific/custom role catalog | Use Descope tenant-level roles only if this behavior actually exists in the app |\n\nConfirm whether roles gate UI only or backend auth too; whether roles/permissions appear in tokens;\nwhether the app stores assignments locally; and whether SSO/SCIM mappings are source of truth.\n**Effort: Medium** for normal RBAC; higher if mixed with Connected Apps scopes or app-defined resource authorization.\n\n### Authorization Beyond RBAC\n\nIf the Stytch app has authorization **beyond RBAC** — relationship-based or per-resource checks such\nas project membership, document ownership, workspace hierarchy, or shared/delegated access — do not\nassume a plain RBAC migration covers it. This maps to Descope ReBAC/FGA (only when the model truly\ndepends on relationships between entities) or stays in the application database.\n\nSee `references/implementation-nuances.md` → **Authorization beyond RBAC → Descope ReBAC** for the\ndecision guide, an example schema, the recommended-approach table, and effort estimate.\n\n### JIT Provisioning → Descope JIT Provisioning / Tenant Association\n\nStytch JIT auto-adds Members to Organizations from auth context — main paths: email-domain JIT,\nSSO Connection JIT, and OAuth-tenant JIT. Trusted Auth Tokens have a separate JIT option that can\ncreate Members or Organizations from external JWTs. Invitations are a distinct onboarding path, not\nJIT.\n\nDescope supports tenant association, self-provisioning domains, domain-based SSO routing, SSO-driven\nJIT, SCIM, and Flow-based tenant/user logic. Preserve whichever Stytch provisioning model is\nconfigured; do not assume the customer picks only one.\n\n**Important:** for each tenant, identify the source of truth for membership and role assignment —\nJIT, SCIM, invitations, manual admin membership, Trusted Auth Tokens, or app-side onboarding. SCIM\nand JIT can coexist, but mixed sources without clear precedence cause duplicate accounts, unexpected\ntenant access, missed deprovisioning, or role confusion. Confirm which paths are enabled per\nOrganization in Stytch.\n\n| Stytch | Descope |\n| --- | --- |\n| Email-domain JIT provisioning | Tenant self-provisioning domains / Flow logic |\n| `email_allowed_domains` | Tenant domains / self-provisioning domains |\n| SSO Connection JIT provisioning | SSO JIT provisioning / tenant association |\n| `sso_jit_provisioning` | Tenant SSO provisioning behavior |\n| OAuth-tenant JIT provisioning | Custom Flow / Connector / app-side tenant logic |\n| Allowed GitHub, Slack, or HubSpot tenants | Custom tenant association logic if still required |\n| Member created on first login | User associated with tenant during login |\n| JIT role assignment from SSO claims | SSO group/attribute mapping to roles |\n| Trusted Auth Token JIT | JWT Bearer |\n| Email invitations | Separate invite/admin onboarding flow, not JIT |\n| JIT plus SCIM | Preserve both if both are configured |\n\n**Effort: Medium** — low for email-domain JIT only; higher with SSO/SCIM role assignment, Trusted\nAuth Tokens, OAuth-tenant membership, or custom onboarding.\n\n### MFA and Step-up Authentication → Descope MFA / Flow Conditions\n\nStytch MFA and step-up authentication can involve OTPs, TOTP, passkeys/WebAuthn, passwords, OAuth,\nmagic links, and organization-specific MFA requirements. Descope maps this to MFA methods and\nconditional Flow logic.\n\nUse Flows for MFA whenever possible because MFA is usually part of the user journey, not just a backend\nAPI call. Flow conditions can branch based on user state, tenant context, risk signals, completed auth\nmethods, or sensitive actions.\n\n| Stytch | Descope |\n| ---------------------------------- | -------------------------------------------------- |\n| SMS/email OTP MFA | Descope OTP MFA |\n| TOTP MFA | Descope Authenticator Apps / TOTP |\n| Passkey/WebAuthn as MFA or step-up | Descope Passkeys / WebAuthn |\n| Organization-specific MFA policy | Tenant-aware Flow condition |\n| Step-up for sensitive actions | Step-up Flow or backend-triggered reauth pattern |\n| Risk-based MFA | Flow condition using risk signals / fingerprinting |\n| Recovery codes / fallback behavior | Confirm support and design fallback explicitly |\n\nAsk whether MFA is required globally, per organization, per role, per risk level, or only for sensitive\nactions. **Effort: Low–Medium** unless MFA is deeply customized or risk-based.\n\n### Sessions and Tokens → Descope Session Management + JWT Templates\n\nStytch sessions may use `session_token`, `session_jwt`, intermediate sessions, cookies, custom claims,\norganization context, and session revocation. Descope sessions should be validated with the appropriate\nbackend SDK/session validation path, and claims should be shaped with JWT Templates or Flow Custom\nClaims where appropriate.\n\n| Stytch | Descope |\n| ------------------------------- | ------------------------------------------------------- |\n| `session_token` | Descope session token |\n| `session_jwt` | Descope JWT |\n| Intermediate sessions | Flow-driven intermediate state / MFA / step-up handling |\n| Organization context in session | Tenant claims / active tenant context |\n| Custom claims | JWT Templates or Custom Claims action |\n| Session revocation | Descope session/user logout or revocation pattern |\n| Cookie-based sessions | Descope SDK cookie/session configuration |\n| Backend session authentication | Descope backend session validation |\n\nSearch the codebase for direct reads of Stytch session fields, token claims, organization/session\nexchange calls, and middleware that assumes Stytch-specific token shapes. **Effort: Medium** — token\ndifferences often affect middleware, API routes, and frontend hydration.\n\n### Fraud & Risk / Device Fingerprinting → Descope Flow + Connectors\n\nSee `references/implementation-nuances.md` → **Attack protection: Stytch Fraud & Risk → Descope Flow-based security** for connector mappings (Arkose, reCAPTCHA, Fingerprint, Have I Been Pwned, AbuseIPDB), Flow branching guidance, and fraud/KYC connector docs. Ask whether Stytch verdicts are monitoring-only or actually gate login. **Effort: Medium–High** only when verdicts affect production login outcomes.\n\n### Connected Apps → Federated Apps + Inbound Apps\n\nStytch Connected Apps enables a Stytch-powered application to act as an OAuth/OIDC Authorization\nServer for first-party apps, third-party integrations, desktop apps, CLI tools, AI agents, MCP\nclients, and other clients that need scoped access to user data.\n\n**Do not map every Stytch Connected Apps client to Descope Inbound Apps.** Stytch distinguishes\nfirst-party from third-party clients; Descope splits the equivalent workloads across two\n[identity-federation](https://docs.descope.com/identity-federation) features:\n\n| Stytch Connected Apps client | Descope equivalent | Purpose |\n| ---------------------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **First-party client** | **Federated Apps** | SSO across apps you own — Descope acts as the IdP so users authenticate once and access multiple connected applications without signing in again to each |\n| **Third-party client** | **Inbound Apps** | OAuth/OIDC authorization server — external clients obtain scoped tokens to access your Resources, with consent and permission management |\n\n**Public vs. confidential applies only to Inbound Apps** (Stytch third-party clients). Confidential\nclients are server-side apps that can securely store a client secret; public clients (SPAs, mobile\napps, CLI tools) cannot store secrets and must use PKCE.\n\n#### First-party clients → Federated Apps\n\n| Stytch Connected Apps (first-party) | Descope Federated Apps |\n| ----------------------------------- | ----------------------------------------------------------- |\n| First-party OAuth/OIDC client | Federated App (SAML or OIDC SSO connection) |\n| Known first-party app | Federated App registration and callback URL |\n| SSO across owned applications | Descope as IdP; users sign in once across connected apps |\n| Session / ID token for owned apps | Federated App OIDC token or SSO session |\n\n#### Third-party clients → Inbound Apps\n\n| Stytch Connected Apps (third-party) | Descope Inbound Apps |\n| ----------------------------------- | --------------------------------------------------------- |\n| OAuth/OIDC Authorization Server | Inbound Apps authorization server (`/oauth2/v1/apps/*`) |\n| Public client + PKCE | Public Inbound App / PKCE-capable flow |\n| Confidential client | Confidential Inbound App with client secret |\n| Authorization Code flow | Inbound App Authorization Code flow |\n| Refresh tokens | Inbound App refresh token support (confidential clients use client secret; public clients do not) |\n| ID tokens | OIDC ID tokens |\n| Access tokens | Descope-issued scoped access tokens |\n| Consent screen | Inbound App consent / consent management |\n| Custom scopes | Resources and scopes |\n| RBAC-backed scopes | Role/scope/resource mapping review |\n| Token revocation | Inbound App token revocation |\n| Dynamic Client Registration | DCR / Agentic Identity Hub client registration, if needed |\n\nThis is a high-complexity migration if real external clients depend on the current Stytch issuer,\nJWKS, token claims, scopes, refresh token lifetimes, consent records, or callback URLs. Inventory every\nclient, redirect URI, grant type, scope, token audience, and resource server before writing code.\n**Effort: High** when third-party clients or AI agents are already in production.\n\n### AI Agent / MCP Authentication → Descope Agentic Identity Hub / MCP Servers / Inbound Apps\n\nStytch can use Connected Apps for AI agents, MCP clients, CLI tools, and agentic integrations that\nneed delegated OAuth/OIDC access. Descope has Agentic Identity Hub, MCP server configuration, Inbound\nApps, resources/scopes, client registration, and token issuance patterns for these use cases.\n\nDo not treat AI/MCP auth as a generic OAuth migration without review. Agentic flows often require\nclear resource scopes, dynamic client registration, token lifetimes, consent design, and\norganization-level controls.\n\n| Stytch AI / MCP pattern | Descope |\n| -------------------------------- | -------------------------------------------- |\n| Connected App for AI agent | Agentic Identity Hub client |\n| MCP client authorization | MCP Server authorization / Inbound App |\n| Dynamic Client Registration | DCR / CIMD / known client registration |\n| Agent scopes | Resource scopes / policies |\n| Agent consent | Inbound App consent |\n| CLI or desktop app client | Public client + PKCE |\n| Organization-level agent control | Tenant-aware policy / scope / consent design |\n\nAsk whether Stytch is acting as the OAuth provider for agents, whether the app exposes MCP tools, and\nwhether external agents already store refresh tokens. **Effort: Medium–High — flag for dedicated\nreview.**\n\n### Machine-to-Machine Authentication → Resources + Inbound Apps + Policies\n\nStytch M2M authentication uses M2M clients, client credentials, access tokens, scopes, custom\nclaims, and secret rotation for service-to-service authentication.\n\n**Default mapping:** For most Stytch M2M use cases, use **[Resources](https://docs.descope.com/identity-federation/resources) + [Inbound Apps](https://docs.descope.com/identity-federation/inbound-apps) + [Policies](https://docs.descope.com/identity-federation/policies)** — not Access Keys. Stytch defines scopes on the application itself; in Descope each protected API is a **Resource** (its identifier becomes the token `aud`) with an OAuth scope catalog, an **Inbound App** is the confidential OAuth client, and a **Policy** grants that client specific scopes on that Resource via the `client_credentials` grant (no consent screen).\n\n1. Create a **Resource** per protected API with scopes (optionally mapped to RBAC roles).\n2. Register a **confidential Inbound App** for each M2M service.\n3. Create a **Policy** allowing that client M2M access (`client_credentials`) to the Resource scopes it needs.\n\n| Stytch M2M | Descope (default) |\n| ------------------------- | ------------------------------------------------------ |\n| M2M client | Confidential Inbound App |\n| Client ID / client secret | Inbound App client ID + secret |\n| Client credentials flow | Inbound App `client_credentials` grant via Policy |\n| M2M scopes | Resource scopes granted by Policy |\n| Token audience | Resource identifier (`aud`) |\n| Custom claims | JWT Template on Inbound App |\n| Secret rotation | Inbound App client secret rotation |\n\nUse **[Access Keys](https://docs.descope.com/management/m2m-access-keys)** only when the service needs a Descope-issued JWT without OAuth scope or audience enforcement — a simpler internal service-auth pattern, not a scoped API access model.\n\nAsk which services use M2M credentials, which APIs they call, what scopes and audiences they enforce, and whether downstream APIs validate `scope` and `aud`. **Effort: Medium** — often straightforward with Resources + Policies, but production services require careful secret rotation and rollout.\n\n### Webhooks / Events / Event Logs → Descope Webhooks / Connectors / Audit Events\n\nStytch webhooks and event logs may be used to synchronize users, organizations, members, sessions,\nSCIM lifecycle events, fraud decisions, or Connected Apps consent/token events into the application.\nDescope can use audit events, webhook connectors, generic HTTP connectors, and audit/troubleshooting\nconnectors depending on the use case.\n\n| Stytch | Descope |\n| ---------------------------------- | ----------------------------------------------------- |\n| Webhook endpoint + signing secret | Descope webhook/HTTP connector + signature validation |\n| User events | Descope user/audit events |\n| Organization/Member events | Tenant/user events or app-side lifecycle sync |\n| SCIM lifecycle events | Descope SCIM provisioning events / audit events |\n| Fraud/Risk events | Flow branch + audit/webhook/logging connector |\n| Connected App consent/token events | Inbound App consent/token event review |\n| Event log streaming | Audit & Troubleshooting connectors |\n| Compliance logs | Audit Webhook Connector / log destination connector |\n\nSearch the codebase for Stytch webhook handlers and event-name switches. Update event names,\nsignature validation, payload parsing, retry behavior, and downstream side effects. Identify which\nevents are business-critical before cutover. **Effort: Medium.**\n\n### High-Complexity Stytch Areas to Flag Before Step 0.5\n\nAfter mapping confirmed Stytch features, summarize findings and flag high-complexity items before\nproceeding to Step 0.5. The main high-complexity Stytch areas are:\n\n* **SCIM** — lifecycle, group sync, deprovisioning, role mapping, IdP cutover.\n* **Enterprise SSO with JIT provisioning** — routing, tenant mapping, domain behavior, SSO claim\n mapping.\n* **RBAC tied to SSO or SCIM** — group-to-role mapping and token/permission enforcement.\n* **Authorization beyond RBAC** — possible ReBAC/FGA or app-side authorization model review.\n* **Fraud & Risk / Device Fingerprinting** — especially if verdicts block or challenge users.\n* **Protected Auth** — must be redesigned as Flow-based risk handling.\n* **Connected Apps** — OAuth/OIDC issuer, clients, scopes, consent, tokens, refresh tokens, resource\n servers.\n* **AI Agent / MCP Authentication** — scopes, DCR/CIMD, MCP server authorization, token lifetimes,\n tenant controls.\n* **Machine-to-Machine Authentication** — client credentials, access keys, secrets, rotation, scopes.\n* **Trusted Auth Tokens** — external issuers, JWKS, JWT bearer exchange, claim mapping, provisioning.\n* **Provider token storage / external account connections** — possible Outbound Apps migration.\n* **Webhooks/Event Streaming** — event names, payloads, signing, retries, downstream sync.\n* **Custom domains and OAuth/OIDC issuer URLs** — DNS, cookies, callbacks, token validation impact.\n\n\n## Step 4: Critical Gotchas (Always Cover These)\n\n### JWT Claims Are Not the Same\n\nDescope session JWTs contain `sub`, `amr`, `drn`, `tenants`, `roles`, `permissions`, and `dct` by\ndefault. They do **not** contain `email`, `name`, or `picture`. Stytch returns profile fields on the\n`member`/`user` object (and may carry them as custom claims in the `session_jwt`), so code that reads\nthose fields off the token or session response will break after migration.\n\n`dct` and `tenants` only matter when you read a user's tenant context **from their session at\nrequest time** — not for tenant administration, which is done by tenant ID through\n`management.tenant.*` / `management.user.*`. When you do read the session, `dct` (Descope Current\nTenant) is a flat string holding the active tenant ID — the direct equivalent of Stytch's\n`organization_id` — and `tenants` is a keyed object (`{ [tenantId]: { roles, permissions } }`) for\nper-tenant roles/permissions. Prefer the SDK's role/permission helpers (e.g.\n`validateTenantRoles(authInfo, tenantId, [...])`) over reading these claims by hand; reach for `dct`\nwhen you only need the active 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### Stytch Session Tokens Become a Single Descope Signed JWT\n\nStytch issues two session representations — an opaque `session_token` (validated by a network call\nto Stytch) and a `session_jwt` (a short-lived JWT validated locally), typically stored in the\n`stytch_session` and `stytch_session_jwt` cookies. Descope collapses this into one signed session\nJWT in the `DS` cookie (refresh in `DSR`). Code that stores, reads, or validates either Stytch\nsession cookie must be replaced with Descope session validation (`validateSession()`), which returns\ndecoded JWT claims. There is no opaque-token-vs-JWT distinction to maintain in Descope.\n\n### Logout Is Two Steps\n\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### Audience Validation Is Opt-In\n\nDescope session tokens have no `aud` claim by default. Apps that rely on audience-scoped API access\nmust (1) configure a custom `aud` claim in JWT Templates and (2) pass `audience` to\n`validateSession()` on the backend.\n\n### Organization Handling: Tenant IDs, Not Token Parsing\n\nMost code that references a Stytch `organization_id` (and an SSO `connection_id`) is\nmanagement/admin code — it becomes a Descope **tenant ID** passed to `management.tenant.*` /\n`management.user.*` calls. Only request-time code that read the organization off the Stytch session\nchanges shape: Descope exposes the active tenant as `dct` and membership as the nested `tenants`\nobject, read off the validated session (ideally via SDK helpers). Grep for all `organization_id`\nreads and sort them into these two buckets — by-ID management calls vs. session reads — before\nupdating.\n\n### No Drop-In Middleware\n\nDescope ships no drop-in auth-middleware package. Whatever validates Stytch sessions today — e.g. a\nNext.js `middleware.ts` calling `sessions.authenticateJwt()`, or Stytch's session helpers — becomes\n~20 lines of custom code that reads the `DS` cookie and calls `validateSession()`.\n\n### `cookies()` and `headers()` Are Async in Next.js 15\n\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### SCIM Is a Lifecycle, Not a One-Time Import\n\nIf SCIM is in use, re-point the SCIM pipeline at Descope before cutover.\n\n### Approved Domains Are Domain-Only (Not Stytch Callback URLs)\n\nStytch apps register full callback URLs (e.g. `http://localhost:3000/authenticate`). Descope uses\n**Approved Domains** (Console → Project Settings → Security) — domain only, no protocol, no path.\nFor local dev: `localhost:3000`, **not** `http://localhost:3000/authenticate`. Descope embedded\nFlows complete auth client-side; there is no `/authenticate` route to whitelist.\n\n### Split-origin SPA + API: do NOT rely on the SDK's session cookies\n\nSee `references/implementation-nuances.md` → **Cookie names: `DS` and `DSR`** → *Split-origin (separate SPA + API) gotcha* for the full explanation, Go code example, and fix (manage `DS`/`DSR` yourself with dev-friendly cookie attributes).\n\n### Use `Management.User().Load*` (not `Auth.MyTenants`) to list a user's tenants\n\n`Auth.MyTenants` requires exactly one of a `dct` flag or an explicit `ids` list and errors with\n`E011004` (\"should get only 1 of dct / ids\") if you pass neither — it cannot enumerate \"all of the\nuser's tenants.\" To list every tenant a user belongs to (with names + roles), validate the session\nto get the user ID, then call `Management.User().LoadByUserID(userId)` and read `UserTenants`.\n\n### Magic-link testing gotchas\n\nMagic-link tokens are single-use and short-lived. `E062504` (\"Token expired … or already used\")\nalmost always means: a stale link from an earlier email, a corporate **email scanner that\npre-clicked** the link, or a **page reload of `/authenticate`** re-submitting a consumed token.\nAlways test with a fresh link, clicked once, to an inbox you control.\n\n### Don't double-write the HTTP response\n\nCalling `w.WriteHeader(...)` and then a JSON responder that also writes a status produces\n`http: superfluous response.WriteHeader call`. Let one place own the status. For SDK methods that\nwrite a redirect to the `ResponseWriter` (e.g. `OAuth().SignUpOrIn`), on error just log — don't then\nemit a second body.\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 -rni \"@stytch\\|stytch\\|com\\.stytch\" \\\n --include=\"*.ts\" --include=\"*.tsx\" --include=\"*.js\" --include=\"*.py\" --include=\"*.go\" \\\n --include=\"*.rb\" --include=\"*.java\" --include=\"*.kt\" --include=\"*.swift\" \\\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\n- `Cannot find module '@stytch/...'` (or `stytch`) → stale import; re-run Phase 0\n- `Property 'X' does not exist on type '...'` → wrapper built against the Stytch session/member response shape; re-derive from the Descope `authInfo` shape\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 Stytch shapes, or a\ntest validates JWT claims that are now missing (e.g., `email` without a JWT Template), or a test\nstill uses `organization_id` where the code now passes a Descope tenant ID (management calls) or\nreads `dct`/`tenants` off the validated session.\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 (including `dct`/`tenants` for B2B) 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, dct):** ✅ 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\n- Phase 0 grep returns zero Stytch 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 Stytch concept to its Descope replacement\n2. **Behavioral differences and open questions** — numbered list of significant differences\n between the Stytch and Descope implementations. For each item: Stytch behavior, Descope\n behavior, action required.\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 (project, Flow,\n JWT template, tenants, SSO/SCIM) and the SCIM re-point — these are the things easiest to\n 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, flag the high-effort items\nexplicitly with 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\ncases for several frameworks.\n- Descope Docs: [https://docs.descope.com](https://docs.descope.com)\n- Migration Guide: [https://docs.descope.com/migrate](https://docs.descope.com/migrate) \n- User Import (Custom): [https://docs.descope.com/migrate/custom](https://docs.descope.com/migrate/custom)\n- Descope OIDC Endpoints: [https://docs.descope.com/getting-started/oidc-endpoints](https://docs.descope.com/getting-started/oidc-endpoints)\n- Descope Flows: [https://docs.descope.com/flows](https://docs.descope.com/flows)\n- JWT Templates: [https://docs.descope.com/management/jwt-templates](https://docs.descope.com/management/jwt-templates)\n- Resources: [https://docs.descope.com/identity-federation/resources](https://docs.descope.com/identity-federation/resources)\n- Policies: [https://docs.descope.com/identity-federation/policies](https://docs.descope.com/identity-federation/policies)\n- Inbound Apps: [https://docs.descope.com/identity-federation/inbound-apps](https://docs.descope.com/identity-federation/inbound-apps)\n- Access Keys (M2M — simple cases only): [https://docs.descope.com/management/m2m-access-keys](https://docs.descope.com/management/m2m-access-keys)\n- Messaging Templates: [https://docs.descope.com/management/messaging-templates](https://docs.descope.com/management/messaging-templates)\n- Audit Webhook: [https://docs.descope.com/connectors/connector-configuration-guides/network/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](https://docs.descope.com/how-to-deploy-to-production/custom-domain)\n- ReBAC: [https://docs.descope.com/authorization/rebac](https://docs.descope.com/authorization/rebac)\n- Outbound Apps: [https://docs.descope.com/identity-federation/outbound-apps](https://docs.descope.com/identity-federation/outbound-apps)\n\n### Session Validation by Language\n\n- Node.js: [https://docs.descope.com/getting-started/nodejs#implement-session-validation](https://docs.descope.com/getting-started/nodejs#implement-session-validation)\n- Python: [https://docs.descope.com/getting-started/python#implement-session-validation](https://docs.descope.com/getting-started/python#implement-session-validation)\n- Go: [https://docs.descope.com/getting-started/golang#implement-session-validation](https://docs.descope.com/getting-started/golang#implement-session-validation)\n- Ruby: [https://docs.descope.com/getting-started/ruby#implement-session-validation](https://docs.descope.com/getting-started/ruby#implement-session-validation)\n- Java / Kotlin: [https://docs.descope.com/getting-started/java#implement-session-validation](https://docs.descope.com/getting-started/java#implement-session-validation)\n- .NET / C#: [https://docs.descope.com/getting-started/dotnet#implement-session-validation](https://docs.descope.com/getting-started/dotnet#implement-session-validation)\n- Next.js: [https://docs.descope.com/getting-started/nextjs#implement-session-validation](https://docs.descope.com/getting-started/nextjs#implement-session-validation)\n- React: [https://docs.descope.com/getting-started/react#implement-session-validation](https://docs.descope.com/getting-started/react#implement-session-validation)\n- Angular: [https://docs.descope.com/getting-started/angular#implement-session-validation](https://docs.descope.com/getting-started/angular#implement-session-validation)\n- Vue: [https://docs.descope.com/getting-started/vue#implement-session-validation](https://docs.descope.com/getting-started/vue#implement-session-validation)\n- Swift / iOS: [https://docs.descope.com/getting-started/swift#implement-session-validation](https://docs.descope.com/getting-started/swift#implement-session-validation)\n- Kotlin / Android: [https://docs.descope.com/getting-started/android#implement-session-validation](https://docs.descope.com/getting-started/android#implement-session-validation)\n- Flutter: [https://docs.descope.com/getting-started/flutter#implement-session-validation](https://docs.descope.com/getting-started/flutter#implement-session-validation)\n\n### SDKs (GitHub)\n\n- Node SDK: [https://github.com/descope/node-sdk](https://github.com/descope/node-sdk)\n- Python SDK: [https://github.com/descope/python-sdk](https://github.com/descope/python-sdk)\n- Go SDK: [https://github.com/descope/go-sdk](https://github.com/descope/go-sdk)\n- Ruby SDK: [https://github.com/descope/descope-ruby-sdk](https://github.com/descope/descope-ruby-sdk)\n- Java SDK: [https://github.com/descope/descope-java](https://github.com/descope/descope-java)\n- .NET SDK: [https://github.com/descope/descope-dotnet](https://github.com/descope/descope-dotnet)\n- Swift SDK: [https://github.com/descope/swift-sdk](https://github.com/descope/swift-sdk)\n- Kotlin SDK: [https://github.com/descope/descope-kotlin](https://github.com/descope/descope-kotlin)\n- Flutter SDK: [https://github.com/descope/descope-flutter](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](https://github.com/descope/descope-js)"
}SHA-256: b5900b5651e26e5c8b00e2df9e2806f67b24cc31112da6d6632435de3ad3b11a