← DescopeCONTENT HISTORY

Update to Descope

Snapshot Sep 30, 2026 · 22:52 UTC · version 1.0.0

Collection source: not recorded for this historical snapshot.

WHAT CHANGED · RULE-BASED ANALYSIS

First saved snapshot

No earlier snapshot is available to establish a change.

Compare saved observations

Download comparison JSON
Full technical diff · 0 changed fields
Full snapshot data
{
  "name": "okta-cis-to-descope",
  "description": "Use this skill whenever anyone asks about migrating from Okta Customer Identity Service (CIS) to Descope — whether they're a developer doing it themselves or a technical lead evaluating the move. Triggers on: \"how do I migrate from Okta\", \"replace Okta CIS with Descope\", \"we're moving off Okta\", \"Okta to Descope\", \"switch from Okta\", \"our app uses okta-auth-js / @okta/okta-react / @okta/okta-angular / @okta/oidc-middleware / okta-jwt-verifier and we want to use Descope instead\", or any question about Okta CIS features (Sign-On Policies, Authorization Servers, Authenticators, Identity Providers, Log Streams, Service Apps, scp claim) 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/backend-sdks.md",
      "size_in_bytes": 21621
    },
    {
      "relative_path": "references/flows-and-widgets.md",
      "size_in_bytes": 14722
    },
    {
      "relative_path": "references/implementation-nuances.md",
      "size_in_bytes": 42115
    }
  ],
  "skill_md_contents": "---\nname: okta-cis-to-descope\ndescription: >\n  Use this skill whenever anyone asks about migrating from Okta Customer Identity Service (CIS)\n  to Descope — whether they're a developer doing it themselves or a technical lead evaluating\n  the move. Triggers on: \"how do I migrate from Okta\", \"replace Okta CIS with Descope\", \"we're\n  moving off Okta\", \"Okta to Descope\", \"switch from Okta\", \"our app uses okta-auth-js /\n  @okta/okta-react / @okta/okta-angular / @okta/oidc-middleware / okta-jwt-verifier and we want\n  to use Descope instead\", or any question about Okta CIS features (Sign-On Policies, Authorization\n  Servers, Authenticators, Identity Providers, Log Streams, Service Apps, scp claim) in the\n  context of Descope. Works for any language or framework with a Descope SDK. Always use this\n  skill before producing migration guidance — do not rely on memory alone.\n---\n\n# Okta CIS → Descope Migration Skill\n\nThis skill guides self-service migrations from Okta Customer Identity Service (CIS) to Descope.\nIt runs in three parts:\n\n1. **MCP Check** — confirm whether the Descope Docs MCP is available and suggest installing it if not\n2. **Migration Plan** — gather context via triage questions, analyze the codebase's auth touchpoints, and produce a human-readable `MIGRATION-PLAN.md` for the user to review\n3. **Execution** — if the user confirms they want to proceed, execute the plan\n\nDo not collapse these parts or skip ahead. The plan must be reviewed before code changes begin.\n\n**Primary references** (all in this skill's directory):\n- `references/implementation-nuances.md` — verified migration patterns for each JS/TS framework, Okta CIS feature-to-Descope mappings, and known gotchas\n- `references/flows-and-widgets.md` — Descope terminology/lingo (Okta→Descope), Flow structure and templates, Widgets, SSO Setup Suite, Console-vs-code decision guide\n- `references/backend-sdks.md` — Python and Java backend migration patterns (Flask, FastAPI, Django, Spring Boot, management SDK, M2M)\n\n---\n\n## Guiding Principles\n\n**Console-first.** Before recommending SDK code for any user-facing auth feature, check whether the Console, a Flow, or a Widget covers the use case. Okta CIS is a low-code platform — users configure auth logic through the Okta Sign-In Widget, the visual policy builder (OIE), email customization, and the admin console. Descope has direct equivalents for all of these: Flows replace the visual policy builder, the Descope Flow component replaces the Sign-In Widget, Messaging Templates replace email customization, and Widgets replace custom management UIs. Engineers integrate once (SDK setup + session validation). All subsequent auth evolution — new methods, MFA changes, UI updates, branding — 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 — especially Inbound Apps vs. Federated Apps (the core Okta strategy fork), Flow vs. custom code, Widget vs. custom page, MFA inline vs. separate enrollment — use `AskUserQuestion` rather than proceeding with an assumption. The cost of a wrong assumption compounds across 20+ files. Always confirm whether the backend validates `scp` claims before recommending the Inbound Apps path.\n\n**MCP over memory.** When the Docs MCP is available (confirmed in Part 1), use `ask-question-about-descope` to verify every SDK method name, option shape, and return type before writing it. Do not fall back to \"verify the exact method name in the SDK type declarations\" as a hedge — just verify it directly.\n\n---\n\n## Part 1: MCP Check (BLOCKING)\n\nBefore doing anything else, check whether the Descope Docs MCP is available by calling\n`search-descope-docs` with a simple query (e.g., \"session validation\").\n\n**If the tool is available:** proceed to Part 2 immediately.\n\n**If the tool is not available**, show this message and use `AskUserQuestion` to ask whether\nthey want to install it first:\n\n> **Descope Docs MCP is not installed.**\n>\n> This skill uses the Descope Docs MCP to look up current API signatures, SDK methods, and\n> feature availability during migration. Without it, guidance is based on static training data,\n> which may be stale and can produce SDK calls that don't exist.\n>\n> You can install it in a few minutes at **https://docs-mcp.descope.com/** (server URL:\n> `https://docs-mcp.descope.com/mcp`). It significantly improves the accuracy of the\n> migration output — especially for SDK lookups and flow-specific configuration.\n>\n> **Would you like to install the MCP before we continue, or proceed without it?**\n\n- If they choose to install: pause and wait. Once they confirm it's installed, re-check by calling `search-descope-docs` again before proceeding.\n- If they choose to proceed without it: continue, but flag any SDK-specific answers as \"based on last known documentation — verify against the current SDK.\"\n\nDo not proceed to Part 2 until this step is resolved.\n\n---\n\n## Part 2: Migration Plan\n\nPart 2 has two sub-steps:\n\n1. **Triage** — ask the questions needed to understand scope\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 significantly based on these answers.\n\nDo not proceed to Step 0.5 until the user has answered.\n\n**Decision 0 — Login mode (resolve this before anything else):**\n\nAsk this as the first `AskUserQuestion`:\n\n> \"Is the app using Okta's **hosted/redirect login** — for example, `loginWithRedirect`, `@okta/oidc-middleware`, or users being sent to an Okta-hosted login page to authenticate? Or does it use an **embedded login UI** — the Okta Sign-In Widget embedded in the page, or a custom auth form built with `okta-auth-js` in non-redirect mode?\"\n\nDecision tree:\n```\nLogin mode?\n├── REDIRECT (hosted Okta page, loginWithRedirect, oidc-middleware, passport-openidconnect)\n│     → Default to OIDC path: update OIDC client config to point at Descope endpoints\n│       Set up Federated App or Inbound App in Console (Decision 1 determines which)\n│       No new login page, no new SDK required — redirect/callback plumbing stays intact\n│\n└── EMBEDDED (Okta Sign-In Widget in-page, custom okta-auth-js non-redirect flow)\n      → Default to embedded Descope Flow component path\n        Replace widget/form with <Descope flowId=\"sign-up-or-in\" />\n        Still determine Federated vs. Inbound App via Decision 1\n```\n\nDo not proceed until this is resolved — it determines the entire migration approach.\n\n---\n\n**Decision 1 — Inbound Apps vs. Federated Apps:**\n\nAsk as the second `AskUserQuestion` (applies to both login modes — it determines which type of app to configure in the Console):\n\n> \"Does the backend validate OAuth scopes from the Okta access token? (i.e., is there backend code that reads `token.scp`, `claims[\"scp\"]`, or similar to make authorization decisions?)\"\n\nDecision tree:\n```\nDoes any backend service validate token scopes (scp claim)?\n├── YES  → Inbound Apps path\n│          (Descope enforces scopes; custom claims go in JWT Template on the Inbound App)\n├── NO   → Federated Apps + OIDC layer\n│          (Okta used for identity only; often just update JWKS URL + Issuer, no scope changes)\n└── UNSURE → Ask them to grep: token.scp  claims[\"scp\"]  req.auth.scp\n             Then re-ask.\n```\n\nDo not proceed until this is resolved.\n\n---\n\n**Remaining triage — first `AskUserQuestion` call (up to 3 questions):**\n\n1. **Backend language / framework** — Present the most likely options based on cues in the conversation (Node.js/Express, Next.js, Angular, React SPA, Go, Python, Java). The user can always pick \"Other.\"\n2. **Migration goal** — Full cut-over, incremental/phased migration, or just evaluating.\n3. **Existing user base** — Are they migrating an app with active users in Okta, or starting fresh? This determines whether user migration planning is needed.\n\n**Second `AskUserQuestion` call — Okta CIS feature usage (use `multiSelect: true`):**\n\nWhich Okta CIS features are in use? Present these options:\n- Okta Sign-In Widget (`@okta/okta-signin-widget` — embedded login UI component)\n- Sign-On Policies (per-app auth rule chains / visual policy builder)\n- Authenticator Enrollment Policies (MFA factor requirements)\n- Authorization Servers / APIs (custom OAuth audiences and scopes)\n- Identity Providers (external SAML/OIDC SSO per customer org)\n- Authenticators (WebAuthn/Passkeys, TOTP, Okta Verify, SMS, etc.)\n- Log Streams (Splunk Cloud, Amazon EventBridge)\n- Service Apps / API Services (M2M / client credentials)\n- Token Inline Hooks (custom logic during auth)\n- Groups (used for RBAC/access control)\n\nThe user can add others via \"Other.\" Follow up on anything selected — e.g., if Authorization\nServers is selected, ask about custom claims using Okta Expression Language. If Authenticators\nis selected, ask which specific types.\n\nAfter both calls, summarize findings and flag high-complexity items (Token Inline Hooks with\nexternal dependencies, complex Sign-On Policy rule chains, custom Expression Language claims)\nbefore proceeding to Step 0.5.\n\n---\n\n### Step 0.5: Engineer Review Checkpoint (BLOCKING — requires `AskUserQuestion`)\n\nThese questions surface blockers the framework doesn't expose. Ask even the ones you think\nyou know. Use `AskUserQuestion` before proceeding to codebase analysis.\n\nBatch into calls of up to 4 questions. Skip questions that are clearly inapplicable given\nStep 0 answers (e.g., skip user migration planning if they said they're starting fresh).\n\n**Strategy confirmation**\n- Does the backend validate `scp` claims from the Okta access token? (If yes → Inbound Apps. If unsure, show them what to grep for: `token.scp`, `claims[\"scp\"]`, `req.auth.scp`.) — skip if already resolved in Decision 1\n- For redirect-mode apps: is the migration goal to keep the redirect flow (OIDC endpoint swap only) or eventually move to the embedded Descope Flow component? (The OIDC path is a valid permanent solution — not just a stepping stone.)\n- Are Sign-On Policies per-app, global, or both? (Determines scope of Flow migration.)\n- Is scope validation in application code or in an API gateway / JWT authorizer? (If gateway → just update JWKS URL and Issuer, no code change.)\n\n**Access and credentials**\n- Do they have access to the Descope Console and a Project ID? (If not, see Step 1.5.)\n- Do they need a Management Key? (Required for user CRUD, role management, tenant management, SCIM.)\n\n**Codebase scope**\n- Are there places in the app that read claims directly from the token (e.g., `token.scp`, `req.auth.permissions`, `token.groups`)? These need a JWT Template configured before they'll work.\n- Do they have Token Inline Hooks? Each one needs to be recreated as a Descope Flow Scriptlet or Generic HTTP Connector.\n- Are there multiple services or microservices validating Okta tokens? Each needs to be updated to validate Descope JWTs (or have its JWKS URL + Issuer updated if using an API gateway).\n\n**Deployment and risk**\n- Do they have multiple environments (dev / staging / prod)? Each needs its own Descope project and Project ID.\n- Is there a maintenance window, or does this need to be zero-downtime?\n\n**User migration** (if they indicated existing users in Step 0)\n\nThere are three migration paths — pick one or combine them. Confirm which fits before planning.\n\n- **Full migration**: Export all users from Okta (Management API `GET /api/v1/users`, paginated), transform attributes, and bulk-import into Descope before cutover. Use the Batch Create Users Management API directly. Optionally set a `freshlyMigrated` custom attribute to `true` on import to enable first-login Flow logic.\n- **JIT (password verification)**: Don't bulk-export. When a user signs in, verify their password against the Okta Authentication API (`POST /api/v1/authn`), then create or link the user in Descope and issue a Descope session. The user must re-enter credentials but no upfront export is needed.\n- **Session migration (JIT without re-login)**: The app sends the user's existing Okta session token to Descope; Descope validates it, provisions the user in Descope just-in-time, and issues a Descope token. The user only needs the app to update — no re-login. This is the highest-quality zero-disruption path. See [docs.descope.com/migrate/session-migration](https://docs.descope.com/migrate/session-migration).\n\n**Password constraint (all paths):** Okta does not export password hashes. For full migration, plan for a reset campaign, a first-login \"set new password\" Flow step, or a full switch to passwordless.\n\n**Dual-token validation (critical for phased rollouts):** During any gradual cutover, the backend will receive both Okta JWTs (from users not yet migrated) and Descope tokens. The backend must validate both — inspect the token issuer or `kid` to route to the correct validator. See `references/implementation-nuances.md` → Dual Token Validation.\n\n**Passkeys and TOTP cannot be migrated** — Okta does not expose these seeds. Users who enrolled passkeys or TOTP in Okta must reprovision them in Descope after migration.\n\n**Gaps to flag immediately** (don't ask — flag these proactively based on Step 0 answers)\n- If they're using **Passkeys or TOTP authenticators**: **these cannot be migrated**. Okta does not expose passkey credentials or TOTP seeds. Users will need to reprovision both in Descope after cutover — this requires a user-facing prompt (add a re-enrollment step to the sign-in Flow for affected users). Flag this early; it directly affects the user experience at launch.\n- If they're using **Okta Verify push notifications**: there is no direct equivalent in Descope. Recommend replacing with Email Magic Link, TOTP, or WebAuthn/Passkeys.\n- If they're using **Smart Card authenticator**: contact Descope support before migrating.\n- If they're using **Security Question authenticator**: no equivalent in Descope. Plan removal or replacement.\n- If they're using **Okta Workflows** (separate from CIS Policies): flag as out-of-scope for this skill — Workflows require a separate evaluation.\n- If they're using **Log Streams to Datadog**: Datadog is NOT a direct Okta Log Stream destination, and Descope has no native Datadog audit connector. Plan for a custom Audit Webhook.\n\n**Console/Flow/Widget opportunities** (flag before codebase analysis, then ask):\n- If the app embeds the **Okta Sign-In Widget** (`@okta/okta-signin-widget`): the migration is almost entirely Console-side. Embed the Descope Flow component (`<Descope flowId=\"sign-up-or-in\" />`) in the same location. No redirect required; the same low-code/no-code principle applies.\n- If the app uses Okta's **hosted/redirect login** (`loginWithRedirect`, `@okta/oidc-middleware`, or any redirect-based OIDC flow): **default to the OIDC path** — set up a Federated App or Inbound App in Console and update the issuer/client-ID env vars. Do NOT recommend replacing the redirect flow with an embedded Descope component unless the user explicitly wants that. See `references/implementation-nuances.md` → OIDC compatibility path and the Node.js + @okta/oidc-middleware section (Option A).\n- If the app has a custom SSO settings page: ask whether the SSO Setup Suite + Tenant Profile Widget replaces that code.\n- If the app has a profile edit page or user management UI: ask whether a Descope Widget covers the use case.\n- If the app has a separate MFA enrollment page: ask whether MFA should be integrated into the main sign-in Flow as a step or subflow (almost always cleaner in Descope).\n- If any server-side code generates emails or runs logic during the auth journey: ask whether that logic can be a Flow Scriptlet or Connector instead.\n\nSummarize any blockers and Console/Flow opportunities before proceeding to codebase analysis.\n\n---\n\n### Step 0.75: Fast-Track Assessment\n\nBefore running codebase analysis, determine whether the app qualifies for a minimal-code migration.\n\n**Fast-track A — OIDC redirect swap (all three must be true):**\n1. App uses **hosted/redirect login** (Decision 0 = redirect)\n2. **Decision 1 resolved to Federated Apps** (no backend scope validation)\n3. **No Token Inline Hooks** selected in the feature multiselect\n\n**If all three are true:** this is a minimal-config migration. The work is ~80% Console setup:\n- Create a Federated App in Console (Applications → Federated Apps → + Application); register the callback URL\n- Configure the Descope Flow linked to the app (auth methods, branding) — this replaces the Okta hosted login page\n- Update env vars: `OKTA_ISSUER` → `https://api.descope.com/DESCOPE_PROJECT_ID`; `OKTA_CLIENT_ID` → Project ID; `OKTA_CLIENT_SECRET` → a Descope Access Key\n- If using `@okta/oidc-middleware`: replace with `openid-client` (Okta's middleware is not confirmed to work with non-Okta issuers)\n- Check `scp` → `scope` claim rename in any backend authorization code (see `implementation-nuances.md` → scp vs. scope claim)\n- Configure JWT Template for `email`/`name` claims\n- No new login page, no SDK swap, no changes to callback routes\n\nSkip or abbreviate framework-specific code changes in Step 2. Codebase analysis is still useful to find stale Okta references and `scp` usages, but the diff will be small.\n\n---\n\n**Fast-track B — Embedded widget swap (all four must be true):**\n1. App embeds the **Okta Sign-In Widget** (`@okta/okta-signin-widget`) rather than a custom SDK-based auth flow\n2. **Decision 1 resolved to Federated Apps** (no backend scope validation)\n3. **No Token Inline Hooks** selected in the Step 0 feature multiselect\n4. **No Authorization Servers** with custom claims or resource policies selected in Step 0\n\n**If all four are true:** this is a minimal-code migration. The work is 90%+ Console-side:\n- Embed `<Descope flowId=\"sign-up-or-in\" />` (or the web component) where the widget was\n- Configure the Flow in the Console — auth methods, MFA steps, branding\n- Update env vars (`OKTA_*` → `DESCOPE_PROJECT_ID`)\n- That's most of the migration\n\nSkip or abbreviate Step 2 (framework-specific code changes). Codebase analysis is still useful to find any stale Okta references, but the diff will be small.\n\n**If neither fast-track applies:** proceed with full codebase analysis below.\n\n---\n\n### Step 1: Codebase Analysis\n\nScan the codebase and fetch Okta policies before writing the plan. Both are required — code analysis finds what changes, policy analysis determines how complex the Flow migration will be.\n\n**Step 1a — Code analysis (adapt file extensions to the user's language):**\n\n```bash\n# Find all Okta import sites\ngrep -rn \"okta-auth-js\\|@okta/okta-react\\|@okta/okta-angular\\|@okta/okta-vue\\|@okta/oidc-middleware\\|okta-jwt-verifier\\|@okta/jwt-verifier\\|@okta/okta-signin-widget\" \\\n  --include=\"*.ts\" --include=\"*.tsx\" --include=\"*.js\" --include=\"*.py\" --include=\"*.go\" \\\n  --exclude-dir=node_modules --exclude-dir=.next --exclude-dir=dist --exclude-dir=venv \\\n  . 2>/dev/null\n\n# Find all Okta env var references\ngrep -rn \"OKTA_\\|OKTA_CLIENT\\|OKTA_ISSUER\\|OKTA_DOMAIN\\|OKTA_AUDIENCE\\|OKTA_REDIRECT\" \\\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 scp / scope claim access patterns (things that need scp→scope update or JWT Template)\ngrep -rn \"\\.scp\\b\\|token\\.scp\\|claims\\[.scp.\\]\\|req\\.auth\\.scp\\|req\\.userContext\\|token\\.claims\\b\" \\\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 / auth guard declarations\ngrep -rn \"requiresAuth\\|OktaAuthGuard\\|loginWithRedirect\\|authGuard\\|isAuthenticated\\$\\|oktaAuth\\b\\|withRequiredAuthInfo\\|ensureAuthenticated\" \\\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 Okta dependencies\nfind . -maxdepth 3 \\( -name \"package.json\" -o -name \"go.mod\" -o -name \"requirements.txt\" \\) \\\n  ! -path \"*/node_modules/*\" -exec grep -l \"okta\" {} \\;\n```\n\n**Step 1b — Policy analysis (required before writing the plan):**\n\nPolicy rules determine how complex the Flow migration will be. Retrieve them now so MIGRATION-PLAN.md reflects the actual logic, not a generic template.\n\n```bash\n# Sign-On Policies (type=ACCESS_POLICY → called \"Sign-On Policies\" in the Okta Console)\n# Each one becomes a Descope Flow\ncurl -s -H \"Authorization: SSWS ${OKTA_API_TOKEN}\" \\\n  \"https://${OKTA_DOMAIN}/api/v1/policies?type=ACCESS_POLICY\" \\\n  | jq '[.[] | {name, id, ruleCount: (.rules | length), conditions: .conditions}]'\n\n# Authenticator Enrollment Policies (MFA requirements → Flow MFA steps or subflows)\ncurl -s -H \"Authorization: SSWS ${OKTA_API_TOKEN}\" \\\n  \"https://${OKTA_DOMAIN}/api/v1/policies?type=MFA_ENROLL\" \\\n  | jq '[.[] | {name, id, rules: [.rules[] | {priority, conditions, actions}]}]'\n\n# Global Session Policies (session lifetime → Console → Project Settings → Session Management)\ncurl -s -H \"Authorization: SSWS ${OKTA_API_TOKEN}\" \\\n  \"https://${OKTA_DOMAIN}/api/v1/policies?type=OKTA_SIGN_ON\" \\\n  | jq '[.[] | {name, id, rules: [.rules[] | {maxSessionIdleMinutes: .actions.signon.session.maxSessionIdleMinutes, maxSessionLifetimeMinutes: .actions.signon.session.maxSessionLifetimeMinutes}]}]'\n```\n\nFor each Sign-On Policy found, note: number of rules, conditions per rule (group, network zone,\ndevice), factors required per rule, and any post-auth hooks. This becomes the Flow complexity\nestimate in the plan.\n\nFor each hit, record:\n- **File path and line** — where the change happens\n- **What it does** — import, route protection, claim access, logout handler, etc.\n- **Complexity** — Low (drop-in replacement), Medium (logic rewrite), High (no equivalent)\n\nRead `package.json` (or equivalent) for the exact framework version — this affects async\nbehavior (Next.js 15 vs 14) and SDK compatibility.\n\nIf the Descope Docs MCP is available, use `search-descope-docs` or `ask-question-about-descope`\nto verify current SDK method names for anything you plan to reference in the plan.\n\n---\n\n### Step 2: Write MIGRATION-PLAN.md\n\nWrite `MIGRATION-PLAN.md` to the working directory using the triage answers and codebase\nanalysis.\n\nTwo audiences: the engineer needs enough technical detail to execute; the PM or tech lead\nneeds scope, risk, and timeline without decoding jargon. Use plain English. Explain\ntechnical terms on first use. Open each section with a sentence summarizing what it means\nbefore presenting tables or evidence. Say what breaks if a risk is missed, not just that it\nexists. Pair complexity labels with time estimates; skew toward the lower bound. Group\nexecution into phases so parallel vs. sequential work is clear.\n\nThe plan must include these sections, in this order:\n\n#### Overview\n\n2–3 sentences: what's being replaced, what replaces it, and the recommended approach with a\none-sentence rationale. Add one sentence on what doesn't change — user-facing login behavior,\nsessions, and existing accounts are preserved.\n\nInclude a **Migration at a Glance** table:\n\n| | |\n|---|---|\n| **Approach** | Inbound Apps (full native) / Federated Apps (OIDC layer) |\n| **Files changing** | N source files across N areas |\n| **Console setup** | N configuration steps before launch |\n| **User impact** | No re-login required / Users will need to log in once after cutover |\n| **Estimated engineering effort** | N–N hours |\n| **Biggest risk** | One sentence naming the highest-complexity item |\n\n---\n\n#### What's Changing and Why\n\nProse (not a table) describing what each part of the system does today and what it does\nafter. Example:\n\n> Today, Okta handles everything related to login: it shows the hosted sign-in page, issues\n> tokens, and validates them on every API request. After this migration, Descope takes over all\n> of those responsibilities. The login UI becomes a Descope Flow embedded in the app. Token\n> validation moves to the Descope SDK. The five Okta environment variables are replaced by a\n> single Descope Project ID.\n>\n> Okta CIS features in use that need to carry over: [list in plain English, one clause each].\n\nTailor to triage findings.\n\n---\n\n#### Auth Touchpoints: What the Code Analysis Found\n\nOpen with the scope count (e.g., \"9 files across 3 areas\"). Group by area, not file path.\nEach group gets a sentence on what it does and what changes.\n\n**Session handling (2 files)** — These files read and validate the current user's login\nstate. They'll be updated to use the Descope session SDK instead of Okta's.\n\n| File | What it does today | What changes |\n|---|---|---|\n| `middleware/auth.ts:22` | Validates Okta access token via `okta-jwt-verifier` | Rewritten to call `descopeClient.validateSession()`; `scp` → `scope` claim reference updated |\n| `lib/session.ts:8` | Returns `req.userContext.userinfo` | Updated to return Descope `AuthenticationInfo.token` |\n\nCover all functional groupings. End with: \"Total: N files. Estimated code-change effort: N–N hours.\"\n\n---\n\n#### Feature Migration: Okta CIS → Descope\n\nFor each Okta CIS 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. Only recommend SDK code when programmatic control is\ngenuinely required. Example:\n\n> **Sign-On Policies → Descope Flows**\n> Okta Sign-On Policies define per-app authentication rule chains: which factors are required,\n> in what order, and under which network or group conditions. Descope Flows replace this with a\n> visual pipeline where each rule becomes a Condition or step. The Sign-On Policy can be fetched\n> via `GET /api/v1/policies?type=ACCESS_POLICY` to understand the exact logic before building\n> the corresponding Flow. Most single-rule policies map to one Flow with a Condition branch.\n> **Effort: Low–Medium (30 min per simple policy, 2–3 hours for complex branching logic).**\n\nOnly include confirmed features.\n\n---\n\n#### Before the Code Can Run: Required Configuration\n\nList every Console setup item as a checkbox. Group into \"Required before any testing\" and\n\"Required before production\":\n\n**Required before any testing:**\n- [ ] **Create a Descope project** — Takes 2 minutes. Produces a Project ID that replaces all Okta credentials in the app's environment variables.\n- [ ] **Create an authentication Flow** — The built-in `sign-up-or-in` flow works for most apps without customization. Use it to start.\n- [ ] **Configure a JWT Template** — Okta ID tokens include `email` and `name` by default. Descope does not. In Console → **Project Settings → JWT Templates → + JWT Template → User JWT**, add claims with **Type: Dynamic**: `email` → `user.email`, `name` → `user.name`. Without this, any UI reading the user's name or email shows blank values. (~10 minutes)\n- [ ] **Configure authentication methods** — Enable the methods that match the Okta Authenticators in use (Passkeys, TOTP, SMS OTP, Email OTP/Magic Link). (~5 minutes each)\n\n**Required before production:**\n- [ ] **Create roles** (if using Groups for RBAC) — List actual roles found in codebase.\n- [ ] **Configure Tenant SSO** (if migrating Identity Providers) — Per-tenant SAML/OIDC setup via Console → SSO or SSO Setup Suite.\n- [ ] **Set up Audit Connector** (if using Log Streams) — Splunk Connector OOTB; custom webhook for EventBridge/Datadog.\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**For the OIDC path (redirect-based login):**\n\n| Remove | Add | Why |\n|---|---|---|\n| `OKTA_ISSUER` / `OKTA_DOMAIN` | — | Replaced by `DESCOPE_PROJECT_ID` in the issuer URL (`https://api.descope.com/PROJECT_ID`). |\n| `OKTA_CLIENT_ID` | `DESCOPE_PROJECT_ID` | For Federated OIDC Apps, the Project ID is the OIDC `client_id`. |\n| `OKTA_CLIENT_SECRET` | `DESCOPE_ACCESS_KEY` | For Federated OIDC Apps, an Access Key is the `client_secret`. Generate one in Console → Access Keys. |\n| `OKTA_AUDIENCE` | — | Handled by the Inbound App definition, if in use. |\n| — | `DESCOPE_MANAGEMENT_KEY` | Only needed if the app manages users, roles, or tenants server-side. |\n\n`OKTA_REDIRECT_URI` / callback URL stays — Descope's OIDC endpoints accept the same callback path.\n\n**For the embedded path (Descope Flow component):**\n\n| Remove | Add | Why |\n|---|---|---|\n| `OKTA_CLIENT_ID` | — | Okta identifies apps by client ID. Descope uses a Project ID instead. |\n| `OKTA_CLIENT_SECRET` | — | Not needed. Descope's embedded flow doesn't require a secret. |\n| `OKTA_ISSUER` / `OKTA_DOMAIN` | — | The Okta tenant URL. Replaced by the Project ID. |\n| `OKTA_AUDIENCE` | — | Used for API access scoping. Can be replicated via Inbound App + JWT Template if needed. |\n| `OKTA_REDIRECT_URI` | — | Descope's embedded flow doesn't use redirect URIs. |\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 Next.js client components. |\n| — | `DESCOPE_MANAGEMENT_KEY` | Only needed if the app manages users, roles, or tenants server-side. |\n\nFollow with: \"Net change: [N variables removed, N added — use the appropriate table above based on login mode].\"\n\n---\n\n#### User Migration (only if existing users need to be migrated)\n\nProse strategy first, then steps. Start with: \"X existing users need to be in Descope before cutover.\"\n\nKey Okta-specific constraint: **Okta does not export password hashes to third parties.** Users\nwill need to reset their passwords or switch to passwordless after cutover. Plan for one of:\n- A password reset email campaign sent before cutover\n- A \"set new password on first login\" step added to the Descope sign-in Flow\n- A full switch to passwordless (magic link, passkeys, TOTP)\n\nEnd with a brief PM-trackable checklist:\n- [ ] Choose password migration strategy — reset campaign, first-login step, or passwordless\n- [ ] Export user list from Okta (Management API `GET /api/v1/users` or Okta Reports)\n- [ ] Transform user data to Descope import format\n- [ ] Run import against the Descope dev project and review output for errors\n- [ ] Run import against staging, then production\n\n---\n\n#### Trade-offs and considerations\n\nWrite each in plain English with three parts: **what it is**, **what breaks if it's ignored**, **what to do**.\n\n> **Consideration: scp → scope claim rename (code change, always required)**\n> This is a JWT claim name change, separate from the Inbound vs. Federated App decision.\n> Okta access tokens carry scopes in `scp` (JSON array). Descope uses `scope` (space-separated\n> string or array). Any backend code reading `token.scp`, `claims[\"scp\"]`, or `req.auth.scp`\n> will receive `undefined` after migration and authorization checks will fail silently —\n> regardless of whether Inbound or Federated Apps are used.\n> **Action:** Grep for `.scp` in backend code before testing. Update all references to `.scope`\n> and handle both string and array formats. This is separate from configuring Inbound Apps\n> (which is about *whether* scopes are enforced, not the claim name).\n\n> **Consideration: User profile data won't appear after login until a JWT Template is configured**\n> Descope session tokens don't include `email` or `name` by default. Any UI that shows user\n> profile information will show blank values after migration.\n> **Action:** Configure the JWT Template in the Console before running any tests. (~10 minutes.)\n\n> **Consideration: Password migration is blocked by Okta policy**\n> Okta does not release password hashes. Password users will need to reset their passwords after\n> cutover.\n> **Action:** Decide on a migration strategy (reset campaign, first-login flow step, or switch\n> to passwordless) before setting a cutover date.\n\n> **Consideration: Passkeys and TOTP credentials cannot be migrated**\n> Okta does not expose passkey credentials or TOTP seeds. Users who enrolled these authenticators\n> in Okta must re-provision them after cutover — there is no way to migrate them silently.\n> **Action:** Add a re-enrollment step to the sign-in Flow conditioned on `freshlyMigrated: true`\n> and set user expectations before the cutover date.\n\n> **Consideration: Inbound Apps vs. Federated Apps misclassification**\n> If the backend validates `scp` claims from Okta access tokens and Federated Apps are configured\n> instead of Inbound Apps, the backend receives tokens with no `scope` claim and all scope\n> checks fail — likely silently.\n> **Action:** Confirm before Console setup whether any backend service validates token scopes.\n> If yes, configure Inbound Apps with scope definitions matching the Okta Authorization Server.\n\nInclude only applicable trade-offs and considerations.\n\n---\n\n#### Execution Plan\n\nPhases run in sequence. Steps within a phase can run in parallel.\n\n**Phase 1 — Console Setup** (~20–30 minutes, no code required)\nProject and credentials boilerplate. Nothing here depends on the codebase.\n\n- [ ] Create Descope project, copy Project ID\n- [ ] Generate Management Key (only if the app manages users, roles, or tenants server-side)\n- [ ] Enable authentication methods: (list actual methods matching Okta Authenticators found)\n- [ ] Configure JWT Template with `email`, `name`, and any custom claims\n- [ ] Set up Audit Connector: (Splunk / custom webhook, if Log Streams are in use)\n\n**Phase 2 — Flow Migration** (~1–4 hours, Console only, no code required)\nThe core work of an Okta migration. Translate Okta authentication policies into Descope Flows\nentirely through the Console — no code changes yet. Complexity scales with the number and\ncomplexity of policy rules.\n\n- [ ] Fetch Sign-On Policies: `GET /api/v1/policies?type=ACCESS_POLICY` — review all rules\n- [ ] Create Descope Flow for each Sign-On Policy (start from `sign-up-or-in` template; add Condition branches per rule)\n- [ ] Fetch Authenticator Enrollment Policies: `GET /api/v1/policies?type=MFA_ENROLL` — note required vs. optional factors\n- [ ] Add MFA steps or subflows to sign-in Flow for each required factor\n- [ ] Fetch Global Session Policies: `GET /api/v1/policies?type=OKTA_SIGN_ON` — note session lifetime values\n- [ ] Set session lifetime in Console → Project Settings → Session Management to match\n- [ ] Configure Tenant SSO: (list actual IdPs found, if any)\n- [ ] Create roles: (list actual roles found)\n\n**Phase 3 — Code Changes** (~X–Y hours, 1 engineer)\n\n- [ ] Update environment variables in `.env.example` and CI config\n- [ ] Replace Okta SDK imports with Descope SDK\n- [ ] Rewrite session validation middleware\n- [ ] Add `/login` page with `<Descope>` component (or web component)\n- [ ] Update protected route guards\n- [ ] Update logout handler (two steps: SDK call + cookie clear)\n- [ ] Update `scp` → `scope` claim references in backend code\n- [ ] Compile check and fix any type errors before proceeding\n\n**Phase 4 — User Migration** (~1–2 hours)\nRun import against dev/staging before production. Do not run against production until Phase 5 passes.\n\n**Phase 5 — Testing** (~30–45 minutes)\n\n- [ ] Compilation 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\n- [ ] Logout invalidates session\n- [ ] `scope` claim (not `scp`) is present if scopes are used\n\n**Phase 6 — Production Cutover**\n- [ ] (cutover-specific steps based on their strategy)\n\n---\n\nTotal estimated engineering effort: **N–N hours** across N engineers.\nBlocking dependencies: (list anything on the critical path)\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 Execution Plan from `MIGRATION-PLAN.md` (the final section, Phase 1 through 6). Follow the detailed guidance below for 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.\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- Login mode: [Redirect (OIDC path) / Embedded (Descope Flow component)]\n- App type in Descope Console: [Federated App / Inbound App]\n- Migration path: [OIDC endpoint swap / Embedded Flow component / Full SDK replacement]\n- Migration goal: [Full cutover / Phased / Evaluating]\n\n## Triage Answers\n- Existing users: [Yes — N users / No — greenfield]\n- Password migration strategy: [Reset campaign / First-login step / Passwordless]\n- Okta 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| `middleware/auth.ts` | Replace okta-jwt-verifier with Descope SDK | ⬜ Pending |\n| `src/App.tsx` | Replace Security/OktaAuth provider | ⬜ Pending |\n\n## Console Setup Checklist\n- [ ] Descope project created — Project ID: (fill in when done)\n- [ ] JWT Template configured\n- [ ] Auth methods enabled: (list)\n- [ ] Roles created: (list)\n- [ ] Tenant SSO configured: (list IdPs)\n\n## Decisions Log\n_Non-obvious decisions made during migration._\n\n_(none yet)_\n\n## Current Phase\nPhase 1 — Console Setup (not started)\n\n## Next Action\nComplete Phase 1 Console Setup, then Phase 2 Flow Migration (policy translation in Console) before touching any code.\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\nOutput a context line before each code change:\n\n> `Migration context: Next.js 14 · Inbound Apps · Phase 2, step 4/9 · Next: update scp→scope in middleware.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.\n\n---\n\n## Pre-Generation Protocol (apply before writing any code)\n\nRun before generating any import, wrapper type, or helper. Skipping produces code that\ncompiles but fails at runtime.\n\n**1. Verify SDK exports before writing any import.**\nWhen the Docs MCP is available, use `ask-question-about-descope` to confirm the exact method name, option shape, and return type before writing any SDK call. Do not write a method name and add a hedge like \"verify the exact name\" — just verify it.\n\nWhen the Docs MCP is unavailable: resolve the package's type declarations (`node_modules/<pkg>/dist/types/` or its `package.json` `types` field) and confirm the exact exported name and signature.\n\n**Prefer local `node_modules/` over GitHub** when reading type declarations. Installed packages reflect the exact version in use. Install the Descope package first if not yet installed, then read local type declarations.\n\n**1a. After rewriting any module, grep for remaining Okta imports.**\n```bash\ngrep -r \"@okta\\|okta-auth-js\\|okta-jwt-verifier\" --include=\"*.ts\" --include=\"*.tsx\" --include=\"*.js\" .\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. Okta's field names,\nnesting, 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` 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.\n\n**5. Verify published package versions before writing to `package.json` or running `npm install`.**\n```bash\nnpm view @descope/node-sdk version\nnpm view @descope/nextjs-sdk version\nnpm view @descope/react-sdk version\n```\nIf npm is unavailable, leave the version as `\"latest\"` and flag it.\n\n---\n\n## Step 1.5: Descope Project Setup & Console Configuration\n\nUse `AskUserQuestion` to ask whether they already have a Project ID and working Flow. If\nyes, skip to verifying items 5–7.\n\n### 1. Create a project and get your Project ID\n- Sign in at [console.descope.com](https://console.descope.com)\n- Your **Project ID** appears in the top-left project selector and under **Project → Settings**. It starts with `P` (e.g. `P2abc123...`).\n- For Next.js client-side code: `NEXT_PUBLIC_DESCOPE_PROJECT_ID`. For all server-side SDKs: `DESCOPE_PROJECT_ID`.\n\n### 2. Get a Management Key (if needed)\nRequired for: user management API, role/permission management, tenant operations, SCIM configuration.\n- Console → **Company → Management Keys → Generate Key**\n- Store as `DESCOPE_MANAGEMENT_KEY`. Never expose client-side.\n\n### 3. Choose or create a Flow\n- Console → **Authentication → Flows**\n- The built-in **\"sign-up-or-in\"** flow handles email OTP, magic link, social, and passkeys. Use it for most migrations.\n- To customize: duplicate \"sign-up-or-in\", rename it, then edit in the visual builder.\n- 100+ Flow templates in the library — check for an existing template before building from scratch. See `references/flows-and-widgets.md` → Flows.\n\n### 4. Configure authentication methods\n- Console → **Authentication** → select methods matching Okta Authenticators in use\n- Passkeys (FIDO2 WebAuthn), TOTP, Email OTP/Magic Link, SMS OTP\n- Note: Okta Verify push notifications have no direct equivalent — plan a replacement\n\n### 5. Configure a JWT Template (almost always needed)\nOkta ID tokens include `email` and `name` by default. Descope does not.\n- Console → **Project Settings → JWT Templates → + JWT Template → User JWT**\n- Under **Custom Claims**, add each with **Type: Dynamic**: `email` → `user.email`, `name` → `user.name`, `picture` → `user.picture`\n- For custom Okta Expression Language claims: recreate them with Type: Dynamic, value `user.customAttributes.X`\n- Without this step, any code reading `token.email` will get `undefined` after migration.\n\n### 6. Create roles in the Console (if using Groups for RBAC)\nDescope roles are referenced by **name**, not ID. They must be created in the Console before\nthe code that assigns them will work.\n- Console → **Authorization → RBAC → + Role**\n\n### 7. Define custom attributes (if using Okta user/group metadata)\nOkta user profile custom attributes map to Descope `customAttributes`. Pre-define them before\nsetting them via the SDK.\n- Console → **Project → Custom Attributes**\n\n### 8. Env var summary\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 |\n\n### 9. Consider Widgets for management UI\n\nBefore migrating custom profile pages or user management pages, ask whether a Descope Widget\ncovers the use case. See `references/flows-and-widgets.md` → Widgets.\n\n---\n\n## Step 2: Framework-Specific Migration\n\nRead `references/implementation-nuances.md` in two passes before writing any code:\n\n1. **General Insights** (always) — architecture, Inbound vs. Federated Apps decision, scp/scope claim, feature mapping, gotchas.\n2. **Framework section** — read only the section matching the user's stack:\n   - React + `@okta/okta-react` → `## React + @okta/okta-react` in `implementation-nuances.md`\n   - Angular + `@okta/okta-angular` → `## Angular + @okta/okta-angular` in `implementation-nuances.md`\n   - Vue + `@okta/okta-vue` → `## Vue + @okta/okta-vue` in `implementation-nuances.md`\n   - Node.js / Express + `@okta/oidc-middleware` → `## Node.js / Express + @okta/oidc-middleware` in `implementation-nuances.md`\n   - Backend JWT validation only → `## Backend JWT validation (okta-jwt-verifier)` in `implementation-nuances.md`\n   - Next.js → `## Next.js` in `implementation-nuances.md`\n   - Custom/open-source OIDC client → `## Custom / open-source OIDC clients` in `implementation-nuances.md`\n   - **Python** (Flask / FastAPI / Django) → `references/backend-sdks.md` → Python section\n   - **Java** (Spring Boot / standalone) → `references/backend-sdks.md` → Java section\n\n---\n\n## Step 2.5: Non-Code File Updates\n\nScan for Okta references in non-code files after updating source files.\n\n### `.env.example` / `.env.template` / `.env.sample`\n```\n# REMOVE\nOKTA_CLIENT_ID=\nOKTA_CLIENT_SECRET=\nOKTA_ISSUER=\nOKTA_DOMAIN=\nOKTA_AUDIENCE=\nOKTA_REDIRECT_URI=\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 (if using management SDK)\n```\nRun `grep -r \"OKTA\"` to find all env var references — `.env.example`, Docker, CI, shell scripts.\n\n### README / docs\nSearch all `.md` files for Okta references. At minimum, update:\n- **Setup section** — replace \"create an Okta application\" instructions with Descope Console setup steps\n- **Environment variables section** — reflect the reduced env var set\n- **Auth flow diagrams or descriptions** — update to reflect Descope's embedded approach\n\n### Docker / CI files\nCheck `Dockerfile`, `docker-compose.yml`, `.github/workflows/`, and any CI config for\n`OKTA_*` env var declarations. Update them to `DESCOPE_*`.\n\n---\n\n## Step 3: Feature Migration Mapping\n\n### Authentication Policies → Descope Flows\n\nOkta has **three distinct policy types**, each with a different Descope migration target. Treat them separately — don't collapse them into a single \"Flows\" step.\n\n#### Sign-On Policies (per-app) → Flow\n\nSign-On Policies define per-app rule chains: which factors are required, in what order, and under which conditions (group membership, network zone, device trust). Each rule becomes a Condition branch or auth step in a Descope Flow.\n\nFetch before building:\n```bash\ncurl -H \"Authorization: SSWS ${OKTA_API_TOKEN}\" \\\n  \"https://${OKTA_DOMAIN}/api/v1/policies?type=ACCESS_POLICY\"\n```\n\n| Okta Sign-On Policy rule | Descope Flow equivalent |\n|---|---|\n| Require factor X | Auth method step in Flow |\n| Condition: user in group Y | Condition branch on `user.roles` |\n| Condition: network zone | Condition branch on IP/request context |\n| Post-auth custom logic (Inline Hook) | Flow Scriptlet or Generic HTTP Connector |\n\nOne Sign-On Policy typically maps to one Flow. Complex branching logic (multiple rules with different factor requirements per condition) maps to Conditions + subflows.\n\n#### Authenticator Enrollment Policies → Flow (MFA step or subflow)\n\nAuthenticator Enrollment Policies control when users must enroll in MFA and which factors are required vs. optional. In Descope, enrollment happens **inline in the sign-in Flow**, not through a separate enrollment journey.\n\nFetch before building:\n```bash\ncurl -H \"Authorization: SSWS ${OKTA_API_TOKEN}\" \\\n  \"https://${OKTA_DOMAIN}/api/v1/policies?type=MFA_ENROLL\"\n```\n\nRead the policy's `required` vs `optional` authenticator list, then add an MFA step to the sign-in Flow for required factors, or use a subflow triggered by a condition (e.g., user is in an admin group) for context-sensitive enrollment.\n\n#### Global Session Policies → Project session config\n\nGlobal Session Policies control session lifetime, idle timeout, and re-authentication frequency. These map to Descope's project-level session settings, not to Flows.\n\nFetch before configuring:\n```bash\ncurl -H \"Authorization: SSWS ${OKTA_API_TOKEN}\" \\\n  \"https://${OKTA_DOMAIN}/api/v1/policies?type=OKTA_SIGN_ON\"\n```\n\nIn Descope: Console → **Project Settings → Session Management**. Map Okta fields to Descope fields correctly: `maxSessionLifetimeMinutes` → **Refresh Token Timeout** (total logged-in duration); `maxSessionIdleMinutes` → **Session Inactivity** (idle timeout). These are different fields — do not conflate them.\n\n---\n\n### Authenticators → Auth Methods\n\n| Okta Authenticator | Descope Auth Method |\n|---|---|\n| Passkeys (FIDO2 WebAuthn) | Passkeys |\n| TOTP (Google Authenticator, Okta Verify TOTP) | TOTP |\n| Password | Password |\n| Phone (SMS, Voice) | SMS OTP |\n| Email (magic link or OTP) | Email OTP / Magic Link |\n| Okta Verify (push) | No direct equivalent — replace with Email Magic Link, TOTP, or Passkeys |\n| Security Question | No equivalent — plan removal |\n\n### Identity Providers → Tenant SSO\n\n**Key selling point:** Descope can consume the existing IdP response using the same ACS URL already configured in the customer's IdP. Tenant admins do **not** need to reconfigure their SAML or OIDC settings — the migration is transparent to them. This is handled via DNS redirect at the Okta → Descope cutover. See [docs.descope.com/migrate/sso](https://docs.descope.com/migrate/sso) for the full process.\n\nBefore migrating any Management SDK SSO calls, ask whether the SSO Setup Suite eliminates the need for that code. See `references/flows-and-widgets.md` → SSO Setup Suite.\n\n| Okta | Descope |\n|---|---|\n| SAML Identity Provider (per-org) | `management.sso.configureSAMLByTenant(tenantId, settings)` |\n| OIDC Identity Provider (per-org) | `management.sso.configureOIDCByTenant(tenantId, settings)` |\n\n### Authorization Servers → Resources + Inbound Apps\n\n- Recreate the Authorization Server as a Descope Resource with the same audience string (immutable in both).\n- Move scope definitions from the Authorization Server to the Inbound App.\n- Move custom claims (Expression Language) from the Authorization Server to a JWT Template on the Inbound App.\n- Move resource-level policies (which scopes are accessible under which conditions) to Inbound App authorization rules.\n\n### RBAC: Groups → Descope Roles\n\n| Okta | Descope |\n|---|---|\n| `req.auth.groups.includes('admin')` | `token.roles.includes('admin')` |\n| Group membership via Okta | Role assignment via Management SDK or Console |\n| `groups` claim in token | `roles` array in JWT (built-in) |\n\nSDK: `descopeClient.management.role.create(name, description, permissionNames, tenantId)`\n\n### Service Apps → Access Keys\n\n| Okta | Descope |\n|---|---|\n| Service App (client ID + secret) | Access Key |\n| `POST /token` (client credentials) | `descopeClient.exchangeAccessKey(accessKey)` |\n\n### Log Streams → Audit Connectors\n\n| Okta Log Stream | Descope Connector |\n|---|---|\n| Splunk Cloud | Splunk Audit Connector (OOTB — Console → Connectors) |\n| Amazon EventBridge | Custom Audit Webhook Connector |\n| Datadog (indirect) | Custom Audit Webhook Connector |\n\nSet up before cutover to avoid gaps in event logging.\n\n### Token Inline Hooks → Flow Scriptlets / Connectors\n\n| Okta Inline Hook type | Descope equivalent |\n|---|---|\n| Token Inline Hook (modify claims) | Flow Scriptlet or JWT Template |\n| Token Inline Hook (call external service) | Generic HTTP Connector |\n\n### User Migration\n\nSee [docs.descope.com/migrate/okta-cis](https://docs.descope.com/migrate/okta-cis) for the authoritative guide. Three paths:\n\n#### Path 1: Full migration (bulk export → import)\n\nExport all users from Okta, import into Descope before cutover. Use the Descope Batch Create Users API directly.\n\n```bash\n# Export users (paginated — max 200 per page)\ncurl -H \"Authorization: SSWS ${OKTA_API_TOKEN}\" \\\n  \"https://${OKTA_DOMAIN}/api/v1/users?limit=200\"\n# For next page, use ?after=${lastUserId} from the Link header\n```\n\n**Attribute mapping:**\n\n| Okta field | Descope field |\n|---|---|\n| `profile.login` or `profile.email` | `loginId` (required; unique per user) |\n| `profile.firstName` | `givenName` |\n| `profile.lastName` | `familyName` |\n| `profile.email` | `email` |\n| Custom profile fields | `customAttributes` |\n\nImport via Management SDK: `management.user.createBatch([...users])`\n\nSet `freshlyMigrated: true` as a custom attribute on import — use this in Flow Conditions to route newly-migrated users through a first-login experience (password reset prompt, re-enrollment for TOTP/passkeys), then flip it to `false` once done.\n\n**Alternative — own data store:** If Okta sits in front of your own database (via On-prem SCIM Server Agent or Access Gateway), you own the user data. Connect that same DB to Descope via a Generic HTTP Connector in your Flow and sever Okta from the path — no export/import needed.\n\n#### Path 2: JIT migration (password verification)\n\nDon't bulk-export. When a user signs in, verify their password against Okta's Authentication API, then create or link the user in Descope and issue a Descope session. The user must re-enter credentials once.\n\n```bash\ncurl -X POST \\\n  -H \"Accept: application/json\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Authorization: SSWS ${OKTA_API_TOKEN}\" \\\n  -d '{\"username\": \"user@example.com\", \"password\": \"...\"}' \\\n  \"https://${OKTA_DOMAIN}/api/v1/authn\"\n```\n\nOn success: create or link the user in Descope via Management SDK, then issue a Descope session. The Okta session is retired; subsequent sign-ins go directly through Descope.\n\n#### Path 3: Session migration (JIT without re-login)\n\nThe highest-quality zero-disruption path. Deploy a new app version using the Descope SDK with session migration enabled. When a user opens the app with an existing Okta session token, Descope validates it, provisions the user just-in-time, and issues a Descope token — no re-login, no interruption. See [docs.descope.com/migrate/session-migration](https://docs.descope.com/migrate/session-migration).\n\n#### Password constraint (Paths 1 and 2)\n\nOkta does not export password hashes. For full migration, plan one of:\n1. Password reset campaign before cutover\n2. \"Set new password on first login\" step in the Descope sign-in Flow (use `freshlyMigrated` condition)\n3. Full switch to passwordless\n\n#### Passkeys and TOTP cannot be migrated\n\nOkta does not expose passkey credentials or TOTP seeds. Users who enrolled these in Okta must reprovision them in Descope. Add a re-enrollment step to the sign-in Flow conditioned on `freshlyMigrated: true`.\n\n### Email Templates → Descope Messaging Templates\nOkta email templates map to Descope [Messaging Templates](https://docs.descope.com/management/messaging-templates),\nconfigured per authentication method in the Console.\n\n### Custom Domains\nCNAME `auth.example.com` → `cname.descope.com`, verify in Console, then pass `baseUrl` to\nthe Descope SDK.\n\n---\n\n## Step 4: Critical Gotchas (Always Cover These)\n\n### scp vs. scope Claim\nOkta access tokens use `scp` (JSON array). Descope uses `scope` (array or space-separated string).\n\n```javascript\n// Okta\ntoken.scp.includes('read:invoices')  // JSON array\n\n// Descope — handle both formats\nconst scopes = Array.isArray(token.scope) ? token.scope : (token.scope || '').split(' ')\nscopes.includes('read:invoices')\n```\n\nThis is a silent correctness bug — not a compile error. Grep for `scp` in all backend code.\n\n### JWT Claims Are Not the Same\nDescope session JWTs contain `sub`, `amr`, `drn`, `tenants`, `roles`, `permissions`, and `dct`\nby default. They do **not** contain `email`, `name`, or `picture`. Okta ID tokens include\nthese by default.\n\n**Action required:** Configure a JWT Template before any testing.\n\n### Audience Validation Is Opt-In\nDescope session tokens have no `aud` claim by default. Apps using `OKTA_AUDIENCE` for\nAPI access control must configure a custom `aud` claim in JWT Templates and pass `audience`\nto `validateSession()`.\n\n### Logout Is Two Steps\n1. Call `descopeClient.logout(refreshToken)` to invalidate server-side\n2. Clear `DS` and `DSR` cookies\n\nSkipping either step leaves a broken state.\n\n### Server-Side Profile Updates Don't Immediately Reflect in the Session Token\nProfile changes via the Management SDK don't update the JWT already in the browser. Options:\n\n1. **Wait for auto-refresh** (~5 min default) — no code required; tolerable for most apps.\n2. **`useDescope().refresh()` client-side** — triggers an immediate token refresh.\n3. **User Profile Widget** — if building a profile edit page, the Widget handles updates and refresh automatically.\n\n### Cookie Names Are Configurable\nDefault: `DS` (session JWT), `DSR` (refresh JWT). Configure custom names in the Descope\nConsole under the Flow's End action when running multiple Descope projects on the same root domain.\n\n### One Token, Not Two\nOkta issues separate ID tokens and access tokens. Descope has one token: the session JWT\n(`DS` cookie). Forward it as `Authorization: Bearer <DS>` to API servers.\n\n### No Drop-In Middleware\nDescope has no `@okta/oidc-middleware` equivalent. The middleware is ~20 lines of custom code\n(see `references/implementation-nuances.md` → Node.js / Express section).\n\n### `cookies()` and `headers()` Are Async in Next.js 15\nCheck `package.json` for the Next.js version. If ≥ 15: write `await cookies()` and mark\nthe containing function `async`. This cascades to all callers — grep for all call sites.\n\n### Dual Token Validation During Phased Rollouts\nDuring any gradual cutover, the backend will receive both Okta JWTs (from users not yet migrated) and Descope tokens (from users already migrated). If you don't handle both, migrated users break on un-updated backends and vice versa.\n\nInspect the token's issuer (`iss`) or key ID (`kid`) to determine the provider, then route to the correct validator:\n- Okta tokens: `iss` is `https://YOUR_DOMAIN.okta.com/oauth2/...`\n- Descope tokens: `iss` is `https://api.descope.com/YOUR_PROJECT_ID`\n\nThis dual-validation window can be as short as one deployment cycle or as long as weeks depending on rollout speed. Remove the Okta validator once all sessions have expired or been migrated.\n\nSee [docs.descope.com/migrate/session-migration#step-1-dual-token-validation-in-your-backend](https://docs.descope.com/migrate/session-migration#step-1-dual-token-validation-in-your-backend).\n\n### Env Var Reduction\nOkta: `CLIENT_ID`, `CLIENT_SECRET`, `ISSUER`, `AUDIENCE`, `REDIRECT_URI` (5+).\nDescope: `DESCOPE_PROJECT_ID` only (+ `DESCOPE_MANAGEMENT_KEY` for management ops).\n\n---\n\n## Step 5: Automated Testing\n\nRun the app and verify it works — don't just hand over a checklist.\n\n### Phase 0: Final stale-import sweep (BLOCKING)\n\n```bash\ngrep -r \"@okta\\|okta-auth-js\\|okta-jwt-verifier\\|okta-signin-widget\\|OKTA_\" \\\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\n```\n\n**Do not proceed until compilation exits with zero errors.**\n\n**If compilation fails, diagnose by error message:**\n- `Cannot find module '@okta/...'` → stale import; re-run Phase 0\n- `Property 'X' does not exist on type 'AuthenticationInfo'` → wrapper built against Okta 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: go run . / python main.py\n```\n\n### Phase 2: Run existing tests\n\n```bash\nnpm test   # or: pytest / go test ./...\n```\n\nAuth-related test failures usually mean: a mock or fixture still uses Okta shapes, or a\ntest validates JWT claims that are now missing (e.g., `email` without a JWT Template), or\n`scp` → `scope` claim wasn't updated in the test fixture.\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\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 custom claims are present. Verify `scope` claim (not `scp`)\nis present if scopes are in use.\n\n### Phase 5: Report results\n\n```\n## Test Results\n\n**Server startup:** ✅ Started successfully on port 3000\n**Existing tests:** ✅ 12 passed / ❌ 2 failed (list failures)\n**Unauthenticated /dashboard:** ✅ 302 → /login\n**Unauthenticated /api/protected:** ✅ 401\n**Login page loads Descope component:** ✅\n**JWT claims (email, name):** ✅ Present / ❌ Missing — JWT Template not yet configured\n**scope claim (not scp):** ✅ Present / ❌ Missing — update backend scope-validation code\n\n**Blockers before going live:**\n- [ ] (list anything that failed or needs manual action)\n```\n\n**Do not proceed to Step 6 until ALL of the following are true:**\n- [ ] Phase 0 grep returns zero Okta 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 Okta CIS concept to its Descope replacement\n\n2. **Behavioral differences and open questions** — numbered list of significant differences\n   between the Okta and Descope implementations. For each item: Okta behavior, Descope\n   behavior, action required.\n\n3. **Pre-deploy checklist** — actionable checkbox items for everything that must happen\n   before the migrated app can run. Prominently include all Console setup tasks.\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 (Token Inline Hooks, custom Expression Language claims, complex\nSign-On Policy rule chains), flag the high-effort items explicitly with estimated complexity\n(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 cases for each JS/TS framework and Okta CIS feature.\n- `references/flows-and-widgets.md` — Okta→Descope lingo map, Flow structure, Widgets, SSO Setup Suite, Console-vs-code decision guide.\n- `references/backend-sdks.md` — Python and Java backend migration patterns (Flask, FastAPI, Django, Spring Boot, management SDK, M2M access keys).\n- Descope Docs: https://docs.descope.com\n- Descope Migration Guide: https://docs.descope.com/migrate\n- Descope OIDC Endpoints: https://docs.descope.com/getting-started/oidc-endpoints\n- Descope Flows: https://docs.descope.com/flows\n- JWT Templates: https://docs.descope.com/management/jwt-templates\n- Access Keys (M2M): https://docs.descope.com/management/m2m-access-keys\n- Messaging Templates: https://docs.descope.com/management/messaging-templates\n- Audit Webhook: https://docs.descope.com/connectors/connector-configuration-guides/network/audit-webhook\n- Custom Domains: https://docs.descope.com/how-to-deploy-to-production/custom-domain\n- ReBAC: https://docs.descope.com/authorization/rebac\n- Session Migration: https://docs.descope.com/migrate/session-migration\n\n### Session Validation by Language\n- Node.js: https://docs.descope.com/getting-started/nodejs#implement-session-validation\n- Python: https://docs.descope.com/getting-started/python#implement-session-validation\n- Go: https://docs.descope.com/getting-started/golang#implement-session-validation\n- Ruby: https://docs.descope.com/getting-started/ruby#implement-session-validation\n- Java / Kotlin: https://docs.descope.com/getting-started/java#implement-session-validation\n- .NET / C#: https://docs.descope.com/getting-started/dotnet#implement-session-validation\n- Next.js: https://docs.descope.com/getting-started/nextjs#implement-session-validation\n- React: https://docs.descope.com/getting-started/react#implement-session-validation\n- Angular: https://docs.descope.com/getting-started/angular#implement-session-validation\n- Vue: https://docs.descope.com/getting-started/vue#implement-session-validation\n- Swift / iOS: https://docs.descope.com/getting-started/swift#implement-session-validation\n- Kotlin / Android: https://docs.descope.com/getting-started/android#implement-session-validation\n- Flutter: https://docs.descope.com/getting-started/flutter#implement-session-validation\n\n### SDKs (GitHub)\n- Node SDK: https://github.com/descope/node-sdk\n- Python SDK: https://github.com/descope/python-sdk\n- Go SDK: https://github.com/descope/go-sdk\n- Ruby SDK: https://github.com/descope/descope-ruby-sdk\n- Java SDK: https://github.com/descope/descope-java\n- .NET SDK: https://github.com/descope/descope-dotnet\n- Swift SDK: https://github.com/descope/swift-sdk\n- Kotlin SDK: https://github.com/descope/descope-kotlin\n- Flutter SDK: https://github.com/descope/descope-flutter\n- JS/TS monorepo (React, Angular, Vue, Next.js, Web Component, Web JS): https://github.com/descope/descope-js\n\n### Okta CIS Reference\n- Authenticators overview: https://developer.okta.com/docs/guides/authenticators-overview/main/\n- Policies concept: https://developer.okta.com/docs/concepts/policies/\n- Policy Management API: https://developer.okta.com/docs/api/openapi/okta-management/management/tag/Policy/\n- Authorization Servers: https://developer.okta.com/docs/concepts/auth-servers/\n- Identity Providers: https://help.okta.com/oie/en-us/content/topics/security/identity_providers.htm\n- Log Streams: https://help.okta.com/oie/en-us/Content/Topics/Reports/log-streaming/about-log-streams.htm\n- Client Credentials (M2M): https://developer.okta.com/docs/guides/implement-grant-type/clientcreds/main/\n"
}

SHA-256: 3eff1765a9f032aa7daeb910927a96d718b4dfddba158c663c858a490aa52853