Callback

Learn how Wristband SDKs complete the login flow and return callback results.

After a user authenticates, Wristband redirects them to your Callback Endpoint with an authorization code and the state value from the login request. For example:

GET https://customer01.yourapp.io/auth/callback?state=f983yr893hf89ewn0idjw8e9f&code=shcsh90jf9wc09j9w0jewc

The SDK's callback function uses the Login State Cookie to verify the request, exchanges the authorization code for tokens, and retrieves the user's data from Wristband. It then returns a callback result, which tells your Callback Endpoint whether the login completed or needs to start over.

For example, using the Express Auth SDK:

app.get('/auth/callback', async (req, res) => {
  const callbackResult = await wristbandAuth.callback(req, res);
  const { callbackData, reason, redirectUrl, type } = callbackResult;
 
  // Certain edge cases require a redirect back to the Login Endpoint.
  if (type === 'redirect_required') {
    console.debug(reason);
    res.redirect(redirectUrl);
    return;
  }
 
  // Create the application session from the callback data.
  req.session.fromCallback(callbackData);
  await req.session.save();
 
  // Send the user back into your application.
  res.redirect(callbackData.returnUrl || '<your_app_home_url>');
});

How the Callback Works

When your Callback Endpoint calls the callback function, the SDK performs the following steps in order:

  1. Resolves the tenant: The SDK reads the tenant name from the request's subdomain when your loginUrl contains the {tenant_name} placeholder, or from the tenant_name query parameter that Wristband adds otherwise (e.g. https://yourapp.io/auth/callback?state=f983yr893hf89ewn0idjw8e9f&code=shcsh90jf9wc09j9w0jewc&tenant_name=customer01). If Wristband also passes a tenant_custom_domain query parameter (e.g. ...&tenant_custom_domain=mytenant.com), the SDK validates it with the Validate Custom Domain API and ignores it if it isn't valid.
  2. Verifies the Login State Cookie: The SDK finds the Login State Cookie that matches the state query parameter, clears it, decrypts it, and checks that its state value matches.
  3. Checks for errors from Wristband: If Wristband passed an error query parameter instead of an authorization code, the SDK handles it before going any further.
  4. Exchanges the authorization code for tokens: The SDK calls the Wristband Token Endpoint with the authorization code, along with the PKCE code verifier and redirect URI stored in the Login State Cookie.
  5. Retrieves the user's data: The SDK calls the Wristband Userinfo Endpoint with the new access token. The data returned depends on the scopes in your SDK configuration.
  6. Validates the return URL: If the login request included a return URL, the SDK validates it before returning it.

See the Login State Cookie section of Login for more on the Login State Cookie.

Return Value

The callback function returns a callback result containing the following fields:

FieldTypeDescription
typestringThe result type: completed or redirect_required.
callbackDataCallbackData or undefinedThe callback data used to create your application session. Only present for completed results.
reasonstring or undefinedWhy the login couldn't be completed. Only present for redirect_required results.
redirectUrlstring or undefinedThe URL of your Login Endpoint to redirect the user to. Only present for redirect_required results, and only in SDKs that return a redirect URL. See Redirecting the User to Your App below.
responseredirect responseThe redirect response your Callback Endpoint returns. For completed results, it redirects to the URL from your resolver function. For redirect_required results, it redirects to your Login Endpoint. Only present in SDKs that return a redirect response. See Redirecting the User to Your App below.

The callback function always returns one of two result types:

Result TypeDescription
completedThe login succeeded, and the result includes the callback data needed to create your application session.
redirect_requiredThe login can't be completed, and your Callback Endpoint should redirect the user back to your Login Endpoint to start a new login, without creating a session or doing any other processing.

Redirecting the User to Your App

How your Callback Endpoint redirects the user depends on the SDK.

🚧

Responses Must Not Be Cached

The SDK sets the Cache-Control: no-store and Pragma: no-cache headers on every response from your Callback 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.

Performing the Redirect Manually

Some SDKs leave the redirect to your Callback Endpoint (e.g. Express, and the Next.js Pages Router). For redirect_required results, the SDK provides your Login Endpoint's URL in redirectUrl. For completed results, your Callback Endpoint decides where to send the user.

For example, using the Express Auth SDK:

