Login

Learn how Wristband SDKs start the login flow and resolve the tenant domain.

The Login Endpoint is where every login starts. Your frontend redirects users to it whenever they need to log in. For example:

GET https://customer01.yourapp.io/auth/login

The SDK's login function then determines which tenant the user is logging into, creates a Login State Cookie to track the login request, and builds the redirect that sends the user to Wristband to authenticate. Your Login Endpoint returns that redirect.

For example, using the Express Auth SDK:

app.get('/auth/login', async (req, res) => {
  const loginUrl = await wristbandAuth.login(req, res);
  res.redirect(loginUrl);
});

The login function also accepts an optional login configuration for cases where your application needs custom behavior, such as setting a return URL or a default tenant. These options are covered in the sections below.

Tenant Resolution

Every login must be completed through a Tenant-Level domain by the time the user reaches your Callback Endpoint. To get there, the SDK first tries to resolve the tenant at your Login Endpoint, which determines the domain it uses when building the Wristband Authorize URL. It checks the following sources in precedence order, where each one resolves to a Tenant-Level domain:

  1. tenant_custom_domain query parameter: A tenant custom domain passed to your Login Endpoint (e.g. https://yourapp.io/auth/login?tenant_custom_domain=mytenant.com). The SDK first validates it with the Wristband Validate Custom Domain API, and if it isn't a valid tenant custom domain for your application, the SDK ignores it and moves on to the next source.
  2. Tenant subdomain: The tenant name in the request's host, when your loginUrl contains the {tenant_name} placeholder (e.g. https://customer01.yourapp.io/auth/login). If your application runs behind a reverse proxy, see the Reverse Proxies section of Backend SDK Configuration for how the SDK determines the host.
  3. tenant_name query parameter: A tenant name passed to your Login Endpoint, when your loginUrl doesn't contain the placeholder (e.g. https://yourapp.io/auth/login?tenant_name=customer01).
  4. defaultTenantCustomDomain login configuration: A fallback tenant custom domain set by your application.
  5. defaultTenantName login configuration: A fallback tenant name set by your application.

When No Tenant Is Resolved

If none of the above sources resolve a Tenant-Level domain, what happens next depends on your applicationAuthorizationRequestsEnabled configuration:

  • When true: The SDK builds the redirect to the Wristband Authorize Endpoint using your Application's vanity domain, and Wristband handles tenant discovery as part of the same login flow.
  • When false: The SDK builds the redirect to the Application-Level Login (Tenant Discovery) Page instead, either the Wristband-hosted page or your self-hosted page if customApplicationLoginPageUrl is set. After the user identifies their tenant, Wristband sends them back to your Login Endpoint to start a tenant-level login.

When a tenant can be resolved, such as when a user navigates directly to a Tenant-Level Login URL from a bookmark, the SDK always proceeds with a Tenant-Level login. This is especially relevant for Global and Standalone tenants, which are either excluded from tenant discovery entirely (Global Tenants) or intended to bypass Application-Level branding (Standalone Tenants). Users of these tenant types should always reach your Login Endpoint with their tenant already resolvable.

Default Tenants

If most or all of your users belong to the same tenant, you can set defaultTenantName or defaultTenantCustomDomain in the login configuration. Since a default always resolves a tenant, users skip tenant discovery entirely, and the SDK never falls back to the Application level.

For example, using the Express Auth SDK with a default tenant name:

const loginUrl = await wristbandAuth.login(req, res, { defaultTenantName: 'default' });
res.redirect(loginUrl);

Or with a default tenant custom domain:

const loginUrl = await wristbandAuth.login(req, res, { defaultTenantCustomDomain: 'mytenant.com' });
res.redirect(loginUrl);

Return Value

The login function returns where your Login Endpoint should redirect the user. Depending on how the SDK resolved the tenant, that destination is one of the following:

  • The Wristband Authorize Endpoint on a Tenant-Level domain: When the SDK resolved a tenant.
  • The Wristband Authorize Endpoint on your Application's vanity domain: When no tenant was resolved and applicationAuthorizationRequestsEnabled is true.
  • The Application-Level Login (Tenant Discovery) Page: When no tenant was resolved and applicationAuthorizationRequestsEnabled is false. This is either the Wristband-hosted page or your self-hosted page if customApplicationLoginPageUrl is set.

How the destination is returned depends on the SDK:

  • As a URL: Some SDKs return the URL as a string, and your Login Endpoint performs the redirect itself (e.g. Express, and the Next.js Pages Router).
  • As a redirect response: Some SDKs return a ready-made redirect response, and your Login Endpoint returns it as-is (e.g. the Next.js App Router).
🚧

Responses Must Not Be Cached

The SDK sets the Cache-Control: no-store and Pragma: no-cache headers on every response from your Login Endpoint. These responses set or clear cookies and redirect to URLs that are only valid once, so a browser or intermediary cache must never reuse them.

Authorize URL Parameters

When the destination is the Wristband Authorize Endpoint, the SDK builds the URL with the following query parameters:

Query ParameterValue
client_idYour clientId.
redirect_uriYour redirectUri.
response_typeAlways code.
stateThe random state value stored in the Login State Cookie.
scopeYour scopes, space-separated.
code_challengeThe PKCE code challenge, derived from the code verifier stored in the Login State Cookie.
code_challenge_methodAlways S256.
nonceA random value generated for each login request.
idp_hintForwarded from your Login Endpoint's idp_hint query parameter, if present.
login_hintForwarded from your Login Endpoint's login_hint query parameter, if present.

See the Authorize Endpoint documentation for more on each parameter.

Login Hint

The login_hint query parameter pre-fills the email field on the Tenant Login Page. Wristband passes it to your Login Endpoint in flows such as tenant discovery, after a user has already entered their email on the Application-Level Login Page. For example:

GET https://customer01.yourapp.io/auth/[email protected]

IdP Hint

The idp_hint query parameter tells Wristband which identity provider the user should log in with. Its value is the name of an identity provider enabled for the tenant. For example:

GET https://customer01.yourapp.io/auth/login?idp_hint=google

Wristband handles the hint as follows:

  • External identity provider (e.g. google): Wristband skips the Tenant Login Page and sends the user straight to that provider's login page.
  • Wristband identity provider: Wristband shows the Tenant Login Page with only the email-based login form.
  • Unrecognized value: Wristband ignores the hint and shows the Tenant Login Page as normal.

Login State Cookie

Before redirecting the user to Wristband, the SDK creates a Login State Cookie that holds everything it needs to complete this specific login request at the Callback Endpoint. The SDK encrypts the cookie so its contents can't be read or modified.

The cookie contains the following:

FieldDescription
stateA random value sent to Wristband with the authorization request and checked again at the callback.
codeVerifierThe PKCE code verifier, used when exchanging the authorization code for tokens.
redirectUriThe Callback Endpoint URL used for this login request.
returnUrlThe return URL for this login, if one was provided.
customStateYour application's custom state, if any was provided.

When Wristband redirects the user back to your Callback Endpoint, the SDK finds the matching cookie, clears it, and checks that its state value matches the state query parameter. This ensures the callback belongs to a login request your application actually started, and protects against tampered or forged callback requests. If the cookie is missing, such as when it has expired or the user opened a bookmarked callback URL, or if the state values don't match, the callback returns a redirect to your Login Endpoint so a new login can start.

Concurrent Logins

Each Login State Cookie is named after its state value and creation time (e.g. login#{state}#{timestamp}), so logins started in multiple browser tabs don't overwrite each other. The SDK keeps up to three Login State Cookies at a time and clears the oldest when a new login starts. Each cookie expires after one hour.

Cookie Domain

The domain the Login State Cookie is set on depends on your configuration:

  • When applicationAuthorizationRequestsEnabled is true and your loginUrl contains the {tenant_name} placeholder: The SDK sets the cookie on your application's root domain, which it derives by removing the placeholder from your loginUrl (e.g. https://{tenant_name}.yourapp.io/auth/login gives .yourapp.io). This makes the cookie available across all tenant subdomains, which is needed because a login can start on your Application-Level domain before the tenant is known, while the callback arrives on the tenant's subdomain.
  • In all other cases: The SDK sets the cookie without a domain, so the browser only sends it back to the exact host that handled the login request (e.g. customer01.yourapp.io).

Return URLs

Unauthenticated users often try to reach a specific page in your application, such as from a bookmark or a shared link, and get sent to log in first. A return URL lets the SDK carry that location through the login flow, so the user lands back where they started instead of on a default page.

You can provide a return URL in either of two ways:

  • returnUrl login configuration: Set by your Login Endpoint. For example, using the Express Auth SDK:
    const loginUrl = await wristbandAuth.login(req, res, {
      returnUrl: 'https://customer01.yourapp.io/settings/profile',
    });
    res.redirect(loginUrl);
  • return_url query parameter: Passed to your Login Endpoint by your frontend. For example:
    GET https://customer01.yourapp.io/auth/login?return_url=https://customer01.yourapp.io/settings/profile

If both are present, the login configuration takes precedence.

The SDK stores the return URL in the Login State Cookie and returns it from the callback, where your Callback Endpoint can redirect the user to it. The SDK validates the return URL during the callback: relative URLs are resolved against the callback request, and URLs that don't match your application's domain are dropped.

Return URLs During Tenant Discovery

ℹ️

Application-Level Authorization Requests

This section only applies when applicationAuthorizationRequestsEnabled is false. When it's true, the return URL is stored in the Login State Cookie as usual, and no extra steps are needed.

When applicationAuthorizationRequestsEnabled is false, the user goes to the Tenant Discovery Page before any Login State Cookie exists. The SDK appends the return URL to the Tenant Discovery Page URL (if provided) as the state query parameter, and Wristband includes that same state value when it redirects the user back to your Login Endpoint after tenant discovery. Your Login Endpoint must then pass it to the login function as returnUrl.

For example, the SDK sends the user to the Tenant Discovery Page with the return URL in state:

https://yourapp-yourcompany.us.wristband.dev/login?client_id=123&state=https%3A%2F%2Fcustomer01.yourapp.io%2Fsettings

After tenant discovery, Wristband redirects the user back to your Login Endpoint with the same state value:

https://yourapp.io/auth/login?tenant_name=customer01&state=https%3A%2F%2Fcustomer01.yourapp.io%2Fsettings

For example, using the Express Auth SDK:

app.get('/auth/login', async (req, res) => {
  const returnUrl = typeof req.query.state === 'string' ? req.query.state : undefined;
  const loginUrl = await wristbandAuth.login(req, res, { returnUrl });
  res.redirect(loginUrl);
});
🚧

State Parameter Length

The state query parameter is limited to 512 characters. If the return URL is longer, it's dropped and the login continues without it.

Custom State

Custom state lets your application carry its own context through the login flow, such as which feature or action triggered the login. If you set customState in the login configuration, then the SDK stores it in the Login State Cookie and returns it unmodified from the callback. Empty customState objects are ignored.

For example, using the Express Auth SDK:

const loginUrl = await wristbandAuth.login(req, res, {
  customState: { test: 'abc' }
});
res.redirect(loginUrl);
🚧

Use Custom State Sparingly

Most applications don't need custom state. Browsers limit cookies to 4KB, so keep custom state small. Around 1KB of JSON is a safe ceiling.

Reference

Login Configuration

FieldTypeDescription
customStateobjectApplication-specific state stored in the Login State Cookie and returned unmodified from the callback.
defaultTenantCustomDomainstringA fallback tenant custom domain used when no tenant can be resolved from the request.
defaultTenantNamestringA fallback tenant name used when no tenant can be resolved from the request.
returnUrlstringThe URL to send the user to after login. Takes precedence over the return_url query parameter.

Login Endpoint Query Parameters

Query ParameterDescription
idp_hintThe name of an identity provider enabled for the tenant, used to send the user directly to that provider.
login_hintAn email address used to pre-fill the Tenant Login Page.
return_urlThe URL to send the user to after login.
stateThe return URL passed back by Wristband after tenant discovery, when applicationAuthorizationRequestsEnabled is false.
tenant_custom_domainThe tenant custom domain to log in through.
tenant_nameThe name of the tenant to log in to. Only used when your loginUrl doesn't contain the {tenant_name} placeholder.


Did this page help you?