/
Contact usSee pricingStart building

    About B2B Saas Authentication

    Introduction
    Stytch B2B Basics
    Integration Approaches
      Full-stack overview
      Frontend (pre-built UI)
      Frontend (headless)
      Backend
    Next.js
      Routing
      Authentication
      Sessions
    Migrations
      Overview
      Reconciling data models
      Migrating user data
      Additional migration considerations
      Zero-downtime deployment
      Defining external IDs for members
      Exporting from Stytch
    Custom Domains
      Overview

    Authentication

    Single Sign On
    • Resources

      • Overview
        External SSO Connections
        Standalone SSO
    • Integration Guides

      • Start here
        Backend integration guide
        Headless integration guide
        Pre-built UI integration guide
    OAuth
    • Resources

      • Overview
        Authentication flows
        Identity providers
        Google One Tap
        Provider setup
    • Integration Guides

      • Start here
        Backend integration
        Headless frontend integration
        Pre-built UI frontend integration
    Connected AppsBeta
      Setting up Connected Apps
      About Remote MCP Servers
    • Resources

      • Integrate with AI agents
        Integrate with a remote MCP server
    Sessions
    • Resources

      • Overview
        JWTs vs Session Tokens
        How to use Stytch JWTs
        Custom Claims
    • Integration Guides

      • Start here
        Backend integration
        Frontend integration
    Email OTP
      Overview
    Magic Links
    • Resources

      • Overview
        Email Security Scanner Protections
    • Integration Guides

      • Start here
        Backend integration
        Headless frontend integration
        Pre-built UI frontend integration
    Multi-Factor Authentication
    • Resources

      • Overview
    • Integration Guides

      • Start here
        Backend integration
        Headless frontend integration
        Pre-built UI frontend integration
    Passwords
      Overview
      Strength policies
    UI components
      Overview
      Implement the Discovery flow
      Implement the Organization flow
    DFP Protected Auth
      Overview
      Setting up DFP Protected Auth
      Handling challenges
    M2M Authentication
      Authenticate an M2M Client
      Rotate client secrets
      Import M2M Clients from Auth0

    Authorization & Provisioning

    RBAC
    • Resources

      • Overview
        Stytch Resources & Roles
        Role assignment
    • Integration Guides

      • Start here
        Backend integration
        Headless frontend integration
    SCIM
    • Resources

      • Overview
        Supported actions
    • Integration Guides

      • Using Okta
        Using Microsoft Entra
    Organizations
      Managing org settings
      JIT Provisioning

    Testing

    E2E testing
    Sandbox values
Get support on SlackVisit our developer forum

Contact us

B2B Saas Authentication

/

Guides

/

Authentication

/

Sessions

/

Integration Guides

/

Frontend integration

Frontend Sessions guide

In this guide, we'll walk you through how to create and manage B2B Stytch Sessions via using our frontend JavaScript SDK. This guide is relevant both to integrations that use our pre-built UI components and integrations that use our headless frontend methods.

Overview

Frontend SDK sessions diagram

When a user successfully authenticates, our frontend JavaScript SDK will automatically store the user's session token and JWT as browser cookies. The user's session token and JWT will then be automatically included in request headers by the browser in all requests to your backend, as long as your frontend and backend share a domain.

Your backend can use the session token or JWT to authenticate the session and determine whether or not it should proceed with the request. See our Backend sessions guide for additional information about backend session authentication.

The session token will also be included in all frontend SDK requests to Stytch's servers, so that the SDK can make requests on behalf of the authenticated user.

Background session authentication

The frontend SDK makes background session authentication calls to Stytch servers roughly every three minutes while the user is actively using your application. After each background request, the frontend SDK will replace the old session JWT with a new one, ensuring that the JWT remains unexpired.

The SDK will also update its cached User and Session data after its background session authentication calls to reflect any changes in the session data, and will clear the session cookies if the session is no longer valid.

Session cookie configuration