app.get('/auth/callback', async (req, res) => {
  const callbackResult = await wristbandAuth.callback(req, res);
  const { callbackData, reason, redirectUrl, type } = callbackResult;
 
  // Certain edge cases require a redirect back to the Login Endpoint.
  if (type === 'redirect_required') {
    console.debug(reason);
    res.redirect(redirectUrl);
    return;
  }
 
  // Create the application session from the callback data.
  req.session.fromCallback(callbackData);
  await req.session.save();
 
  // Send the user back into your application.
  res.redirect(callbackData.returnUrl || '<your_app_home_url>');
});

Returning the SDK’s Redirect Response

Some SDKs return a ready-made redirect response for both result types (e.g. the Next.js App Router). For redirect_required results, it redirects to your Login Endpoint, and your Callback Endpoint returns it as-is. For completed results, it redirects to the URL returned by a resolver function you provide, and your Callback Endpoint saves the session onto it before returning it.

For example, using the Next.js Auth SDK with the App Router:

export async function GET(req: NextRequest) {
  const callbackResult = await wristbandAuth.appRouter.callback(req, (request, data) => {
    return data.returnUrl || '/home';
  });
  const { callbackData, response, type } = callbackResult;

  if (type === 'redirect_required') {
    return response;
  }

  const session = await getRequestSession(req);
  session.fromCallback(callbackData);
  return await session.saveToResponse(response);
}

The resolver is passed as the resolveRedirectUrl argument to the callback function, with the following signature, where Request is your framework's request type (e.g. NextRequest in Next.js):

resolveRedirectUrl: (request: Request, callbackData: CallbackData) => string | Promise<string>

It receives the request and the callback data, and the SDK sets the URL it returns as the redirect location on the response:

  • It only runs when the callback completes successfully.
  • It can return the URL directly or as a Promise, such as after looking up data in your own database.
  • The URL can be absolute or a relative path (e.g. /home). A relative path is resolved against the host of the incoming request, which comes from the X-Forwarded-Host header when trustProxyHeaders is enabled.
  • Returning an empty value throws a TypeError.

For example, a resolver that sends new users to an onboarding page:

const callbackResult = await wristbandAuth.appRouter.callback(req, async (request, data) => {
  const hasOnboarded = await db.hasCompletedOnboarding(data.userinfo.userId);
  return hasOnboarded ? data.returnUrl || '/home' : '/onboarding';
});

Completed

A completed result includes the callback data your Callback Endpoint uses to create the application session, typically by passing it to the session's fromCallback() function.

Creating the Session

Your Callback Endpoint creates the application session by passing the callback data to the session's fromCallback() function, as shown in the example at the top of this page. In some SDKs, fromCallback() only changes the session in memory, and your Callback Endpoint must still save the session so the SDK writes the session cookie. Others persist session changes automatically when the response is sent.

See the Session Fields section of Session Management for the fields fromCallback() stores.

Callback Data

Callback Data FieldTypeDescription
accessTokenstringThe access token for calling Wristband APIs and protecting your application's backend APIs.
customStateobject or undefinedThe custom state your application provided during login, returned unmodified.
expiresAtnumberThe access token's expiration time, in milliseconds since the Unix epoch, with the tokenExpirationBuffer already subtracted.
expiresInnumberThe number of seconds until the access token expires, with the tokenExpirationBuffer already subtracted.
idTokenstringThe ID token, which identifies the user and contains claims about them.
refreshTokenstring or undefinedThe refresh token for getting new access tokens. Only present if the offline_access scope was requested and the Refresh Token grant type is enabled on your OAuth2 Client.
returnUrlstring or undefinedThe validated return URL from the login request. Not present if no return URL was provided or if it failed validation.
tenantCustomDomainstring or undefinedThe tenant custom domain used during login, if any.
tenantNamestringThe name of the tenant the user belongs to.
userinfoUserInfoThe user's data from the Wristband Userinfo Endpoint.

UserInfo

The userinfo field maps the claims returned by the Wristband Userinfo Endpoint to the following fields. Which optional fields are present depends on the scopes in your SDK configuration.

