Session Migration

You can use session migration when moving from Auth0 or Okta (including Okta Customer Identity Solution) to Descope. Migrating should not require users to re-authenticate. In many applications, especially those involving mobile, desktop, or smart devices, logging a user out during a migration introduces unnecessary friction and disrupts the user experience.

With session migration, your client sends a session token from Auth0 or Okta to Descope. Descope validates the token and issues a new Descope session token for the same user. Users continue their sessions without noticing any change in the underlying authentication system.

How It Works

  1. A user makes a request with an active session token from your existing authentication provider (e.g. an Auth0 or Okta access token or JWT).
  2. Your frontend will use the Descope SDK to send the token to Descope for validation.
  3. Upon successful validation, Descope reads the user's attributes from the provider and finds the Descope user whose login ID matches them.
  4. If no Descope user matches, Descope either creates the user or rejects the request, depending on the user sync mode.
  5. Descope issues a new session token for that user and returns it to the client, replacing the previous session.

User Sync Modes

Session migration has two modes, and the mode decides whether you have to import your users first.

  • Just-in-time (jit): If no Descope user matches the token, Descope creates the user from the provider's attributes. You do not have to import users before you release the updated app, which suits providers like Okta that do not let you export password hashes.
  • Match only (matchOnly): Descope only signs in users who already exist in Descope. If no user matches, the migration fails, so you must import your users first.

In both modes, Descope matches a user by comparing the provider attributes you choose, such as email, against Descope login IDs.

Match on a verified, unique attribute

Match on an attribute that your provider verifies and keeps unique across all of its connections.

If your provider can issue tokens with an unverified email, or the same email in two connections, a token holder could sign in as another user. In that case, match on a stable identifier that you also imported as a login ID.

Prerequisites

Note

Session migration is in beta. Built-in session migration supports Auth0 and Okta as the source provider.

Before implementing session migration, ensure the following:

  • If you use match only mode, all users who authenticate using session migration must already exist in Descope.
  • Your backend is capable of validating tokens from Descope, as well as your existing authentication provider.

Configure Session Migration

You configure session migration once per project. In the Descope Console, go to Project Settings and open Session Migration (Beta) under Session Management.

The settings describe your source provider and how Descope maps its users:

SettingDescription
VendorThe source provider, either Auth0 or Okta. Session migration is off until you select a vendor.
Client IDThe client ID of the app in your Auth0 or Okta configuration.
Domain and audienceAuth0 only. Your Auth0 domain, and an optional audience.
Issuer and API tokenOkta only. Your Okta issuer URL, and an Okta API token for the Okta management API.
User sync typejit or matchOnly, as described in User Sync Modes.
Login ID matched attributesThe provider attributes, such as email, that Descope compares with Descope login IDs.
User mappingPairs of a provider attribute and the Descope user attribute that receives its value.

You can also manage these settings as code with the descope_session_migration resource in the Descope Terraform provider.

Session migration maps user attributes only. It does not map roles or tenants, so assign those through your import, a Flow, or the Management API.

Step-by-Step Implementation

Step 1. Dual Token Validation in Your Backend

During the migration period, it is recommended for your backend to be capable of validating both legacy and Descope tokens. This is a transitional strategy that allows you to gradually move users to Descope while maintaining your existing authentication as a fallback.

A typical implementation involves checking the token's issuer and key ID (kid) to determine the provider, then validating it accordingly.

Example high-level logic:

function handleRequest(req) {
  const token = extractToken(req);

  // First try to validate as a Descope token
  if (isDescopeToken(token)) {
    validateDescopeToken(token);
    return continueRequest();
  }
  
  // If not a Descope token, try legacy token validation
  if (isLegacyToken(token)) {
    const userId = validateLegacyToken(token);
    const descopeToken = issueDescopeToken(userId);
    return respondWithToken(descopeToken);
  }
  
  // If neither token is valid, proceed with normal authentication flow
  return proceedWithNormalAuth();
}