When using the Stytch frontend SDK for session management, you may configure certain values such as the session cookie names and which HTTP paths, domain, and subdomains the cookie should be accessible to.

For additional information, see our Cookies & session management SDK resource.

HttpOnly vs. non-HttpOnly session cookies

By default, session cookies set by the Stytch frontend JavaScript SDK are not marked as HttpOnly. For enhanced security, you can optionally enable the use of HttpOnly cookies for your project.

When HttpOnly cookies are enabled, session handling behavior will remain largely the same, with a few minor differences. Check out our HttpOnly Cookies overview for additional information.


Handling intermediate session tokens

The Stytch API returns intermediate_session_tokens instead of full-fledged Member session_tokens during authentication flows that require an intermediate step before the user is fully authenticated. The frontend JavaScript SDK will automatically store any intermediate_session_tokens returned by the Stytch API and will include them in subsequent authentication requests.

There are three flows in which intermediate_session_tokens are used:

  • The Discovery flow, where an intermediate_session_token is returned after the user authenticates but before they select or create an Organization.
  • The MFA flow, where an intermediate_session_token is returned after the user completes their first authentication factor but before they complete their second factor.
  • The step-up flow, where an intermediate_session_token is returned after the user completes one form of authentication but must complete additional authentication to verify their email address or to comply with an Organization's allowed authentication methods before they can be granted a session.

Our frontend pre-built UI components handle all three of the above flows out of the box. If you're using our headless methods with your own UI, you'll need to implement some of the logic yourself.

Discovery flow

An intermediate_session_token will be returned in response to any of the Discovery authenticate methods (like Authenticate discovery magic link or Authenticate discovery OAuth, for example).

In the Discovery authenticate response, you'll receive a Discovered Organizations object that you can use to inform the subsequent UI. Here's an example Discovery authenticate response:

{
    "discovered_organizations": [
        {
            "member_authenticated": false,
            "membership": {
                "details": null,
                "member": {
                    // Full Member object
                },
                "type": "active_member"
            },
            "mfa_required": {
                "member_options": {
                    "mfa_phone_number": "XXXXXXX1234",
                    "totp_registration_id": ""
                },
                "secondary_auth_initiated": null
            },
            "organization": {
                // Full Organization object (fields omitted for brevity)
                ...
                "organization_id": "organization-...",
                "organization_logo_url": "",
                "organization_name": "Example Organization One",
                "organization_slug": "example-organization-one",
            },
            "primary_required": null
        },
        {
            "member_authenticated": true,
            "membership": {
                "details": null,
                "member": {
                    // Full Member object
                },
                "type": "pending_member"
            },
            "mfa_required": null,
            "organization": {
                // Full Organization object (fields omitted for brevity)
                ...
                "organization_id": "organization-...",
                "organization_logo_url": "",
                "organization_name": "Example Organization Two",
                "organization_slug": "example-organization-two",
            },
            "primary_required": null
        },
    ],
    "email_address": "example@stytch.com",
    "intermediate_session_token": "Is9gF...",
    "request_id": "request-id-...",
    "status_code": 200
}

At this point, you can either allow the user to start a session in an Organization that they have access to using the Exchange intermediate session method, or you can allow them to create a new Organization using the Create Organization via discovery method. If you'd like to allow Organization creation via frontend calls, make sure that the Create Organizations setting is enabled for your project in the Frontend SDKs tab in the Stytch Dashboard.

Note that the SDK will automatically save the intermediate_session_token in browser cookies, so there's no need to explicitly pass it into these methods.

export const exchangeDiscoveryIntermediateSessionToken = (organization_id) => {
    stytch.discovery.intermediateSessions.exchange({
        organization_id,
        session_duration_minutes: DESIRED_SESSION_LENGTH,
    });
}

export const createOrganization = (organization_name, organization_slug) => {
    stytch.discovery.organizations.create({
        organization_name,
        organization_slug,
        session_duration_minutes: DESIRED_SESSION_LENGTH,
    });
}

