Skip to content
TDACorpIDENTITYSign in

Next.js adapter

Mount login, callback, logout and back-channel logout in a Next.js app with one catch-all route, and what each route does.

On this page

The Next.js adapter turns the relying-party module into four route handlers. It is imported from the SDK's rp/next subpath. The handlers are plain functions of the standard Request and Response, so the underlying createAuthRoutes works in any runtime that speaks them; the adapter only dispatches by path.

This page is the reference for the handlers. The walkthrough is in the Quickstart and the behaviour you must build around them is in Log users in.

Mount#

Put one catch-all route file where appOrigin plus the path equals your registered redirect address.

import { createNextAuthHandlers } from '@tdacorp/identity-sdk/rp/next'const handlers = createNextAuthHandlers({  rp,  appOrigin: 'https://app.example.com',  cookieSecret,  onLogin: async (login) => {    // login.idToken.sub, login.tokens, login.returnTo, login.redirectTo    return createSessionAndRedirect(login)  },})export const GET = handlers.GETexport const POST = handlers.POST

createSessionAndRedirect is yours. handlers also exposes login, callback, logout and backchannelLogout for a file-per-route layout, for example export const GET = handlers.login.

Routes#

The adapter reads only the last path segment of the request, never the host.

Path ends inMethodWhat it does
loginGETPushes the authorization request, seals the transaction into a cookie and redirects (302) to identity. Reads returnTo from the query.
callbackGETChecks the response, exchanges the code, verifies the ID token and calls onLogin.
logoutPOST (or GET with allowGet)Clears your cookies and redirects (303) to identity's end-session address. Only when you pass logout.
backchannel-logoutPOSTReceives a logout token from identity. Only when you pass backchannelLogout.

Anything else answers 404 with {"error":"not_found"}. In the catch-all route a wrong method on a known segment is also 404. A 405 with Allow comes only from a handler you mount on its own path.

Options#

OptionDefaultMeaning
rprequiredA relying party.
appOriginrequiredA bare origin. https, or http for loopback with secureCookies: false.
cookieSecretrequiredSeals the transaction cookie. At least 32 bytes, or a map for rotation. See Sealed sessions.
onLoginrequired(login, request) => Response. Create your session here.
onError400 JSONTurns a failed callback into a response. Receives { code, message, cause }.
scopeopenid profile emailMust include openid.
authorizationnoneExtra request members: maxAge, acrValues, prompt, tenant, resource, loginHint, uiLocales.
getTenantnone(request) => tenantId or undefined: picks the organization for each sign-in, for an app whose people belong to different organizations. It wins over authorization.tenant; undefined falls back to it. A value that is not a canonical lowercase tenant id refuses the login with invalid_tenant.
defaultReturnTo/Used when there is no valid returnTo.
getReturnTothe returnTo queryRead it from elsewhere.
logoutroute is 404{ postLogoutRedirectUri, getIdTokenHint?, clearSessionCookies?, allowGet? }
backchannelLogoutroute is 404{ onLogout, claimJti?, releaseJti? }
transactionCookieName__Host-tdaid_txnThe cookie base name. __Host- requires Secure.
maxConcurrentLogins5Logins in flight, 1 to 10. Oldest are dropped.
transactionTtlSeconds600How long a person has to finish.
secureCookiestruefalse only for an http loopback origin.

What onLogin receives#

LoginSuccess: everything handleCallback returns (tokens, idToken), plus returnTo, the sanitized path or null, and redirectTo, an absolute address on appOrigin. Return a Response, normally a redirect to redirectTo that sets your own session cookie. The transaction cookie is expired for you on every outcome.

Errors#

A failed callback becomes a RouteError with one of these codes, passed to onError. The default response is 400 with { "error": code, "error_description": message }, or 500 for internal_error. The message is fixed and never repeats text from identity.

CodeCause
invalid_tenantgetTenant returned something that is not a canonical tenant id. The login starts nothing; the default answer is 400.
provider_errorIdentity returned an error.
missing_state, missing_transaction, invalid_transaction, state_mismatchThe browser's transaction is absent, expired, edited or does not match.
invalid_responseA repeated parameter, a missing or wrong iss, or a missing code.
token_exchange_failedThe token endpoint refused or could not be reached.
token_verification_failedThe ID token failed a check.
invalid_tenantgetTenant returned something that is not a canonical tenant id. Raised on login, before anything is sent to identity.
internal_errorAnything else, including an exception from onLogin or getTenant.

Back-channel logout#

The route accepts only application/x-www-form-urlencoded, a body of at most 8192 bytes and exactly one logout_token. It verifies the token, calls onLogout with sid, jti and the rest, and answers 200. A bad request is 400; an exception in your onLogout is 500, which lets identity retry. Replay protection defaults to an in-memory store per instance; pass claimJti and releaseJti over shared storage when you run more than one. See Back-channel logout.

Security notes#

  • Absolute addresses come from appOrigin. The Host header and X-Forwarded-* are never read.
  • returnTo must be a path on your app. A protocol-relative address, a backslash, a control character or a javascript: style value becomes the default.
  • Logout refuses a cross-origin request with 403, and refuses GET unless you set allowGet.
  • Without an ID token hint the logout route still redirects to identity, but identity then asks the person to confirm; see End session.

Verified against commit 5d85077