You can use your legacy provider's SDK to validate legacy tokens—for example, Auth0's jsonwebtoken or Okta's JWT verifier. This approach ensures a smooth transition by:

  1. Prioritizing Descope tokens for users who have already migrated
  2. Converting legacy tokens to Descope tokens when encountered
  3. Falling back to normal authentication for users who haven't migrated yet

Step 2. Generate Descope Session Token

Important

This action requires the user to already exist in Descope. If the user is not present, the API will return an error.

To implement session migration on the client side, use the getExternalToken prop in the Descope AuthProvider component. This function should return a valid token from your external provider (e.g. Auth0 or Okta), which the SDK will use to authenticate the user with Descope.

Terminal
npm i --save @descope/react-sdk
import { AuthProvider } from '@descope/react-sdk';

const AppRoot = () => {
  return (
    <AuthProvider
      projectId="my-project-id"
      getExternalToken={async () => {
        // Return current Auth0 or Okta session/access token
        return 'my-external-token';
      }}
    >
      <App />
    </AuthProvider>
  );
};

The SDK will automatically handle the token exchange process, converting the external token into a Descope session token. This allows for a seamless transition without requiring users to re-authenticate.

Step 3. Populate Descope with Users (Match Only Mode)

If you use just-in-time mode, you can skip this step, because Descope creates each user the first time their session migrates.

In match only mode, session migration requires that the users already exist in Descope. Descope will not provision users during the migration.

Options for populating users:

  • Bulk import: Use the User Import Tool or Management API to import your existing user base. If you're migrating from Auth0, see the Auth0 migration guide; from Okta CIS, see the Okta CIS migration guide.
  • Automated sync:
    • Auth0: Use Auth0 Actions to create users in Descope during login or registration events.
    • Okta: Use Okta Workflows or SCIM provisioning to push users into Descope.
  • Manual provisioning: For static user lists or low-volume use cases.

Important

It is strongly recommended to automate user creation in Descope for any new accounts created between your initial export and the session migration cutover.

Handling the New Descope Token

If you're using Descope's client SDKs (React, Next.js, WebJS, Kotlin, Swift), the SDK will automatically handle session management for you after successful session migration.

The SDK will automatically store and manage the new Descope tokens, so you don't need to manually handle token storage or session management - it works just like completing a flow or logging in via any other SDK authentication method.

Finalizing the Migration

Once your application is stable and all users are actively receiving Descope tokens, you can:

  • Remove legacy token validation logic from your backend.
  • Fully enforce Descope-issued session tokens across all endpoints.
  • Revoke or expire legacy sessions if needed.

Testing and Verification

Before launching session migration in production:

  • Test the flow using both legacy and Descope tokens across various clients.
  • Verify that session tokens are issued with expected claims and expiration times.

Monitoring Session Migration

Descope logs audit events for both successful and failed migrations: ExternalSessionMigrationSuccess when a Descope session is issued, and ExternalSessionMigrationFailure when the exchange fails. The failure event's Data section includes error_message along with the external provider and user attributes Descope attempted to match.

In match only mode, Descope rejects a valid external token when no Descope user matches it. See Session Migration Audit Detail for the fields logged on failure.

Limitations

  • Built-in session migration supports Auth0 and Okta as the source provider.
  • In match only mode, you must pre-provision users. Use just-in-time mode if you want Descope to create users during the migration.
  • Session migration does not carry over legacy tokens' claims (e.g., roles, groups). You must recreate any needed claims in Descope's user profile or token generation logic. For this, you can use a JWT Template.
  • This migration flow only applies to token-based sessions (JWTs, access tokens) and may not work with opaque session cookies unless decoded by your legacy provider.

Summary

Session migration enables a seamless transition to Descope by allowing your backend to recognize and convert existing sessions from Auth0 or Okta. This is especially critical for high-availability or consumer-facing applications where user disruption must be minimized.

Prepare your backend, then either import users in advance or let Descope create them just in time. Either way, users move from Auth0 or Okta to Descope without signing in again, which matters most for mobile and enterprise apps.

Was this helpful?

On this page