UX tips: If there is exactly one Organization in the discovered_organizations array, one option is to log the user into that Organization automatically and skip the Organization selection step. Similarly, if the discovery_organizations array is empty, you may choose to create a new Organization automatically to streamline the user experience.

After calling the Exchange intermediate session method, you may still need to trigger the MFA flow or primary step-up authentication flow based on the authentication requirements for the Organization that the user selects. See below for additional information.

Multi-factor authentication (MFA) flow

If a user is required to complete MFA, the following will occur:

  • An intermediate_session_token will be returned instead of a session_token in response to whichever authentication method is being called (for example, Email Magic Links authenticate, Passwords authenticate, or Exchange intermediate session).
  • The member_authenticated value in the authentication response will be false.
  • The primary_required value in the authentication response will be null.
  • The mfa_required value in the authentication response will be non-null and will indicate which form of MFA the user is required to complete.
  • If the user is enrolled in SMS OTP MFA, an OTP code will automatically be sent to their phone number.

The following is an example Email Magic Links authenticate response where SMS OTP MFA is required as a next step:

{
    "intermediate_session_token": "Fxq0E...",
    "member": {
        // Full Member object
    },
    "member_authenticated": false,
    "member_id": "member-...",
    "member_session": null,
    "method_id": "member-email-...",
    "mfa_required": {
        "member_options": {
            "mfa_phone_number": "XXXXXXX1234",
            "totp_registration_id": ""
        },
        "secondary_auth_initiated": "sms_otp"
    },
    "organization": {
        // Full Organization object
    },
    "organization_id": "organization-...",
    "request_id": "request-id-...",
    "reset_sessions": false,
    "session_jwt": "",
    "session_token": "",
    "status_code": 200
}

In this case, you will need to prompt the user to complete the appropriate MFA flow. See our Headless frontend MFA guide for more information and implementation steps.

Primary step-up flow

If a user must complete an additional form of primary authentication, the following will occur:

  • An intermediate_session_token will be returned instead of a session_token in response to whichever authentication method is being called (for example, OAuth authenticate, Passwords authenticate or Exchange intermediate session).
  • The member_authenticated value in the authentication response will be false.
  • The primary_required value in the authentication response will be non-null and will detail the action(s) that the user must take in order to meet the primary step-up requirement for the Organization in question.

The following is an example OAuth authenticate response where primary step-up authentication is required as a next step:

{
    "intermediate_session_token": "fTwWh...",
    "member": {
        // Full Member object
    },
    "member_authenticated": false,
    "member_id": "member-...",
    "member_session": null,
    "mfa_required": null,
    "organization": {
        // Full Organization object
    },
    "organization_id": "organization-...",
    "primary_required": {
        "allowed_auth_methods": [
            "sso",
            "microsoft_oauth",
            "email_otp",
            "magic_link",
            "google_oauth"
        ]
    },
    "provider_subject": "U08...",
    "provider_type": "Slack",
    "provider_values": {
        "access_token": "x2f...",
        "expires_at": "2024-12-10T20:11:53Z",
        "id_token": null,
        "refresh_token": null,
        "scopes": [
            "users:read",
            "users:read.email"
        ]
    },
    "request_id": "request-id-...",
    "reset_sessions": false,
    "session_jwt": "",
    "session_token": "",
    "status_code": 200
}

At this point, you'll need to prompt the user to complete one of the authentication methods present in the primary_required.allowed_auth_methods array before they can be granted a full Member session.


Determining whether there is an active session

You can leverage the frontend SDK's session data to determine whether or not there is an active session for frontend UI and navigation purposes. Choose your frontend framework for additional usage details.

When using our React SDK, you can use the useStytchMemberSession() hook to retrieve the user's session data and listen for changes. The useStytchMemberSession() hook returns two values:

  • session: The user's Stytch Session object, if there is an active session. If there's no active session, the session value will be null.
  • fromCache: true if the SDK hasn't yet completed a session authentication call after loading and is returning cached session data; false if the session authentication call has completed.

