Stytch Migration Guide
Migration Skill Available
Want a faster, guided migration? The Stytch to Descope Migration Skill in descope/skills inventories the Stytch features and authentication touchpoints in your application, then produces a migration plan before making code changes.
This guide covers a full migration, where Descope becomes the identity provider and source of truth for users, organizations, sessions, and machine identities. A complete migration includes four connected parts:
- Identity data - Consumer Users, B2B Organizations and Members, profile attributes, roles, and permissions.
- Application integration - Stytch UI and SDKs, protected routes, session validation, refresh, and logout.
- B2B configuration - Organization-specific authentication settings, SSO, SCIM, and member authorization.
- Machine identities - M2M clients, client credentials, scopes, token audiences, and secret rotation.
You can roll out the migration in phases, but every Stytch dependency must be migrated before you retire the Stytch project and credentials.
Stytch and Descope Concepts
| Stytch | Descope | Migration notes |
|---|---|---|
| Project and its Test/Live environments | Project per environment | Use separate Descope projects for development, staging, and production. |
| Consumer User | User | A project-level identity with one or more login IDs. |
| Organization | Tenant | One Stytch Organization normally maps to one Descope Tenant. |
| Member | User with a tenant association | A Descope User can belong to multiple tenants through userTenants, with different roles in each tenant. |
organization_id in a Member Session | dct and tenants claims | dct identifies the active tenant. tenants contains each tenant's roles and permissions. |
| Stytch UI or headless authentication | Descope Flow | A hosted or embedded Flow defines sign-up, sign-in, MFA, recovery, and other authentication journeys. |
Opaque session_token and signed session_jwt | Signed session and refresh JWTs | Descope uses a session JWT and refresh JWT. The Next.js SDK stores them in the DS and DSR cookies by default. |
| RBAC Resource, Action, Permission, and Role | Permission and Role | Map each resource_id and action pair to a stable Descope permission name, then group permissions into roles. |
| Organization SSO Connection | Tenant SSO | Recreate each SAML or OIDC connection for the corresponding tenant. |
| M2M client and client credentials | Resource, confidential Inbound App, and Policy | Preserve OAuth scopes and audiences through the client credentials grant. Use an Access Key only for simpler internal service authentication. |
Full Migration
Prerequisites
Before exporting data, ensure you have:
- A Stytch Project ID and secret with access to the Consumer or B2B resources you need to export.
- An inventory of every Stytch environment and whether it uses Consumer Auth, B2B Auth, or both.
- An inventory of authentication methods, Organizations, Members, RBAC settings, SSO connections, SCIM directories, M2M clients, webhooks, and application code that reads Stytch IDs or token claims.
- A non-production Descope project for testing, plus its Project ID.
- A Descope Management Key. Keep it on the server and never expose it to a browser or mobile application.
- A cutover and rollback plan, including how you will handle writes that occur between the initial export and cutover.
1. Export Data from Stytch
Stytch uses different export APIs for Consumer and B2B projects. All three Search APIs use cursor pagination. Request up
to 1,000 records per page, save each response, and continue with results_metadata.next_cursor until it is empty.
Choose the API host that matches your Stytch credentials:
| Stytch credentials | API host |
|---|---|
project-test-* and secret-test-* | https://test.stytch.com |
project-live-* and secret-live-* | https://api.stytch.com |
Using Test credentials against the Live host, or Live credentials against the Test host, returns a project_not_found
error even when the credentials themselves are valid.
Consumer Auth
Use the Stytch Search Users endpoint to export all Consumer Users:
curl --request POST \
--url "${STYTCH_API_URL}/v1/users/search" \
--user "${STYTCH_PROJECT_ID}:${STYTCH_SECRET}" \
--header "Content-Type: application/json" \
--data '{"limit":1000}'Export each User's login identifiers, verification state, name, status, roles, metadata, authentication registrations,
and original user_id. Stytch also provides a linked
user export utility for writing Consumer Users to CSV or JSON.
B2B Auth
Export Organizations before Members because every Member and tenant-scoped role depends on the Organization mapping.
| Data | Stytch endpoint | Export details |
|---|---|---|
| Organizations | Search Organizations | Save the Organization ID, name, slug, domains, authentication settings, custom roles, SSO references, SCIM reference, and metadata. |
| Members | Search Members | Pass one or more exported organization_ids. Save the Member ID, Organization ID, email, status, name, roles, verification state, registrations, and metadata. |
The Search APIs have different rate limits. Stytch currently documents 150 requests per minute for Consumer Users, 100
requests per second for Organizations, and 100 requests per minute for Members. Process 429 responses with backoff and
use the current endpoint documentation as the source of truth when you run the export.
Password Migration
Stytch's Search APIs do not export password hashes. Request a password hash export from Stytch Support and verify that its algorithm and parameters match Descope's supported password formats. If a compatible hash export is unavailable, use one of these options, both of which can be handled through Descope Flows.
-
Require Password Reset on First Login You can configure a Flow to prompt users to reset their password the first time they sign in through Descope. After resetting, users will authenticate using their newly set password for future logins. Learn more on our Flows page.
-
Move to Passwordless Authentication Alternatively, you can adopt a fully passwordless approach. Descope supports a variety of passwordless authentication methods. Explore them on our Authentication Methods page.
Note
This option should only be used if you have a verified email address for all users. Otherwise, we recommend enforcing a one time password reset for your users.
2. Map Stytch Data to Descope
Create a durable mapping file for every Stytch ID used by your application. Preserve it until application records, webhooks, SSO/SCIM configuration, and operational tools no longer depend on the legacy IDs.
| Stytch field or object | Descope field or object | Mapping notes |
|---|---|---|
Consumer emails[].email or Member email_address | loginIds, email | Use a canonical, case-normalized email as a login ID when email is the primary identifier. |
emails[].verified or email_address_verified | verifiedEmail | Never mark an unverified address as verified during migration. |
phone_numbers[].phone_number or mfa_phone_number | loginIds, phone | Normalize phone numbers to E.164 and preserve verifiedPhone accurately. |
Consumer name or Member name | givenName, middleName, familyName, name | Preserve the original display name where available. |
| User or Member status and lock state | status and migration policy | Decide how pending, invited, deleted, and locked records should behave before transforming them. |
trusted_metadata, untrusted_metadata, and external_id | Predefined customAttributes or externalIds | Define custom attribute keys and types in Descope before importing users. Do not blindly copy untrusted metadata into privileged fields. |
user_id, member_id, and organization_id | Migration mapping file and optional custom attributes | Preserve legacy IDs for traceability and idempotent re-runs; do not use them as human login IDs. |
| Organization | Tenant | Reusing the Stytch organization_id as the Descope tenant ID can simplify downstream mapping when that ID is suitable for long-term use. |
| Member | User plus userTenants entry | Associate the User with the mapped tenant and its tenant-scoped role names. |
Organization custom_roles[].permissions[] | Tenant-level Roles and Permissions | Convert each Resource and Action pair to a stable permission such as documents.read. |
Stytch Members are scoped to Organizations, while Descope Users are project-level. If the same verified email represents
one person in multiple Stytch Organizations, create one Descope User and aggregate all tenant memberships in
userTenants. Do not merge records solely because their email strings match: first resolve conflicting profile data,
verification state, status, or evidence that the records represent different people.
Example transformed B2B user:
{
"loginIds": ["ada@example.com"],
"email": "ada@example.com",
"name": "Ada Lovelace",
"verifiedEmail": true,
"status": "enabled",
"userTenants": [
{
"tenantId": "organization-live-acme",
"roleNames": ["admin"]
}
],
"customAttributes": {
"freshlyMigrated": true
}
}The optional freshlyMigrated attribute lets a Descope Flow route migrated users through password reset, contact
verification, or authenticator re-enrollment and clear the flag when the migration journey is complete.
3. Import into Descope
Test the transformation with representative Consumer Users, single-tenant Members, multi-tenant Members, and disabled or invited records in a non-production project. Then import dependencies in this order:
- Permissions - Create the permission names your application checks. See RBAC with SDKs.
- Tenants - Create one tenant for each mapped Stytch Organization. See Tenant Management.
- Roles - Create project-level Consumer roles and tenant-level Organization roles after their permissions and tenants exist.
- Users and memberships - Use the Batch Create Users API or the
Management SDK's
createBatchmethod. Include all mapped tenant IDs and tenant-scoped role names inuserTenants.
Use createBatch, not an invitation operation, so the bulk load does not send unexpected invitation emails. Batch
requests can partially succeed, so save and inspect createdUsers, failedUsers, and additionalErrors after every
request. Correct the underlying data and retry only failed records.
See the user JSON formatting guide for Console imports. Use the API or Management SDK when you need to preserve tenant memberships, roles, password hashes, or precise status values.
4. Cut Over
- Freeze relevant writes in Stytch or keep both systems synchronized during the final migration window.
- Run a delta export for users, Organizations, Members, roles, and profile changes made after the initial export.
- Import and verify the delta, then switch the application and API services to Descope.
- Keep Stytch available for rollback until the post-migration checks pass.
- Disable Stytch sign-up and authentication only after traffic, audit events, and support signals confirm the cutover.
Existing Stytch sessions are not imported into Descope. A full cutover normally requires users to sign in once through Descope. If you need a phased transition, use the Session Migration guide to design dual-token validation or session handoff before switching all traffic.
Replace Stytch SDKs
SDK and Configuration Mapping
Replace each Stytch surface with the Descope SDK for the same application layer:
| Stytch integration | Descope replacement | Responsibility |
|---|---|---|
@stytch/react, @stytch/nextjs, or @stytch/vanilla-js | @descope/react-sdk, @descope/nextjs-sdk, or @descope/web-js-sdk | Render a Flow, hold and refresh the browser session, and provide current-user/session helpers. |
| Stytch UI or frontend headless calls | Hosted or embedded Descope Flow component | Sign-up, sign-in, OAuth, passwordless methods, MFA, recovery, and conditional authentication logic. |
| Stytch backend SDK or direct API calls | Matching Descope backend SDK | Validate sessions and enforce roles/permissions on protected APIs. |
| Stytch user, Member, and Organization management calls | Descope Management SDK with a Management Key | Manage users, tenants, roles, SSO, SCIM, and other administrative resources. |
STYTCH_PROJECT_ID and frontend public token | DESCOPE_PROJECT_ID or NEXT_PUBLIC_DESCOPE_PROJECT_ID | Public project identifier used to initialize client and backend authentication SDKs. |
STYTCH_SECRET | No authentication-flow equivalent | Descope client authentication does not require a project secret. Use DESCOPE_MANAGEMENT_KEY only for server-side management operations. |
Inventory and replace imports, environment variables, callback routes, cookie readers, session middleware, user/member lookups, Organization switching, authorization checks, logout handlers, and webhook verification before removing Stytch packages.
Next.js Example
For a Next.js application, replace the Stytch packages with the Descope SDK:
npm uninstall @stytch/nextjs stytch
npm install @descope/nextjs-sdkWrap the application with AuthProvider, then render the migrated authentication journey as a Descope Flow:
import { AuthProvider } from '@descope/nextjs-sdk';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<AuthProvider projectId={process.env.NEXT_PUBLIC_DESCOPE_PROJECT_ID!}>
<html lang="en">
<body>{children}</body>
</html>
</AuthProvider>
);
}'use client';
import { Descope } from '@descope/nextjs-sdk';
import { useRouter } from 'next/navigation';
export default function SignInPage() {
const router = useRouter();
return <Descope flowId="sign-up-or-in" onSuccess={() => router.replace('/dashboard')} />;
}Configure authentication methods, branding, MFA, and conditional logic in the Flow Builder, rather than recreating each Stytch frontend authentication call in application code.
Protect routes with authMiddleware() and preserve the same public-route allowlist used by the Stytch integration:
import { authMiddleware } from '@descope/nextjs-sdk/server';
export default authMiddleware({
projectId: process.env.NEXT_PUBLIC_DESCOPE_PROJECT_ID,
redirectUrl: '/sign-in',
publicRoutes: ['/', '/sign-in'],
});
export const config = {
matcher: ['/((?!.+\\.[\\w]+$|_next).*)', '/', '/(api|trpc)(.*)'],
};Use session() in App Router Server Components and route handlers:
import { session } from '@descope/nextjs-sdk/server';
export default async function DashboardPage() {
const currentSession = await session();
if (!currentSession) {
return <p>Access denied</p>;
}
return <p>Signed in as {currentSession.token.sub}</p>;
}Use useSession() and useUser() for client state, and use the SDK's logout helper so the refresh token is revoked:
'use client';
import { useDescope, useSession, useUser } from '@descope/nextjs-sdk/client';
export function UserMenu() {
const sdk = useDescope();
const { isAuthenticated, isSessionLoading } = useSession();
const { user, isUserLoading } = useUser();
if (isSessionLoading || isUserLoading) return <p>Loading...</p>;
if (!isAuthenticated) return null;
return (
<div>
<p>{user?.name ?? user?.email}</p>
<button onClick={() => sdk.logout()}>Log out</button>
</div>
);
}See the Next.js SDK guide for Pages Router session access and additional integration options.
Session and Claim Differences
Stytch's session_token is opaque and its session_jwt is a signed representation of the same underlying Session.
Descope instead issues a signed session JWT and a refresh JWT. In Next.js, the SDK stores these in DS and DSR
cookies by default and validates the session through authMiddleware() or session().
Account for these differences when replacing Stytch session code:
- User identity - Read the user ID from
sub. UseuseUser()or a user-management call for profile data. - Tenant context - Replace request-time
organization_idreads withdctfor the active tenant andtenantsfor all memberships and tenant-scoped roles or permissions. - Profile claims - Descope session JWTs do not include fields such as
email,name, orpictureby default. Read them from the User object or add the required claims with a JWT Template. - Audience - If an API currently validates Stytch's
audclaim, add the intended audience to the JWT Template and pass the expected audience to the backend SDK's session-validation method. - Refresh - Use the client SDK's
refresh()helper when profile, role, or tenant changes must appear immediately; otherwise the next automatic refresh obtains current claims. - Logout - Client SDK logout handles revocation and local state. For backend-managed logout, revoke the refresh token and clear both session and refresh cookies.
Migrate M2M Clients
Stytch M2M clients use the OAuth 2.0 client credentials grant and issue access tokens containing a client subject, scopes, an audience, expiration, and optional custom claims. Preserve that OAuth model with Descope Resources, confidential Inbound Apps, and Policies:
- Create a Resource for each protected API. Use its identifier as the token audience and define the API's scope catalog.
- Create a confidential Inbound App for each Stytch M2M client and store its new client secret securely.
- Create a Policy that grants the Inbound App the required scopes on the Resource through the
client_credentialsgrant. - Configure an Inbound App JWT Template for any required custom claims.
- Update the service to request tokens from Descope and update receiving APIs to validate the Descope issuer, JWKS, audience, expiration, and scopes.
| Stytch M2M | Descope |
|---|---|
| M2M client | Confidential Inbound App |
client_id and client_secret | Inbound App client ID and secret |
| Client credentials grant | Inbound App client_credentials grant permitted by a Policy |
| M2M scopes | Resource scopes granted to the Inbound App by the Policy |
Token aud | Resource identifier |
| Custom token claims | Inbound App JWT Template |
| Secret rotation | Inbound App client-secret rotation |
| Stytch token validation | Validate the Descope issuer, signature, aud, expiration, and scopes |
Follow the Inbound Apps client credentials guide for the token endpoint and request format. During cutover, issue the new credentials, update one service at a time, temporarily accept both issuers where necessary, and revoke each Stytch M2M client only after its traffic has moved to Descope.
Note
Use Descope Access Keys only when an internal service needs a Descope-issued JWT without
OAuth scope or audience enforcement. If an existing Stytch client depends on client_credentials, scope, or aud,
use Resources, an Inbound App, and a Policy.
Additional Stytch Features
| Stytch feature | Descope equivalent | Migration action |
|---|---|---|
| Enterprise SSO | Tenant SAML or OIDC SSO | Recreate each Organization connection on its mapped tenant. Use the SSO Setup Suite for customer-admin configuration. |
| SCIM | Tenant-scoped SCIM | Repoint each external directory and test provisioning, deprovisioning, groups, and role mapping before disabling Stytch. |
| B2B RBAC | Descope RBAC | Convert each Resource and Action pair into a permission, recreate roles, and update authorization checks. |
| First-party Connected Apps | Federated Apps | Recreate clients, redirects, claims, and token-validation expectations for applications your organization controls. |
| Third-party Connected Apps | Inbound Apps | Inventory OAuth clients, redirects, PKCE, consent, scopes, audiences, refresh behavior, and JWKS dependencies. |
| Admin Portal UI | Admin Portal and Widgets | Replace member, role, tenant, SSO, and SCIM management screens with hosted or embedded Descope experiences where possible. |
| MFA and step-up authentication | Flows | Recreate enrollment and step-up policy in a Flow; route migrated users through factor re-enrollment when required. |
| Fraud & Risk, Device Fingerprinting, and Protected Auth | Fingerprinting and fraud connectors | Rebuild allow, challenge, block, and notify decisions as Flow conditions using the selected risk signals. |
| Trusted Auth Tokens | Inbound Apps authorization server | Review issuer, JWKS, audience, subject, claim mapping, and provisioning, then use the JWT Bearer grant when it matches the exchange. |
| Webhooks and event streaming | Audit Trail and Audit Webhook | Update event names, signature verification, payload parsing, retries, and downstream side effects before cutover. |
Post-Migration Verification
Data and Authorization
- Compare exported and imported Consumer User counts, and resolve every failed or skipped record.
- Compare Stytch Organization counts with Descope Tenant counts and review the ID mapping file.
- Compare Member counts and verify representative single-tenant and multi-tenant users have every expected
userTenantsassociation. - Review pending, invited, disabled, deleted, and locked records against the migration policy.
- Confirm verified email and phone flags were preserved without promoting unverified identifiers.
- Confirm project-level and tenant-level roles, permissions, and application authorization checks match Stytch behavior.
- Verify legacy Stytch IDs and required metadata remain available in the mapping file or approved custom attributes.
Authentication and Application Integration
- Test every enabled login method, including password reset or passwordless fallback and required factor re-enrollment.
- Confirm protected routes reject unauthenticated requests while intended public routes remain public.
- Verify session and refresh tokens are created, refreshed, revoked, and cleared correctly on logout.
- Confirm APIs reject expired, invalid, and wrong-issuer tokens.
- Confirm
sub,dct,tenants, roles, permissions, custom profile claims, andaudare present where required. - Test tenant discovery and switching for users with one, multiple, and no tenant memberships.
- Search source files, dependency manifests, environment configuration, deployment configuration, and tests for stale Stytch imports, credentials, hosts, callback routes, cookie names, and claim reads.
M2M and Enterprise Features
- Request a Descope client-credentials token for every migrated M2M service and validate its issuer, subject, audience, scopes, expiration, and custom claims.
- Confirm each API rejects a client that lacks the required Policy or scope.
- Test secret rotation and verify services no longer send Stytch M2M credentials before revoking them.
- Test every migrated SAML/OIDC connection, domain-routing rule, attribute mapping, and group-to-role mapping.
- Test SCIM user create, update, deactivate, group membership, and role mapping from each connected directory.
- Confirm audit events and external webhook delivery cover required authentication, administration, and provisioning actions.