← 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": "workos-to-descope",
"description": "Use this skill whenever anyone asks about migrating from WorkOS to Descope — whether they're a developer doing it themselves or a technical lead evaluating the move. Triggers on: \"how do I migrate from WorkOS\", \"replace WorkOS with Descope\", \"we're moving off WorkOS\", \"WorkOS to Descope\", \"switch from WorkOS\", \"our app uses @workos-inc/node / @workos-inc/authkit-nextjs / AuthKit / WorkOS SSO / Directory Sync / SCIM and we want to use Descope instead\", or any question about WorkOS features (AuthKit, Organizations, Enterprise SSO, Directory Sync/SCIM, Admin Portal, RBAC, FGA, Audit Logs, Radar, Pipes) 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": 12985
},
{
"relative_path": "references/implementation-nuances.md",
"size_in_bytes": 59309
}
],
"skill_md_contents": "---\nname: workos-to-descope\ndescription: >\n Use this skill whenever anyone asks about migrating from WorkOS 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 WorkOS\", \"replace WorkOS with Descope\", \"we're moving off WorkOS\", \"WorkOS to\n Descope\", \"switch from WorkOS\", \"our app uses @workos-inc/node / @workos-inc/authkit-nextjs / AuthKit / WorkOS SSO / Directory Sync / SCIM and we want to use Descope instead\",\n or any question about WorkOS features (AuthKit, Organizations, Enterprise SSO, Directory Sync/SCIM,\n Admin Portal, RBAC, FGA, Audit Logs, Radar, Pipes) 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# WorkOS → Descope Migration Skill\n\nThis skill guides self-service migrations from WorkOS 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.\n\nWorkOS is not only an authentication provider — it is a B2B/enterprise-readiness platform spanning\nauthentication, organizations, enterprise SSO, SCIM/directory sync, RBAC, FGA, audit logs, connected\naccounts, admin setup flows, and security controls. A good migration first identifies which WorkOS\nfeatures are in use, then maps each one to the closest Descope feature or migration pattern. Expect\nWorkOS migrations to be more B2B-enterprise heavy than a typical consumer-auth migration.\n\n**Primary references** (both in this skill's directory):\n\n- `references/implementation-nuances.md` — verified migration patterns for each framework, WorkOS 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 — embed Flows vs. OIDC compatibility, 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 WorkOS 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 MCP Server is available by calling\n`docs_search` 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 MCP is not installed.**\n>\n> This skill uses the Descope MCP server 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.descope.com/mcp/mcp-server](https://docs.descope.com/mcp/mcp-server)** (server URL:\n> `https://mcp.descope.com`). 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 `docs_search` 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). 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 WorkOS, 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 — WorkOS feature usage (use `multiSelect: true`):**\n\n1. **Which WorkOS features are in use?** Present the highest-impact categories:\n - **AuthKit** — WorkOS's hosted/embeddable login UI and session management (email/password, social login, passkeys, MFA, magic auth); which sign-in methods are enabled and whether the hosted or embedded flow is used.\n - **Organizations** — organization membership, organization switching, metadata, whether users can belong to multiple organizations.\n - **Enterprise SSO** — connections SAML, OIDC, or both; whether setup is handled by internal engineers or by customer admins; whether domain-based SSO routing is used.\n - **Directory Sync / SCIM** — which directories; group sync; group-to-role mapping; deprovisioning behavior; directory webhook handlers.\n - **Admin Portal / Widgets** — which customer-admin workflows are hosted by WorkOS today; whether the app generates portal links; whether Descope Widgets or the SSO Setup Suite can replace them.\n - **RBAC** — whether roles are global/environment or organization-scoped; where permission checks happen in code; whether roles/permissions are in tokens; whether IdP groups map to roles.\n - **FGA** — the authorization model (resources, relationships, privileges, hierarchy); where checks are performed. Flag as high complexity.\n - **Audit Logs** — whether logs are written to WorkOS, read back from WorkOS, shown to customers, or required for compliance.\n - **Radar** — whether it blocks, challenges, or only notifies about suspicious auth attempts; custom rules.\n - **Pipes** — which providers are connected; where connected-account tokens are used (AI agents, integrations, background jobs).\n - **Vault / Feature Flags** — flag as potentially outside the core Descope identity migration.\n - **MCP Auth / Connect** — flag for deeper review before implementation.\n - The user can add others via \"Other.\"\n\nAfter both calls, summarize findings and flag high-complexity items (Directory Sync/SCIM, FGA,\nPipes, MCP Auth/Connect, Vault) before proceeding to Step 0.5.\n\n---\n\n### Step 0.5: Engineer Review Checkpoint (BLOCKING — requires `AskUserQuestion`)\n\nThese questions surface blockers the framework doesn't expose. Ask even the ones you think\nyou know. Use `AskUserQuestion` before proceeding to codebase analysis.\n\nBatch into calls of up to 4 questions. Skip questions that are clearly inapplicable given\nStep 0 answers (e.g., skip user migration planning if they said they're starting fresh).\n\n**Access and credentials**\n\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, RBAC, ReBAC, tenant/SSO/SCIM configuration, Outbound Apps.)\n\n**Codebase scope**\n\n- Are there places in the app that read claims directly from the session token (e.g. `user.email`, `claims.organization_id`, `role`/`permissions`)? These need a JWT Template configured before they'll work.\n- Does the app read WorkOS `organizationId`, `connectionId`, or `directoryId` in many places? The WorkOS Organization → Descope Tenant remap ripples through SSO, SCIM, RBAC, and membership checks — confirm the org model before writing code.\n- Are there multiple services or microservices validating WorkOS tokens/sessions? Each needs to be updated to validate Descope JWTs.\n\n**Deployment and risk**\n\n- Do they have multiple environments (dev / staging / prod)? Each needs its own Descope project and Project ID.\n- Is there a maintenance window, or does this need to be zero-downtime?\n\n**User and organization migration** (if they indicated existing users/orgs in Step 0)\n\n- How many users and organizations? This determines export approach and whether a phased cutover is warranted.\n- Do they use passwords in AuthKit? Plan how password credentials carry over (export/import vs. reset vs. passwordless). Verify the current WorkOS user-export capability and the Descope import path before committing to an approach.\n- Big-bang cutover or phased? Map each WorkOS Organization to a Descope Tenant first; user membership and tenant-scoped roles depend on it.\n- **SCIM is a lifecycle system, not a one-time import.** If Directory Sync is enabled, enterprise directories will keep pushing create/update/suspend/delete events after cutover. A single user import is not enough — every SCIM/directory workflow must be re-pointed at Descope before cutover, or provisioning silently breaks.\n- Are they aware that active WorkOS sessions will be invalidated on cutover unless a session-bridging approach is used? Plan for a forced re-login or phased rollout.\n\n**Gaps to flag immediately** (don't ask — flag these proactively based on Step 0 answers)\n\n- If they're using **Vault** or **Feature Flags**: these may have no direct Descope identity equivalent. Flag separately; do not pretend they are Descope SDK swaps. Ask whether they're in scope.\n- If they're using **MCP Auth / Connect**: flag for deeper review before any implementation — likely maps to Descope Inbound Apps / OAuth app patterns, but needs dedicated mapping.\n- If they're using **Audit Logs**: set up Descope's Audit Webhook Connector before cutover to avoid gaps in compliance/event logging. Missing this can break compliance visibility even though the app still runs.\n- If they're using **Pipes / connected accounts**: connected third-party tokens may power integrations or background jobs. Identify provider connections and whether users must reconnect accounts.\n\n**Console/Flow/Widget opportunities** (flag before codebase analysis, then ask):\n\n- If the app uses the **WorkOS Admin Portal** or generates portal links: ask whether the SSO Setup Suite + Tenant Profile Widget replaces 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 or user management UI: ask whether a Descope Widget covers the use case.\n- If the app has a separate MFA enrollment page: ask whether MFA should be integrated into the main sign-in Flow as a step or subflow instead (almost always cleaner in Descope).\n- If any server-side code initiates SSO, generates emails, or runs logic during the auth journey: ask whether that logic can be a Flow step or Connector instead of server code.\n\nSummarize any blockers and Console/Flow opportunities before proceeding to codebase analysis.\n\n---\n\n### Step 1: Codebase Analysis\n\nScan the codebase to map every auth touchpoint before writing the plan.\n\n**Run these searches (adapt file extensions to the user's language):**\n\n```bash\n# Find all WorkOS / AuthKit import sites.\ngrep -rni \"workos\\|authkit\" \\\n --include=\"*.ts\" --include=\"*.tsx\" --include=\"*.js\" --include=\"*.jsx\" \\\n --include=\"*.mjs\" --include=\"*.cjs\" --include=\"*.py\" --include=\"*.go\" \\\n --include=\"*.rb\" --include=\"*.php\" --include=\"*.java\" --include=\"*.kt\" \\\n --include=\"*.cs\" --include=\"*.ex\" --include=\"*.exs\" --include=\"*.rs\" \\\n --exclude-dir=node_modules --exclude-dir=.next --exclude-dir=dist --exclude-dir=venv \\\n . 2>/dev/null\n\n# Find all WorkOS env var references\ngrep -rn \"WORKOS_\\|workos\\.\" \\\n --include=\"*.ts\" --include=\"*.tsx\" --include=\"*.js\" --include=\"*.py\" --include=\"*.go\" \\\n --include=\"*.env*\" --include=\"*.yml\" --include=\"*.yaml\" --include=\"Dockerfile\" \\\n --exclude-dir=node_modules --exclude-dir=.next \\\n . 2>/dev/null\n\n# Find WorkOS SDK surface + claim / token / org access patterns (things that may need a JWT Template or org→tenant remap)\ngrep -rn \"workos.userManagement\\|workos.organizations\\|workos.sso\\|workos.directorySync\\|workos.auditLogs\\|workos.fga\\|workos.widgets\\|workos.events\\|workos.webhooks\\|workos.pipes\\|workos.portal\\|workos.organizationDomains\\|workos.featureFlags\\|workos.types\\|workos.mfa\\|workos.authorization\\|workos.vault\\|organizationId\\|organization_id\\|orgId\\|connectionId\\|connection_id\\|directoryId\\|directory_id\\|roleSlug\\|permission\" \\\n --include=\"*.ts\" --include=\"*.tsx\" --include=\"*.js\" --include=\"*.py\" --include=\"*.go\" \\\n --exclude-dir=node_modules --exclude-dir=.next \\\n . 2>/dev/null\n\n# Find protected route / session access declarations\ngrep -rn \"authkitMiddleware\\|withAuth\\|getUser\\|ensureSignedIn\\|getSignInUrl\\|getSession\\|isAuthenticated\\|require_session\\|@login_required\\|authMiddleware\" \\\n --include=\"*.ts\" --include=\"*.tsx\" --include=\"*.js\" --include=\"*.py\" --include=\"*.go\" \\\n --exclude-dir=node_modules --exclude-dir=.next \\\n . 2>/dev/null\n\n# Find B2B / enterprise feature usage (SSO, SCIM, audit, admin portal, security)\ngrep -rn \"scim\\|saml\\|sso\\|auditLog\\|audit_log\\|adminPortal\\|portalLink\\|radar\\|pipes\" \\\n --include=\"*.ts\" --include=\"*.tsx\" --include=\"*.js\" --include=\"*.py\" --include=\"*.go\" \\\n --exclude-dir=node_modules --exclude-dir=.next \\\n . 2>/dev/null\n\n# Check package.json / go.mod / requirements.txt for WorkOS dependencies\nfind . -maxdepth 3 \\( -name \"package.json\" -o -name \"go.mod\" -o -name \"requirements.txt\" \\) \\\n ! -path \"*/node_modules/*\" -exec grep -l \"workos\" {} \\;\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 `docs_search` or `docs_ask_question`\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, WorkOS handles everything related to login: AuthKit shows the login UI, issues tokens\n> and sealed sessions, validates them on every request, and routes enterprise users to the right\n> SSO connection. After this migration, Descope takes over all of those responsibilities. The\n> login UI becomes a Descope Flow embedded in the app. Session validation moves to the Descope\n> SDK. WorkOS Organizations become Descope Tenants. The WorkOS API key, client ID, redirect URI,\n> and cookie password are replaced by a single Descope Project ID.\n>\n> WorkOS 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 WorkOS touchpoint found in triage, produce a concrete, one-to-one mapping — WorkOS 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/app does:\n rendering the login/sign-up UI (a Descope Flow replaces AuthKit's hosted or redirect login),\n initiating authentication, holding the session on the client, refreshing the token, and reading\n the current user for UI purposes. This replaces AuthKit's UI, the redirect cycle, and any\n client-side session access. 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 WorkOS\n server-side `withAuth()` / middleware), checking roles and permissions, and — with a Management\n Key — all administrative operations done by ID (user and tenant CRUD, role/permission definitions,\n SSO/SCIM configuration, ReBAC). This replaces WorkOS server-side validation and every WorkOS\n Management API call.\n\nFor each file or area, name the WorkOS 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 WorkOS feature spans both sides — for example AuthKit login\n(now a client Flow) plus per-request `withAuth()` validation (now the backend SDK) — split it into\nits client half and its backend half so the reader sees exactly what moves where, and why each piece\nbelongs 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 WorkOS AuthKit. \n\n\n| File | What it does today | What changes |\n| ------------------ | ----------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |\n| `lib/auth.ts:34` | Returns WorkOS session via `withAuth()` with `user`, `organizationId`, `role` | Rewritten to return Descope `authInfo`; a thin adapter layer preserves the shape callers expect |\n| `middleware.ts:12` | `authkitMiddleware()` blocks unauthenticated requests app-wide | Updated to call Descope session validation; logic is identical, SDK call changes |\n\n\n**Login / logout routes (2 files)** — These handle the AuthKit redirect-based login flow.\nDescope replaces this with an embedded UI component (or hosted Flow); the redirect cycle changes.\n\n\n| File | What it does today | What changes |\n| ----------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------- |\n| `app/callback/route.ts` | AuthKit OAuth callback handler | Deleted or rewritten — Descope handles this client-side; verify the replacement against the framework section |\n\n\nCover all functional groupings. End with: \"Total: N files. Estimated code-change effort: N–N hours.\"\n\n---\n\n#### Feature Migration: WorkOS → Descope\n\nFor each WorkOS 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 (WorkOS Organizations → Descope Tenants)**\n> WorkOS Organizations group users by company and scope SSO, SCIM, roles, and domain policies.\n> Descope has the same concept, called Tenants. Most code that handles organizations is\n> management/admin code that passes a WorkOS `organizationId` to the API — that simply becomes a\n> Descope **tenant ID** passed to `descopeClient.management.tenant.*` / `management.user.*` calls\n> (load a tenant, create one, add/remove membership, scope roles). This is by-ID work, not token\n> parsing. The only place a tenant shows up as a claim is request-time session reads: WorkOS's flat\n> `organizationId` (from `withAuth()`) becomes Descope's nested `tenants` object (plus `dct` for the\n> active tenant), which you read off the validated session — ideally via SDK helpers like\n> `validateTenantRoles(authInfo, tenantId, [...])` rather than parsing claims by hand. Confirm the\n> org→tenant mapping first, since it ripples into SSO, SCIM, and RBAC.\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\nall WorkOS credentials 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 WorkOS 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| `WORKOS_API_KEY` | — | WorkOS authenticates server-side calls with a secret API key. Descope uses a Project ID (+ optional Management Key) instead. |\n| `WORKOS_CLIENT_ID` | — | WorkOS identifies the AuthKit client. Descope uses a Project ID. |\n| `WORKOS_REDIRECT_URI` | — | AuthKit's OAuth callback URL, configured in console. Descope's embedded Flow doesn't require a server redirect URI in the same way. |\n| `WORKOS_COOKIE_PASSWORD` | — | Used by AuthKit to encrypt/seal the session cookie. Descope issues a signed session JWT instead; no sealing password needed. |\n| — | `DESCOPE_PROJECT_ID` | The single identifier for the Descope project. Replaces all of the above. |\n| — | `NEXT_PUBLIC_DESCOPE_PROJECT_ID` | Same value, exposed to the browser for the login component (Next.js only). |\n| — | `DESCOPE_MANAGEMENT_KEY` | Only needed if the app manages users, roles, tenants, or SSO/SCIM server-side. |\n\n\nFollow with: \"Net change: 4 variables removed, 1–3 added. No secrets need to be rotated\non the WorkOS 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 WorkOS Organization becomes a Descope Tenant; membership and tenant-scoped roles depend on this mapping being correct first\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 WorkOS\n- Map each WorkOS Organization to a Descope Tenant\n- Re-point SCIM/Directory Sync at Descope (if Directory Sync 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> WorkOS 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> Directory Sync 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/directory workflow and re-point it at Descope before cutover.\n\n> **Consideration: Admin Portal workflows should not automatically become custom code**\n> If the app uses the WorkOS Admin Portal, 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: Audit logs can silently disappear**\n> The app may keep working after migration even if audit logging is broken — creating compliance\n> and enterprise-customer issues.\n> **Action:** Set up Descope audit/event forwarding before production cutover.\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- Create authentication flow (use the built-in `sign-up-or-in` to start)\n- Configure user profile token template\n- Create tenants for each WorkOS 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 path: [Path A: OIDC compat / Path B: Full native]\n- Migration goal: [Full cutover / Phased / Evaluating]\n\n## Triage Answers\n- Existing users: [Yes — N users / No — greenfield]\n- Existing organizations: [Yes — N orgs → tenants / No]\n- Password migration needed: [Yes / No]\n- WorkOS 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- [ ] JWT template configured\n- [ ] Tenants created for each WorkOS 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 · Path B · Phase 2, step 3/8 · Next: rewrite lib/auth.ts`\n\nIf this line can't be filled in accurately, re-read the files first.\n\n**Rule 3 — Update `MIGRATION-STATE.md` immediately after each step.**\n\nMark the file done in the Files Inventory, update \"Current Phase\" and \"Next Action\", and\nappend any non-obvious decision to the Decisions Log. Do this before the next step.\n\n---\n\n## Pre-Generation Protocol (apply before writing any code)\n\nRun before generating any import, wrapper type, or helper. Skipping produces code that\ncompiles but fails at runtime.\n\n**1. Verify SDK exports before writing any import.**\nWhen the Descope MCP server is available, use `docs_ask_question` 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 '@workos-inc/\" --include=\"*.ts\" --include=\"*.tsx\" .\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. WorkOS'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 WorkOS'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–7 — 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 a JWT Template (almost always needed)\n\nWorkOS AuthKit 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### 6. 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### 7. Define custom attributes (if using tenant/user metadata)\n\nWorkOS Organization `metadata` and User metadata map to Descope `customAttributes`.\nPre-define them in the Console schema before setting them via the SDK.\n\n- **Tenant** custom attributes (the equivalent of WorkOS Organization `metadata`): Console → **Tenants → Custom Attributes** tab → **Create Attribute**\n- **User** custom attributes: Console → **Project → Custom Attributes**\n\n### 8. 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### 9. 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\nWorkOS publishes exactly two SDK families (per the [WorkOS SDKs page](https://workos.com/docs/sdks)):\n\n- **Backend SDKs** (one per language): `workos-node`, `workos-go`, `workos-ruby`, `workos-rust`, `workos-python`, `workos-php`, `workos-php-laravel`, `workos-kotlin` (Java), `workos-dotnet` (.NET). These call the WorkOS API and hold server-side session helpers.\n- **AuthKit SDKs** (one per JS framework): `authkit-js`, `authkit-react`, `authkit-nextjs`, `authkit-remix`, `authkit-react-router`, `authkit-tanstack-start`. These handle the login UI + session.\n\nThe recipes below cover exactly these SDKs — one section each — annotated with the matching Descope target. Do not infer other frameworks (e.g. Express, Flask, FastAPI); a WorkOS app using those is using the underlying language Backend SDK (`workos-node`, `workos-python`, etc.), so map it via that SDK's section.\n\n> The framework recipes below are stubs listing the WorkOS idioms that need mapping. Confirm the exact WorkOS 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 WorkOS idioms to map (all frameworks)\n\nThese idioms are **AuthKit-JS-specific**. Backend-SDK apps (Python, Go, Ruby, PHP, Java, .NET) don't\nhave these helpers — they instead call the SDK directly (e.g. `workos.userManagement.authenticateWithCode(...)`\nfor the code exchange and `workos.userManagement.loadSealedSession(...)` for session access), which map\nto Descope session validation + the hosted/embedded Flow the same way.\n\n- `withAuth()` / `getUser()` (AuthKit session access) → Descope session validation + an adapter returning the shape callers expect\n- `authkitMiddleware()` → Descope session-validation middleware\n- AuthKit sealed-session cookie (`WORKOS_COOKIE_PASSWORD`) → Descope signed session JWT in `DS`/`DSR` cookies\n- `getSignInUrl()` / hosted AuthKit redirect → embedded Descope Flow component (or hosted Flow), wiring `onSuccess`\n- WorkOS callback route (code exchange) → removed/rewritten; Descope handles auth client-side\n\n### Backend SDKs\n\n#### Node.js\n\n*WorkOS SDK: `workos-node` (`@workos-inc/node`) → Descope `@descope/node-sdk`*\n\n- Remove `@workos-inc/node` auth/session usage; add `@descope/node-sdk`\n- Validate the `DS` session token via custom middleware calling `descopeClient.validateSession()` (parse the cookie yourself)\n\n#### Go\n\n#### *WorkOS SDK: `workos-go` → Descope Go SDK `github.com/descope/go-sdk`*\n\n- Remove the WorkOS Go SDK; add `descope/go-sdk`\n- Session validation: `descopeClient.Auth.ValidateSessionWithToken(ctx, token)` returns `(bool, *descope.Token, error)`\n- WorkOS `organizationId` → 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*WorkOS SDK: `workos-ruby` → Descope Ruby SDK*\n\n- Remove the WorkOS Ruby SDK; add the Descope Ruby SDK\n- Validate the `DS` session token via the Descope Ruby SDK 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#### Rust\n\n*WorkOS SDK: `workos-rust` → **No Descope Rust SDK**; validate via Descope JWKS + Management REST API*\n\n- There is no official Descope Rust SDK. Validate the `DS` session JWT directly against Descope's JWKS endpoint (`https://api.descope.com/v2/keys/<project_id>`) using a standard JWT library.\n- Management operations (users, tenants, roles, SSO) → call the Descope Management REST API directly.\n\n#### Python\n\n*WorkOS SDK: `workos-python` → Descope `descope` Python SDK*\n\n- Remove the WorkOS Python SDK auth/session usage; add the `descope` Python SDK\n- Validate the `DS` session token with `descope_client.validate_session(session_token)` (or validate against Descope's JWKS for a custom authorizer)\n\n#### PHP\n\n*WorkOS SDK: `workos-php` → Descope PHP SDK*\n\n- Remove the WorkOS PHP SDK; add the Descope PHP SDK\n- Validate the `DS` token via the Descope PHP SDK in your request lifecycle\n- No dedicated recipe yet — follow the Node.js / Python backend patterns and verify against the Descope PHP SDK.\n\n#### Laravel\n\n*WorkOS SDK: `workos-php-laravel` → Descope PHP SDK (no Descope Laravel-specific SDK)*\n\n- Remove the WorkOS Laravel package; use the Descope PHP SDK\n- Validate the `DS` token in Laravel middleware\n- No dedicated recipe yet — follow the PHP backend patterns and verify.\n\n#### Java\n\n*WorkOS SDK: `workos-kotlin` (Java/Kotlin) → Descope `descope-java`*\n\n- Remove the WorkOS Java/Kotlin SDK; add `descope-java`\n- Validate the `DS` token via a filter/interceptor\n- No dedicated recipe yet — follow the backend patterns and verify against the [Descope Java SDK](https://github.com/descope/descope-java).\n\n#### .NET\n\n*WorkOS SDK: `workos-dotnet` → Descope `descope-dotnet`*\n\n- Remove the WorkOS .NET SDK; add `descope-dotnet`\n- Validate the `DS` token in middleware / a custom auth handler\n- No dedicated recipe yet — follow the backend patterns and verify against the [Descope .NET SDK](https://github.com/descope/descope-dotnet).\n\n### AuthKit 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, loaders, 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#### JavaScript\n\n*WorkOS SDK: `authkit-js` → Descope `@descope/web-js-sdk` + `@descope/web-component`*\n\n- `createClient()` / `authkit.getUser()` / `getAccessToken()` → `@descope/web-js-sdk` (`getSessionToken()`, `isJwtExpired()`, `refresh()`)\n- Login UI → `<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*WorkOS SDK: `authkit-react` → Descope `@descope/react-sdk`*\n\n- `<AuthKitProvider>` → Descope `<AuthProvider projectId>`\n- `useAuth()` (user/session/loading) → `useSession()` + `useUser()` hooks\n- **Always read auth state through the hooks** — `useSession()` (token + `isAuthenticated`) and `useUser()` (profile), with `useDescope()` for actions. 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- `signIn()` / hosted redirect → embedded `<Descope flowId>` component, wiring `onSuccess`\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*WorkOS SDK: `authkit-nextjs` → Descope `@descope/nextjs-sdk` + `@descope/node-sdk`*\n\n- `authkit-nextjs` → `@descope/nextjs-sdk` + `@descope/node-sdk`\n- AuthKit `<AuthKitProvider>` → Descope `AuthProvider` (takes `projectId`; must use `NEXT_PUBLIC_` prefix)\n- `withAuth()` / `useAuth()` → `session()` (server) + `useSession()` / `useUser()` (client)\n- Remove the AuthKit callback route — verify Descope's client-side handling\n- `authkitMiddleware()` → 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#### Remix\n\n*WorkOS SDK: `authkit-remix` → Descope `@descope/react-sdk` + `@descope/web-js-sdk` (no Descope Remix SDK)*\n\n- `authkitLoader` / `authLoader()` loaders → custom Remix loaders that validate the session token (`@descope/web-js-sdk` / `@descope/node-sdk`) and gate routes\n- `getSignInUrl()` → embedded Descope component (`@descope/react-sdk`) or hosted Flow\n- Logout: clear `DS`/`DSR` cookies in an action + `sdk.logout()`\n- No dedicated recipe yet — follow the Next.js (server + client split) patterns and verify.\n\n#### React Router\n\n*WorkOS SDK: `authkit-react-router` → Descope `@descope/react-sdk`*\n\n- Same shape as Remix (`authkit-react-router` is the React Router 7+ port): loader-based session checks → custom loaders + Descope session validation\n- Login via embedded Descope component; logout via `sdk.logout()` + cookie clear\n- No dedicated recipe yet — follow the React / Remix patterns and verify.\n\n#### TanStack Start\n\n*WorkOS SDK: `authkit-tanstack-start` → Descope `@descope/react-sdk` / `@descope/web-js-sdk` (no Descope TanStack SDK)*\n\n- Server-route session helpers → TanStack server functions validating the Descope session token\n- Login via embedded Descope component; logout via `sdk.logout()` + cookie clear\n- No dedicated recipe yet — follow the Next.js / React patterns and verify.\n\n### Path A: OIDC Compatibility (lower risk, incremental)\n\nDescope exposes standard OIDC endpoints. If the app uses a **generic OIDC client library**\npointed at WorkOS, it can point at Descope's OIDC issuer instead with minimal code changes.\n\n**First classify the current integration — the OIDC endpoints only matter for the hosted-page case.**\nBefore considering Path A, determine how the app authenticates today:\n- **Hosted AuthKit page** (users are redirected to a WorkOS-hosted login URL through a generic\n OIDC/OAuth client) → the Descope OIDC endpoints below are directly relevant; re-point the OIDC\n client at Descope's issuer.\n- **Embedded AuthKit** (AuthKit's own SDK/components rendering login inside the app) **or custom code**\n against the WorkOS SDK → the OIDC endpoints are largely irrelevant; this is a Descope-native SDK +\n Flow migration (Path B), not an issuer swap.\n\nDon't recommend Path A until you've confirmed the app uses a standard OIDC/OAuth client against the\nhosted page.\n\n> Many WorkOS apps use AuthKit's own SDK rather than a generic OIDC client, in which case Path A may not apply cleanly. Confirm whether the app uses a standard OIDC client before recommending this path. Verify the Descope OIDC endpoint table below against current docs.\n\n\n| Endpoint | Descope |\n| ------------- | ------------------------------------------------------------- |\n| Issuer | `https://api.descope.com` |\n| Authorization | `https://api.descope.com/oauth2/v1/authorize` |\n| Token | `https://api.descope.com/oauth2/v1/token` |\n| UserInfo | `https://api.descope.com/oauth2/v1/userinfo` |\n| JWKS | `https://api.descope.com/__ProjectID__/.well-known/jwks.json` |\n\n\n**Good for:** Teams that want to swap the IdP first, then refactor to Descope-native SDKs\nlater. Preserves existing OIDC client code.\n\n**Caveats:** Claim shapes differ, token lifetimes may differ, and WorkOS organization-scoped\nlogin / SSO must be rebuilt in Descope regardless of path.\n\n> **For B2B apps using WorkOS Organizations:** Path A preserves only a fraction of the work;\n> the management SDK, org/tenant-scoped login, SSO/SCIM setup, and claim mapping require full\n> migration regardless. **Path A savings are minimal for B2B workloads** — account for this\n> when estimating effort.\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 WorkOS references in non-code files after updating source files.\n\n### `.env.example` / `.env.template` / `.env.sample`\n\n```\n# REMOVE\nWORKOS_API_KEY=\nWORKOS_CLIENT_ID=\nWORKOS_REDIRECT_URI=\nWORKOS_COOKIE_PASSWORD=\n\n# ADD\nDESCOPE_PROJECT_ID= # Console → Project Settings\nNEXT_PUBLIC_DESCOPE_PROJECT_ID= # Next.js only — same value as above\nDESCOPE_MANAGEMENT_KEY= # Console → Company → Management Keys (only if using management SDK)\n```\n\nRun `grep -r \"WORKOS\"` to find all env var references — `.env.example`, Docker, CI, shell scripts.\n\n### README / docs\n\nSearch all `.md` files for WorkOS references. At minimum, update:\n\n- **Setup section** — replace \"create a WorkOS app / AuthKit setup\" instructions with Descope Console setup steps\n- **Environment variables section** — reflect the reduced env var set\n- **Run instructions** — replace WorkOS 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`WORKOS_`* 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 WorkOS 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, or\nConsole configuration rather than a direct SDK equivalent. Only recommend SDK/API code when\nprogrammatic control is genuinely required, and verify every method name against the Descope MCP server\nbefore writing it. Include only confirmed features.\n\n### AuthKit → Descope Flows + JWT Templates\n\nWorkOS AuthKit handles login UI, authentication methods, users, sessions, and enterprise login\nrouting. Descope splits these responsibilities across a Flow (UI + methods), session validation\n(SDK), Users/Tenants, and JWT Templates (profile claims).\n\n\n| WorkOS | Descope |\n| ---------------------------------------------------------------- | ---------------------------------------------------------------- |\n| Hosted AuthKit login UI | [Descope Flows](https://docs.descope.com/flows) |\n| `withAuth()` / `getUser()` session access | `validateSession()` + adapter returning the shape callers expect |\n| AuthKit user object | Descope User (profile fields via JWT Template) |\n| Auth method config (password, social, passkeys, MFA, magic auth) | Methods toggled in Console + added as Flow steps |\n\n\nUse Flows for the user-facing journey whenever possible; write custom SDK calls only when Flows\ncannot express the requirement. Ask which auth methods are enabled before recommending details.\n**Effort: Low–Medium** (mostly SDK/UI/session swap; token differences matter).\n\n### Organizations → Descope Tenants\n\n- WorkOS `organizationId` → a Descope **tenant ID** (the argument you pass to `management.tenant.*` / `management.user.*` calls). At request time the tenant also appears in the session as the nested `tenants` object (`{ tenantId: { roles, permissions } }`) plus `dct` for the active tenant.\n- WorkOS org-scoped login → Descope **tenant routing** (also called **home realm discovery**). Two\n main approaches:\n 1. **Domain-based routing** — set an email domain on the tenant (when not using SSO) or an SSO\n domain on the tenant's SSO configuration (when using SSO). Descope resolves the tenant/IdP from\n the user's email domain at login.\n 2. **Explicit tenant slug** — pass a tenant name, ID, or slug hardcoded in the app (e.g.\n per-customer login URL or `sso.start(tenantId, ...)` for a known tenant). Use when each customer\n has a dedicated login path rather than a shared email-entry screen.\n- Users are project-level in Descope; associated with tenants, not created per-tenant\n- Organization `metadata` → tenant `customAttributes` (pre-define in the Console schema)\n\nConfirm the one-Organization-to-one-Tenant mapping before writing code — it ripples into SSO, SCIM,\nRBAC, and domain routing. **Effort: Medium** (clean conceptually; most `organizationId` usage is\nmanagement calls that take a tenant ID, with a smaller set of session reads that change shape).\n\n### Enterprise SSO → Descope Tenant SSO\n\n**Preferred approach — SSO Setup Suite:** before migrating any management-SDK SSO calls, 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 (formerly Azure AD), Google Workspace, etc.) — no\nengineering involvement for new tenant onboarding.\n\n**Multiple SSO configurations per tenant.** Descope supports more than one SSO/IdP configuration on a\nsingle tenant: each tenant has a **Default SSO Configuration** plus optional **additional named SSO\nconfigurations** (Console or API/SDK). At login, Descope selects the right IdP by **domain-based\nrouting** (e.g. `@acme.com` → Acme's Okta, `@globex.com` → Globex's Microsoft Entra ID), a **tenant-specific\nlogin URL**, an explicit SSO configuration ID, or Flow logic. SCIM provisioning can be scoped per SSO\nconfiguration, and each configuration can have its own SSO Setup Suite link. See\n`references/flows-and-widgets.md` and [Descope Multi-SSO](https://docs.descope.com/sso/multi-sso).\n\nUse `AskUserQuestion` to ask **two** things here:\n1. Does any single customer use **multiple IdPs** (or did they create multiple WorkOS Organizations for\n the same customer to handle different SSO connections/domains)? If yes, plan to consolidate into one\n Descope Tenant with multiple SSO configurations rather than multiple tenants.\n2. Does the app need **programmatic** SSO configuration (CI/CD provisioning, API-driven onboarding), or\n do tenant admins configure SSO themselves? If the latter, the SSO Setup Suite + Tenant Profile\n Widget may eliminate the SDK calls entirely. See `references/flows-and-widgets.md` → SSO Setup Suite.\n\n**SDK path (when programmatic SSO is needed):**\n\n\n| WorkOS | Descope |\n| --------------------------- | ------------------------------------------------- |\n| SAML connection (`sso.`*) | `management.ssoApplication.createSamlApplication` |\n| OIDC connection | `management.ssoApplication.createOidcApplication` |\n| Per-Organization connection | Per-tenant SSO (Console → SSO or Management SDK) |\n\n\n(Verify exact method names against the Descope MCP server.) Ask whether SSO is configured by internal\nengineers or by customer admins. **Effort: Medium** — setup recreated per tenant.\n\n**Runtime login calls — always use `sso.start` / `sso.exchange`, never the OAuth flow.** When code\ninitiates an enterprise SSO login (the equivalent of WorkOS's `sso.getAuthorizationUrl()` +\n`sso.getProfileAndToken()`), call the Descope SDK's `sso.start(tenant, redirectUrl, ...)` to begin\nthe flow and `sso.exchange(code)` to complete it. Use these **regardless of the IdP's underlying\nprotocol** — even when the tenant's SSO is configured in Descope as OIDC/OAuth — because `sso.start`\nresolves the **tenant-level SSO configuration** (the correct IdP, domain-based routing, and\nconnection settings) for you. Do **not** reach for the generic `oauth.start` / `oauth.exchange`\nfunctions for enterprise SSO: those drive project-level social/OAuth providers and will not apply a\ntenant's SSO config. Rule of thumb: tenant/enterprise SSO → `sso.*`; social or generic OAuth login →\n`oauth.*`.\n\n**Don't rebuild provider-specific SSO UI — let tenant config do the routing.** Especially when the\napp uses the **backend SDKs**, do not migrate or recreate any per-IdP login UI (separate \"Sign in\nwith Okta\" / \"Sign in with Microsoft Entra ID\" buttons, provider-picker screens, etc.), regardless of which\nSSO provider the WorkOS code names. In Descope the IdP is defined as **tenant SSO configuration**,\nand a single `sso.start` call resolves it automatically via **home realm discovery** — either\n**domain-based routing** (email domain or SSO domain on the tenant config) or an **explicit tenant\nslug** (tenant ID/name hardcoded in source). So the login surface just collects an email (or targets a\nknown tenant) and calls\n`sso.start`; Descope selects the correct IdP from config. Keep the UI generic and push all\nprovider-specific details into Console/tenant configuration.\n\n### Directory Sync / SCIM → Descope SCIM / Tenant Provisioning\n\nWorkOS Directory Sync maps to Descope SCIM provisioning. **Treat this as a continuing pipeline, not\na one-time import** — enterprise directories keep pushing create/update/suspend/delete events after\ncutover, so every directory must be re-pointed at Descope before cutover or provisioning silently\nbreaks.\n\n\n| WorkOS Directory Sync | Descope |\n| ------------------------------------------ | ---------------------------------------- |\n| SCIM endpoint + bearer token per directory | Descope SCIM endpoint + token per tenant |\n| Directory user create/update/deprovision | Tenant user provisioning lifecycle |\n| Directory groups | Group → role mapping in Descope |\n| `dsync.`* / directory webhooks | Descope provisioning events / connectors |\n\n\nIdentify every directory, whether groups are synced, and whether groups map to roles.\n**Effort: Medium–High** (lifecycle, groups, deprovisioning, and role mapping can be subtle).\n\n### Admin Portal → Descope SSO Setup Suite / Widgets\n\nWorkOS Admin Portal is a hosted self-serve UI where customer IT admins configure SSO, Directory Sync,\nand domain verification. Do not default to rebuilding it as custom code.\n\n- Generated portal links (`portal.generateLink(...)`) → SSO Setup Suite hosted/embedded flow or Tenant Profile Widget\n- SSO setup screens → SSO Setup Suite\n- Directory Sync / domain setup → corresponding Widgets\n\nAsk which admin workflows are hosted by WorkOS today before choosing a replacement. **Effort: Medium**\n— may remove custom code, but portal-link workflows need replacement.\n\n### RBAC → Descope RBAC\n\nWorkOS roles come in two scopes, and they map to Descope's two scopes:\n\n- **Environment-level role** (defined on the WorkOS environment, available across all organizations) → **Descope project-level role** (applies across all tenants).\n- **Organization-scoped role** (a WorkOS \"custom role\", defined for a specific organization) → **Descope tenant-level role**, created by passing a `tenantId` to `management.role.create(...)` (it surfaces per-tenant when you check roles on the session, e.g. `validateTenantRoles`).\n\n\n| WorkOS | Descope |\n| --------------------------------------------------------- | ------------------------------------------------ |\n| `role` | `role` |\n| `permission` | `permission` |\n| Environment-level role (applies across all organizations) | Project-level role (applies across all tenants) |\n| Organization-scoped role (\"custom role\") | Tenant-scoped role |\n| `roleSlug` reference | Descope role **name** (not ID) |\n| IdP group → role mapping | Group-to-role mapping (SSO Configuration / SCIM) |\n\n\nSDK: `descopeClient.management.role.create(name, description, permissionNames, tenantId)` (verify\nthe exact function name depending on the language sdk). Pass `tenantId` to create a **tenant-level**\nrole (the equivalent of a WorkOS organization-scoped/custom role); omit it for a **project-level**\nrole (the equivalent of a WorkOS environment-level role). Roles must exist in the Console before\nassignment. Check whether each WorkOS role is environment- or organization-scoped and where checks\nhappen (middleware, API routes, DB queries, frontend). **Effort: Medium.**\n\n### Fine-Grained Authorization (FGA) → Descope ReBAC / AuthZ\n\nAuthorization model must be translated and validated. Schema translation example:\n\n```\n# WorkOS FGA\nResource type: project\nParent: workspace\n\nPermissions:\n- project:view\n- project:edit\n- project:delete\n\nRoles:\n- project-viewer\n - project:view\n\n- project-editor\n - project:view\n - project:edit\n\n# Descope ReBAC DSL\ntype document\n relation owner: user\n relation viewer: user\n permission can_view: owner | viewer\n```\n\n**Descope ReBAC schema DSL** — use this syntax to author the Descope ReBAC schema:\n\n```\nSyntax Description Example\n--------------------------------- ------------------- ----------------------------\ntype <name> Define a type type user\nrelation <name>: <type> Define a relation relation owner: user\n| Union (OR) operator user | group\n# Relation reference Group#member\n. Traverse relation parent.owner\npermission <name>: <expression> Define a permission permission can_edit: owner\n```\n\n\n| Operation | WorkOS FGA | Descope ReBAC |\n| -------------- | ------------------------- | ----------------------------------------------------- |\n| Write relation | `fga.writeWarrant({...})` | `descopeClient.management.fga.createRelations([...])` |\n| Check | `fga.check({...})` | `descopeClient.management.fga.check([...])` |\n\n\n(Verify exact WorkOS and Descope shapes against current docs.) Identify resources,\nrelationships/privileges, where checks run, and any hierarchical inheritance. **Effort: High** —\nrequire a dedicated model review.\n\n### Audit Logs → Descope Audit Webhook / Events\n\nWorkOS Audit Logs map to Descope audit events, the Audit Webhook Connector, or other connectors\ndepending on the use case. Determine whether the app writes events to WorkOS, reads them back, shows\nthem to customer admins, or requires them for compliance. **Effort: Medium.**\n\n### Radar → Descope Fingerprinting + Flow Security\n\n**Mechanism difference (read this first):** WorkOS Radar is a dashboard toggle layered on top of\nAuthKit — it collects device-fingerprint signals and *automatically* blocks / challenges / notifies\nbased on the actions you enable, with no app code. Descope has **no single equivalent toggle**.\nInstead you reproduce Radar's behavior by adding Descope's built-in fingerprinting/risk signals to\nyour **Flow** and branching on them. So \"configuring Radar\" becomes \"designing the Flow.\"\n\nDescope surfaces risk signals as `riskInfo` inside a Flow. `riskInfo.botDetected` and\n`riskInfo.riskScore` require adding a **Fingerprint / Assess** action immediately after the\nlogin/signup screen; `riskInfo.impossibleTravel` and `riskInfo.trustedDevice` do not. For stronger\ndetection, layer in fraud/CAPTCHA connectors (reCAPTCHA Enterprise, Turnstile, Telesign,\nFingerprint, Forter, Sardine).\n\n\n| Radar action | What it does in WorkOS | Descope equivalent |\n| ------------- | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Block** | Auth fails even with valid credentials | Flow conditional after Fingerprint Assess: on high `riskInfo.riskScore` / `riskInfo.botDetected`, branch to a deny/failure screen and end the Flow without issuing a session |\n| **Challenge** | Sends an email (or SMS) OTP step-up | Risk-based step-up in the Flow: branch the high-risk path into an OTP/MFA step or a CAPTCHA connector (reCAPTCHA / Turnstile) before continuing |\n| **Notify** | Sends an informational email to user/admin; sign-in still proceeds | Compose it: on the risk branch, fire an email/messaging connector or an outbound webhook (or rely on Descope audit events) to alert the user/admin, while letting the Flow continue |\n\n\n**Detection mapping** (verify current signal names against docs):\n\n- Bot detection → `riskInfo.botDetected` (needs Fingerprint Assess) + CAPTCHA connectors\n- Impossible travel → `riskInfo.impossibleTravel`\n- Unrecognized device → `riskInfo.trustedDevice` (invert: untrusted = unrecognized)\n- Brute force / repeat sign-up / stale accounts / managed lists (disposable email, sanctioned countries) / custom allow-deny restrictions → no single built-in signal; reproduce via `riskInfo.riskScore` thresholds, connectors, or custom Flow conditions. Flag any of these in use for dedicated design.\n\nAsk whether each Radar detection is set to **block, challenge, or notify**, and whether app logic\ndepends on its decisions (vs. pure config). **Effort: Medium** — Console/Flow configuration rather\nthan app code, but the decisioning must be *rebuilt* in the Flow, not simply toggled on.\n\n### Pipes → Descope Outbound Apps\n\nWorkOS Pipes (connected third-party accounts with OAuth token storage/refresh) maps to Descope\nOutbound Apps.\n\nUsers connect accounts client-side:\n\n```\nsdk.outbound.connect(appId, { redirectURL, scopes })\n```\n\nFetch stored tokens server-side:\n\n```\nPOST https://api.descope.com/v1/mgmt/outbound/app/user/token\nAuthorization: Bearer {projectId}:{managementKey}\nBody: { \"appId\": \"google-calendar\", \"userId\": \"U2abc...\", \"scopes\": [...] }\n```\n\nAsk which providers are connected, where tokens are used (including AI agents / background jobs), and\nwhether users must reconnect accounts or tokens can be migrated. **Effort: Medium.**\n\n### Webhooks / Events → Descope Webhooks / Connectors / Events\n\n\n| WorkOS | Descope |\n| ---------------------------------------------------- | ------------------------------------------------- |\n| Webhook endpoint + signing secret | Descope webhook/connector + signature validation |\n| `user.created` / `organization.`* / `dsync.`* events | Corresponding Descope events / connector triggers |\n\n\nSearch the codebase for webhook handlers; update event names, signature/validation logic, and\npayload handling. Identify which event types are business-critical. **Effort: Medium.**\n\n### Domain Verification / Custom Domains → Descope Custom Domains and Tenant Routing\n\nWorkOS Domain Verification maps most closely to Descope’s tenant SSO domain verification, where the customer proves ownership of their email domain with a DNS TXT record. Descope Custom Domains are separate: they configure a CNAME such as auth.example.com so Descope authentication endpoints, cookies, OAuth callbacks, and related URLs can use your own domain.\na custom auth domain. **Effort: Low–Medium.**\n\n### Widgets → Descope Widgets\n\nWorkOS Widgets (org switching, Directory Sync setup, SSO setup, domain verification, audit log\nstreaming, API keys) should be evaluated against Descope Widgets. If a Descope Widget covers the\nworkflow, prefer it over custom migration code. See `references/flows-and-widgets.md` → Widgets.\n\n### MCP Auth / Connect → Deeper Review Required\n\nMay map to Descope Inbound Apps, OAuth app patterns, or custom MCP authorization. **Do not generate\nimplementation code until the exact WorkOS usage is understood.** Determine whether WorkOS is acting\nas an OAuth provider, an OAuth client, or both. **Effort: Medium–High — flag for dedicated review.**\n\n### Vault → Possibly Out of Scope\n\nWorkOS Vault and EKM (encrypting/storing/controlling access to sensitive data) may have no direct Descope\nidentity equivalent. Flag it separately and ask whether it is part of the identity migration or a\nseparate secrets/data-security effort. **Do not present it as an SDK swap.**\n\n### Feature Flags → Usually Out of Scope\n\nWorkOS Feature Flags are usually not part of an auth migration. If they are used for access control,\nsome behavior may map to Descope roles/permissions, but general feature flagging should be treated as\nout of scope.\n\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`. WorkOS AuthKit tokens may expose\nsome profile fields, so code that reads them directly 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 WorkOS's\n`organizationId` — 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### Sealed Sessions Become Signed JWTs\n\nWorkOS AuthKit uses an encrypted/sealed session cookie protected by `WORKOS_COOKIE_PASSWORD`.\nDescope issues a signed session JWT in the `DS` cookie (refresh in `DSR`). The sealing password is\nno longer needed, and code that unseals/inspects the WorkOS cookie must be replaced with Descope\nsession validation (`validateSession()`), which returns decoded JWT claims.\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 WorkOS `organizationId` (and `connectionId` / `directoryId`) 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 org off the WorkOS session changes\nshape: Descope exposes the active tenant as `dct` and membership as the nested `tenants` object,\nread off the validated session (ideally via SDK helpers). Grep for all `organizationId` reads and\nsort them into these two buckets — by-ID management calls vs. session reads — before updating.\n\n### One Token, Not Provider-Specific Access Tokens\n\nForward the Descope session JWT (`DS` cookie) as `Authorization: Bearer <DS>` to API servers. There\nis one session token; downstream services validate it with `validateSession()`.\n\n### No Drop-In Middleware\n\nDescope has no `authkitMiddleware()` equivalent package. The middleware is ~20 lines of custom code\nthat 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### Async Cascade: Trace All Callers Before Finishing\n\nWhen a shared utility becomes async, TypeScript accepts `await` on non-Promises without\nerror — so callers that forget `await` silently return a Promise object. Always grep for\nall call sites of any utility you make async and update them in the same pass.\n\n### SCIM Is a Lifecycle, Not a One-Time Import\n\nIf Directory Sync is in use, re-point the SCIM pipeline at Descope before cutover. A one-time user\nimport leaves provisioning broken the moment the directory pushes its next change.\n\n### Env Var Reduction\n\nWorkOS: `WORKOS_API_KEY`, `WORKOS_CLIENT_ID`, `WORKOS_REDIRECT_URI`, `WORKOS_COOKIE_PASSWORD` (4+).\nDescope: `DESCOPE_PROJECT_ID` only (+ `DESCOPE_MANAGEMENT_KEY` for management ops).\n\n---\n\n## Step 5: Automated Testing\n\nRun the app and verify it works — don't just hand over a checklist.\n\n### Phase 0: Final stale-import sweep (BLOCKING)\n\n```bash\ngrep -r \"@workos-inc\\|workos\\|authkit\" \\\n --include=\"*.ts\" --include=\"*.tsx\" --include=\"*.js\" --include=\"*.py\" --include=\"*.go\" \\\n --exclude-dir=node_modules --exclude-dir=.next --exclude-dir=dist \\\n .\n```\n\nIf this returns any results, **stop and fix them before proceeding**.\n\n### Phase 1: Install, compile, and start\n\n```bash\nnpm install # or: pip install -r requirements.txt / go mod tidy\n```\n\n```bash\nnpx tsc --noEmit # TypeScript\ngo build ./... # Go\nmvn compile -q # Java/Maven\n./gradlew compileJava compileKotlin # Java/Gradle\ndotnet build # .NET\n```\n\n**Do not proceed until compilation exits with zero errors.**\n\n**If compilation fails, diagnose by error message:**\n\n- `Cannot find module '@workos-inc/...'` → stale import; re-run Phase 0\n- `Property 'X' does not exist on type 'AuthenticationInfo'` → wrapper built against WorkOS shape; re-derive\n- `'await' expression is not allowed in synchronous contexts` → async cascade gap\n- `Object is possibly 'undefined'` on session fields → add null check or early return\n\n```bash\nnpm run dev # or: python main.py / go run . / flask run / etc.\n```\n\n### Phase 2: Run existing tests\n\n```bash\nnpm test # or: pytest / go test ./... / etc.\n```\n\nAuth-related test failures usually mean: a mock or fixture still uses WorkOS shapes, or a\ntest validates JWT claims that are now missing (e.g., `email` without a JWT Template), or a test\nstill uses `organizationId` 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 WorkOS 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 WorkOS concept to its Descope replacement\n2. **Behavioral differences and open questions** — numbered list of significant differences\n between the WorkOS and Descope implementations. For each item: WorkOS 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 (Directory Sync/SCIM, FGA, Pipes, MCP Auth), 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- WorkOS 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- Access Keys (M2M): [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)\n\n"
}SHA-256: d95d94ecd151cf833263deec00057bc9063cae5a5dc1a0e795281f7d25f1bbf0