/
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
      Migrating from Stytch Consumer to B2B
      Exporting from Stytch
    Custom Domains
      Overview

    Authentication

    Single Sign On
    • Resources

      • Overview
        External SSO Connections
        Standalone SSO
    • Integration Guides

      • Start here
        Provider setup
        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 Apps
      Overview
      Getting started with the SDK
      Getting started with the API
      Client types
      OAuth scopes
    • Integration Guides

      • Integrate with an Existing Auth System
        MCP Authorization Overview
        Integrate with a remote MCP server
        Integrate with AI agents
    • Resources

      • Consent Management
        Custom Domains
    Sessions
    • Resources

      • Overview
        JWTs vs Session Tokens
        How to use Stytch JWTs
        Custom Claims
        Custom Claim Templates
    • 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
    • Resources

      • Overview
        Strength policy
    • Integration Guides

      • Pre-built UI frontend integration
    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
    Trusted Auth Tokens
      Overview
      Getting Started with External IDPs
      Getting Started with Custom Auth Factors
    Device History
      New device notifications

    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

/

Connected Apps

/

Resources

/

Custom Domains

Custom Domains and Connected Apps

Why domains matter in OAuth

In OAuth and OpenID Connect (OIDC), many core features rely on a single authoritative domain.

The most important of these is the issuer — a required field in Access and ID token JWTs that uniquely identifies your Stytch application as the entity that created the token. Consumers of these JWTs use the issuer during validation to ensure the token’s authenticity.

Another key feature is the set of well-known metadata URLs, which OAuth clients use to automatically discover server configuration. These endpoints expose metadata about available capabilities and supported flows.

  • For OAuth: https://<domain>/.well-known/oauth-authorization-server
  • For OIDC: https://<domain>/.well-known/openid-configuration

Stytch will automatically provision a default domain for your project upon creation. Default domains are intended for convenient testing of an initial integration. However, this domain is frequently shared with external consumers of your application. We recommend setting up a Branded Custom Domain as a subdomain of your main website before going live with your integration.

Configuring Stytch SDKs to use your domain

Stytch SDKs must be configured to use the correct domain for your project:

const client = new stytch.B2BClient({
  project_id: "PROJECT_ID",
  secret: "SECRET",
  custom_base_url: "${projectDomain}",
});
client = B2BClient(
  project_id="PROJECT_ID",
  secret="SECRET",
  custom_base_url="${projectDomain}",
)
client = StytchB2B::Client.new(
  project_id: "PROJECT_ID",
  secret: "SECRET"
  custom_base_url: "${projectDomain}",
)
stytchClient, err := b2bstytchapi.NewClient(
    "PROJECT_ID",
    "SECRET",
    b2bstytchapi.WithBaseURI("${projectDomain}")
)
const client = createStytchB2BUIClient(
  "PUBLIC_TOKEN",
  {
    endpointOptions: {
      testApiDomain: "${projectDomain}", // In Test
      apiDomain: "${projectDomain}", // In Live
    }
  }
);

How Custom Domains affect Connected Apps

Stytch will dynamically determine JWT issuers and well-known URL responses based on the domain used to access the Stytch service.

Using the default project domain

All Stytch projects are provisioned a unique default domain upon creation.

  • In test environments, the domain will look like https://{noun}-{verb}-{number}.customers.stytch.dev.
  • In live environments, the domain will look like https://{noun}-{verb}-{number}.customers.stytch.com.

API calls occurring over this domain will use the domain value while processing the request.

For example, the Access token and ID token returned from this Exchange Authorization Code call will have an issuer of https://{noun}-{verb}-{number}.customers.stytch.dev.

curl -X POST https://{noun}-{verb}-{number}.customers.stytch.dev/oauth2/token \
	-H 'Content-Type: application/json' \
	-d '{
		"client_id": "connected-app-test-d731954d-dab3-4a2b-bdee-07f3ad1be888",
		"client_secret": "NHQhc7ZqsXJVtgmN2MXr1etqsQrGAwJ-iBWNLKY7DzJq",
		"redirect_uri": "https://example.com/callback",
		"grant_type": "authorization_code",
		"code": "PvC5UudZ7TPZbELt95yXAQ-8MeEUCRob8bUQ-g52fIJs"
	}'
# => { "access_token": "eyJ...", "id_token": "eyJ...", ... }

Similarly, the metadata returned from this Get OpenID Configuration call will use the https://{noun}-{verb}-{number}.customers.stytch.dev domain:

curl --request GET \
	--url https://https://{noun}-{verb}-{number}.customers.stytch.dev/.well-known/openid-configuration

like so:

{
  "registration_endpoint": "https://{noun}-{verb}-{number}.customers.stytch.dev/v1/oauth2/register",
  "token_endpoint": "https://{noun}-{verb}-{number}.customers.stytch.dev/v1/oauth2/token",
  "userinfo_endpoint": "https://{noun}-{verb}-{number}.customers.stytch.dev/v1/oauth2/userinfo",
  ...
}

Using a branded custom domain (CNAME)

When you configure a custom domain (e.g. https://login.yourcompany.com) and make API calls using that domain, Stytch will use that domain instead.

The Access token and ID token returned from this call will have an issuer of https://login.yourcompany.com.

curl -X POST https://login.yourcompany.com/oauth2/token \
	-H 'Content-Type: application/json' \
	-d '{
		"client_id": "connected-app-test-d731954d-dab3-4a2b-bdee-07f3ad1be888",
		"client_secret": "NHQhc7ZqsXJVtgmN2MXr1etqsQrGAwJ-iBWNLKY7DzJq",
		"redirect_uri": "https://example.com/callback",
		"grant_type": "authorization_code",
		"code": "PvC5UudZ7TPZbELt95yXAQ-8MeEUCRob8bUQ-g52fIJs"
	}'
# => { "access_token": "eyJ...", "id_token": "eyJ...", ... }

Similarly, the metadata returned from this call will use the https://login.yourcompany.com domain:

curl --request GET \
	--url https://login.yourcompany.com/.well-known/openid-configuration

like so:

{
  "registration_endpoint": "https://login.yourcompany.com/v1/oauth2/register",
  "token_endpoint": "https://login.yourcompany.com/v1/oauth2/token",
  "userinfo_endpoint": "https://login.yourcompany.com/v1/oauth2/userinfo",
  ...
}

Migrating from api.stytch.com

If you were an early customer of Connected Apps, you may be accessing the product over api.stytch.com - e.g. https://api.stytch.com/v1/public/${projectId}/oauth2/token. When accessed this way, Stytch will use the project ID (stytch.com/${projectId}) as the issuer. These issuers are not OpenID Connect (OIDC) compliant, as they are not HTTPS URLs.

Why does this matter?

  • Many OIDC clients and libraries will reject tokens if the issuer does not match the expected https URL.
  • Using a custom domain ensures maximum compatibility and security for your integrations.

To get a fully OIDC-compliant issuer:

  1. Set up a custom domain (CNAME) in your DNS that points to Stytch and configure Stytch to recognize this domain.
  2. Configure your application to make all API calls to Stytch over this domain.
  3. Use this domain for all authentication flows.

All Stytch backend SDKs will accept both the old and new format issuers when performing JWT validation to ensure a seamless transition.

Why domains matter in OAuth

Configuring Stytch SDKs to use your domain

How Custom Domains affect Connected Apps

Using the default project domain

Using a branded custom domain (CNAME)

Migrating from api.stytch.com