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.Client({
project_id: "PROJECT_ID",
secret: "SECRET",
custom_base_url: "${projectDomain}",
});
client = Client(
project_id="PROJECT_ID",
secret="SECRET",
custom_base_url="${projectDomain}",
)
client = Stytch::Client.new(
project_id: "PROJECT_ID",
secret: "SECRET"
custom_base_url: "${projectDomain}",
)
stytchClient, err := stytchapi.NewClient(
"PROJECT_ID",
"SECRET",
stytchapi.WithBaseURI("${projectDomain}")
)
const client = createStytchUIClient(
"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:
- Set up a custom domain (CNAME) in your DNS that points to Stytch and configure Stytch to recognize this domain.
- Configure your application to make all API calls to Stytch over this domain.
- 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.