For many use cases, like deciding whether or not to render certain frontend content, it's generally fine to rely on the session value. However, for use cases where you'd prefer to wait until the session data finishes updating instead of relying on cached data, you can verify that fromCache is false before checking the session value. See our SWR & caching resource for additional information.

import { useStytchMemberSession } from '@stytch/react/b2b';

export const Home = () => {
  const { session } = useStytchMemberSession();

  return session ? <p>Active Session</p> : <p>No Active Session</p>;
};

When using our NextJS SDK, you can use the useStytchMemberSession() hook to retrieve the user's session data and listen for changes. The NextJS useStytchMemberSession() hook returns three values:

  • isInitialized: Whether or not the SDK has finished initializing. Ensure this is true before checking for an active session.
  • session: The user's Stytch Session object, if there is an active session. If there's no active session, the session value will be null.
  • fromCache: true if the SDK hasn't yet completed a session authentication call after loading and is returning cached session data, false if the session authentication call has completed.

For many use cases, like deciding whether or not to render certain frontend content, it's generally fine to rely on the session value as long as isInitialized is true. However, for use cases where you'd prefer to wait until the session data finishes updating instead of relying on cached data, you can verify that fromCache is false before checking the session value. See our SWR & caching resource for additional information.

import { useStytchMemberSession } from '@stytch/nextjs/b2b';

export const Home = () => {
  const { session, isInitialized } = useStytchMemberSession();

  if (!isInitialized) {
    return <p>Loading</p>
  }
  return session ? <p>Active Session</p> : <p>No Active Session</p>;
};

When using our Vanilla JavaScript SDK, you can use the stytch.session.getInfo method to retrieve the user's session data. The stytch.session.getInfo method returns two values:

  • session: The user's Stytch Session object, if there is an active session. If there's no active session, the session value will be null.
  • fromCache: true if the SDK hasn't yet completed a session authentication call after loading and is returning cached session data; false if the session authentication call has completed.
import { StytchB2BHeadlessClient } from '@stytch/vanilla-js/b2b/headless';

const stytch = new StytchB2BHeadlessClient('PUBLIC_TOKEN');

let { session, fromCache } = stytch.session.getInfo();

if (session && fromCache) {
  // There is a cached session and a session authentication is in progress
} else if (session) {
  // Session authentication has completed and there is an active session
} else {
  // There is no active session
}

You can also use the stytch.session.onChange method to listen for changes in the user's session. The session.onChange method takes in a callback that gets called whenever the session object changes, and it returns an unsubscribe method for you to call when you no longer want to listen for changes.

// Listen for session changes
const unsubscribeSession = stytch.session.onChange((newSession) => {
  session = newSession;
  // Trigger any relevant actions in your application based on the session update
  handleSessionChange();
});

window.addEventListener('beforeunload', () => {
  unsubscribeSession && unsubscribeSession();
});

Extending the lifetime of a session

Once a session has been created, the frontend SDK will keep the session JWT updated by making background session authentication calls, but it will not automatically extend the lifetime of the underlying session. For example, if you set an initial session_duration_minutes of 60 minutes and take no further action, the session will expire after 60 minutes regardless of user activity.

If you would like to extend the lifetime of an active session, you can specify the new desired session length by passing a session_duration_minutes value to the Authenticate session method, which will set the session's lifetime to that many minutes from the present. Note that once a session has expired, it can't be reactivated – in that case, the user will need to log in again and create a new session.

One technique is to set an interval where you extend the session's lifetime so that the session is periodically extended while the user keeps your application open:

const extendSession = () => {
  if (stytch.session.getSync()) {
    // Set the session's expiration to 60 minutes from the present
    stytch.session.authenticate({
      session_duration_minutes: 60,
    });
  }
};
// Extend the session's lifetime every 10 minutes
let interval = setInterval(extendSession, 600000);
window.addEventListener('beforeunload', () => {
  clearInterval(interval);
});

Exchanging a session