UserInfo FieldTypeAlways ReturnedDescription
userIdstringYesThe ID of the user (from the sub claim).
tenantIdstringYesThe ID of the user's tenant (from the tnt_id claim).
applicationIdstringYesThe ID of the user's application (from the app_id claim).
identityProviderNamestringYesThe name of the identity provider the user logged in with (from the idp_name claim).
fullNamestringNoThe user's full name (from the name claim; requires the profile scope).
givenNamestringNoThe user's given or first name (requires the profile scope).
familyNamestringNoThe user's family or last name (requires the profile scope).
middleNamestringNoThe user's middle name (requires the profile scope).
nicknamestringNoThe user's casual name (requires the profile scope).
displayNamestringNoThe name the user prefers to be called (requires the profile scope).
pictureUrlstringNoThe URL of the user's profile picture (requires the profile scope).
emailstringNoThe user's preferred email address (requires the email scope).
emailVerifiedbooleanNoWhether the user's email address has been verified (requires the email scope).
genderstringNoThe user's gender (requires the profile scope).
birthdatestringNoThe user's birthday in YYYY-MM-DD format (requires the profile scope).
timeZonestringNoThe user's time zone (requires the profile scope).
localestringNoThe user's locale as a BCP47 language tag, e.g. en-US (requires the profile scope).
phoneNumberstringNoThe user's phone number in E.164 format (requires the phone scope).
phoneNumberVerifiedbooleanNoWhether the user's phone number has been verified (requires the phone scope).
updatedAtnumberNoWhen the user's information was last updated, as a Unix timestamp (requires the profile scope).
rolesUserInfoRole[]NoThe roles assigned to the user (requires the roles scope).
customClaimsobjectNoAny custom claims configured for your application.

Each role in the roles field has the following fields:

UserInfoRole FieldTypeDescription
idstringThe globally unique ID of the role.
namestringThe role name (e.g. app:app-name:admin).
displayNamestringThe human-readable display name of the role.

Return URL Validation

Before returning the return URL from the login request, the SDK validates it to prevent open redirects, where a crafted login link could send a user to a site you don't control after a legitimate login. The SDK:

  1. Resolves relative URLs (e.g. /settings) against the host of the callback request.
  2. Keeps the URL only if it uses the same protocol as the callback request, and its host is either the exact host of the callback request or your application's root domain.
  3. If applicationAuthorizationRequestsEnabled is true, your application uses tenant subdomains, and the URL points at your root domain, adds the tenant's subdomain to it (e.g. https://yourapp.io/settings becomes https://customer01.yourapp.io/settings).

If the return URL fails validation or can't be parsed, the SDK drops returnUrl and excludes it from the callback data. Your Callback Endpoint should then redirect the user to a default page instead.

Redirect Required

A redirect_required result means the login can't be completed, and the user needs to start a new login.

Reasons

A redirect_required result includes a reason that explains why the login couldn't be completed:

ReasonDescription
missing_login_stateThe Login State Cookie wasn't found, such as when it expired or the user opened a bookmarked callback URL.
invalid_login_stateThe Login State Cookie couldn't be decrypted, or its state value didn't match the state query parameter. This can indicate a forged request or a tampered cookie.
login_requiredWristband returned a login_required error, such as when the user's Wristband session expired.
invalid_grantThe authorization code was invalid, expired, or already used.

The redirect always points back to your Login Endpoint for the same tenant:

  • With tenant subdomains: The tenant subdomain is used in the URL (e.g. https://customer01.yourapp.io/auth/login).
  • Without tenant subdomains: The tenant is passed in the tenant_name query parameter (e.g. https://yourapp.io/auth/login?tenant_name=customer01).
  • With a tenant custom domain: It's also passed in the tenant_custom_domain query parameter (e.g. https://yourapp.io/auth/login?tenant_name=customer01&tenant_custom_domain=mytenant.com).

Errors From Wristband

If Wristband encounters an error while processing the login, it redirects to your Callback Endpoint with error and error_description query parameters instead of an authorization code. For example:

GET https://customer01.yourapp.io/auth/callback?state=f983yr893hf89ewn0idjw8e9f&error=login_required&error_description=User%20must%20re-authenticate%20because%20the%20specified%20max_age%20value%20has%20elapsed

The SDK handles a login_required error by returning a redirect_required result with the login_required reason.

For any other error, the SDK throws a WristbandError containing the error and its description. These errors typically come from SDK configuration issues during development and should be resolved before going to production.



Did this page help you?