Even if a user has access to more than one Organization, Stytch session_tokens are valid for one Organization at a time. However, as long as the user's current session meets the authentication requirements for another Organization that they have access to, you may exchange their session for a session in the other Organization. See the Exchange session method documentation for additional information regarding session exchange requirements.

First, you can use the List discovered Organizations method to retrieve a list of Organizations that the active user has access to.

export const listOrganizations = async () => {
  const { discovered_organizations } = await stytch.discovery.organizations.list();
  return discovered_organizations;
};

Here's an example List discovered Organizations response:

{
    "discovered_organizations": [
        {
            "member_authenticated": false,
            "membership": {
                "details": null,
                "member": {
                    // Full Member object
                },
                "type": "active_member"
            },
            "mfa_required": {
                "member_options": {
                    "mfa_phone_number": "XXXXXXX1234",
                    "totp_registration_id": ""
                },
                "secondary_auth_initiated": null
            },
            "organization": {
                // Full Organization object (fields omitted for brevity)
                ...
                "organization_id": "organization-...",
                "organization_logo_url": "",
                "organization_name": "Example Organization One",
                "organization_slug": "example-organization-one",
            },
            "primary_required": null
        },
        {
            "member_authenticated": true,
            "membership": {
                "details": null,
                "member": {
                    // Full Member object
                },
                "type": "active_member"
            },
            "mfa_required": null,
            "organization": {
                 // Full Organization object (fields omitted for brevity)
                ...
                "organization_id": "organization-...",
                "organization_logo_url": "",
                "organization_name": "Example Organization Two",
                "organization_slug": "example-organization-two",
            },
            "primary_required": null
        },
    ],
    "email_address": "example@stytch.com",
    "organization_id_hint": null,
    "request_id": "request-id-...",
    "status_code": 200
}

Once the user selects the Organization that they'd like to access, you can exchange their active session for a session in that Organization using the Exchange session method documentation. If the session is exchanged successfully, the frontend SDK will automatically be populated with the new Session, Member, and Organization data.

export const exchange = (target_organization_id) => {
  stytch.session.exchange({
    organization_id: target_organization_id,
    session_duration_minutes: DESIRED_SESSION_LENGTH,
  });
};

Check out our B2B SDK example app for an example of an Organization switcher component built on top of our frontend SDK endpoints.

Note that if additional authentication is required before the user can be granted a Member Session in the new Organization, the Exchange session method will return an intermediate_session_token instead. See the [Handling intermediate_session_tokens section](TODO: Link to section) for additional details.

Hydrating a session

If necessary, you can hydrate the frontend SDK with an active session that was created elsewhere. This is useful if your application uses any authentication flows where a session is created or updated on the backend and you'd like the frontend SDK to pick up the most recent session data.

You can do so by providing both the active session_token and the session_jwt to the Update session method, followed by a call to the Authenticate session method.

export const hydrateSession = () => {
  stytch.session.updateSession({
    session_token: 'ACTIVE_SESSION_TOKEN',
    session_jwt: 'ACTIVE_SESSION_JWT',
  });

  stytch.session.authenticate();
};

Note that when HttpOnly cookies are enabled for your Stytch project, the Update session method will only work if there isn't already an existing session token set using an HttpOnly cookie. If you do use the Update session method when HttpOnly cookies are enabled, make sure your Stytch SDK client's cookieOptions are configured to match the domain and cookie names used by Stytch's backend.

Alternatively, you can override the session cookies via response headers from your backend. After doing so, be sure to make a frontend call to the Authenticate session method so that the SDK picks up the new session data.

Revoking a session

The Revoke session method makes a call to Stytch's servers to revoke the active session and clears the session cookies from the user's browser.

export const logout = () => {
  stytch.session.revoke();
};

Overview

Background session authentication

Session cookie configuration

HttpOnly vs. non-HttpOnly session cookies

Handling intermediate session tokens

Discovery flow

Multi-factor authentication (MFA) flow

Primary step-up flow

Determining whether there is an active session

Extending the lifetime of a session

Exchanging a session

Hydrating a session

Revoking a session