@auth0/nextjs-auth0
Version:
Auth0 Next.js SDK
377 lines (376 loc) • 16.9 kB
TypeScript
import { NextResponse, type NextRequest } from "next/server.js";
import { RequestCookies, ResponseCookies } from "@edge-runtime/cookies";
import * as jose from "jose";
import { AccessTokenForConnectionError, ConnectAccountError, ConnectAccountErrorCodes, CustomTokenExchangeError, SdkError } from "../errors/index.js";
import { CompleteConnectAccountResponse, ConnectAccountOptions } from "../types/connected-accounts.js";
import { DpopKeyPair, DpopOptions } from "../types/dpop.js";
import { AccessTokenForConnectionOptions, Authenticator, AuthorizationParameters, BackchannelAuthenticationOptions, BackchannelAuthenticationResponse, ChallengeResponse, ConnectionTokenSet, CustomTokenExchangeOptions, CustomTokenExchangeResponse, EnrollmentResponse, EnrollOptions, GetAccessTokenOptions, LogoutStrategy, MfaVerifyResponse, RESPONSE_TYPES, SessionData, StartInteractiveLoginOptions, TokenSet, VerifyMfaOptions } from "../types/index.js";
import { AccessTokenFactory, Fetcher, FetcherMinimalConfig } from "./fetcher.js";
import { AbstractSessionStore } from "./session/abstract-session-store.js";
import { TransactionStore } from "./transaction-store.js";
export type BeforeSessionSavedHook = (session: SessionData, idToken: string | null) => Promise<SessionData>;
export type OnCallbackContext = {
/**
* The type of response expected from the authorization server.
* One of {@link RESPONSE_TYPES}
*/
responseType?: RESPONSE_TYPES;
/**
* The URL or path the user should be redirected to after completing the transaction.
*/
returnTo?: string;
/**
* The connected account information when the responseType is {@link RESPONSE_TYPES.CONNECT_CODE}
*/
connectedAccount?: CompleteConnectAccountResponse;
};
export type OnCallbackHook = (error: SdkError | null, ctx: OnCallbackContext, session: SessionData | null) => Promise<NextResponse>;
export interface Routes {
login: string;
logout: string;
callback: string;
profile: string;
accessToken: string;
backChannelLogout: string;
connectAccount: string;
mfaAuthenticators: string;
mfaChallenge: string;
mfaVerify: string;
mfaEnroll: string;
}
export type RoutesOptions = Partial<Pick<Routes, "login" | "callback" | "logout" | "backChannelLogout" | "connectAccount">>;
/**
* @private
*/
export interface AuthClientOptions {
transactionStore: TransactionStore;
sessionStore: AbstractSessionStore;
domain: string;
clientId: string;
clientSecret?: string;
clientAssertionSigningKey?: string | jose.CryptoKey;
clientAssertionSigningAlg?: string;
authorizationParameters?: AuthorizationParameters;
pushedAuthorizationRequests?: boolean;
secret: string;
appBaseUrl: string;
signInReturnToPath?: string;
logoutStrategy?: LogoutStrategy;
includeIdTokenHintInOIDCLogoutUrl?: boolean;
beforeSessionSaved?: BeforeSessionSavedHook;
onCallback?: OnCallbackHook;
routes: Routes;
fetch?: typeof fetch;
jwksCache?: jose.JWKSCacheInput;
allowInsecureRequests?: boolean;
httpTimeout?: number;
enableTelemetry?: boolean;
enableAccessTokenEndpoint?: boolean;
noContentProfileResponseWhenUnauthenticated?: boolean;
enableConnectAccountEndpoint?: boolean;
useDPoP?: boolean;
dpopKeyPair?: DpopKeyPair;
dpopOptions?: DpopOptions;
/**
* MFA token TTL in seconds (for token encryption expiration).
* Default: 300 (5 minutes, matching Auth0's mfa_token expiration)
*/
mfaTokenTtl?: number;
}
/**
* @private
*/
export declare class AuthClient {
#private;
private transactionStore;
private sessionStore;
private clientMetadata;
private clientSecret?;
private clientAssertionSigningKey?;
private clientAssertionSigningAlg;
private domain;
private authorizationParameters;
private pushedAuthorizationRequests;
private appBaseUrl;
private signInReturnToPath;
private logoutStrategy;
private includeIdTokenHintInOIDCLogoutUrl;
private beforeSessionSaved?;
private onCallback;
private routes;
private fetch;
private jwksCache;
private allowInsecureRequests;
private httpTimeout;
private httpOptions;
private authorizationServerMetadata?;
private readonly enableAccessTokenEndpoint;
private readonly noContentProfileResponseWhenUnauthenticated;
private readonly enableConnectAccountEndpoint;
private dpopOptions?;
private dpopKeyPair?;
private readonly useDPoP;
private readonly mfaTokenTtl;
private proxyFetchers;
constructor(options: AuthClientOptions);
handler(req: NextRequest): Promise<NextResponse>;
startInteractiveLogin(options?: StartInteractiveLoginOptions): Promise<NextResponse>;
handleLogin(req: NextRequest): Promise<NextResponse>;
handleLogout(req: NextRequest): Promise<NextResponse>;
handleCallback(req: NextRequest): Promise<NextResponse>;
handleProfile(req: NextRequest): Promise<NextResponse>;
/**
* Route: GET /auth/mfa/authenticators
* Lists enrolled MFA authenticators.
*
* Headers:
* Authorization: Bearer <encrypted-mfa-token>
*
* Response: 200 + Authenticator[]
* Error: 400/401 + {error, error_description}
*/
handleGetAuthenticators(req: NextRequest): Promise<NextResponse>;
/**
* Route: POST /auth/mfa/challenge
* Initiates an MFA challenge.
*
* Body: {mfaToken, challengeType, authenticatorId?}
*
* Response: 200 + ChallengeResponse
* Error: 400 + {error, error_description}
*/
handleChallenge(req: NextRequest): Promise<NextResponse>;
/**
* Route: POST /auth/mfa/enroll
* Enrolls a new MFA authenticator.
*
* Body: {mfaToken, authenticatorTypes, oobChannels?, phoneNumber?, email?}
*
* Response: 200 + EnrollmentResponse
* Error: 400 + {error, error_description}
*/
handleEnroll(req: NextRequest): Promise<NextResponse>;
/**
* Route: POST /auth/mfa/verify
* Verifies MFA code and returns tokens.
*
* Body: {mfaToken, otp | oobCode+bindingCode | recoveryCode}
*
* Response: 200 + TokenResponse + Set-Cookie
* Error: 400 + {error, error_description, mfaToken?}
*/
handleVerify(req: NextRequest): Promise<NextResponse>;
handleAccessToken(req: NextRequest): Promise<NextResponse>;
handleBackChannelLogout(req: NextRequest): Promise<NextResponse>;
handleConnectAccount(req: NextRequest): Promise<NextResponse>;
handleMyAccount(req: NextRequest): Promise<NextResponse>;
handleMyOrg(req: NextRequest): Promise<NextResponse>;
/**
* Retrieves OAuth token sets, handling token refresh when necessary or if forced.
*
* @returns A tuple containing either:
* - `[SdkError, null]` if an error occurred (missing refresh token, discovery failure, or refresh failure)
* - `[null, {tokenSet, idTokenClaims}]` if a new token was retrieved, containing the new token set ID token claims
* - `[null, {tokenSet, }]` if token refresh was not done and existing token was returned
*/
getTokenSet(sessionData: SessionData, options?: GetAccessTokenOptions): Promise<[null, GetTokenSetResponse] | [SdkError, null]>;
backchannelAuthentication(options: BackchannelAuthenticationOptions): Promise<[null, BackchannelAuthenticationResponse] | [SdkError, null]>;
private discoverAuthorizationServerMetadata;
private defaultOnCallback;
/**
* Handle callback errors with transaction cleanup
*/
private handleCallbackError;
private verifyLogoutToken;
private authorizationUrl;
private getClientAuth;
private get issuer();
/**
* Exchanges a refresh token for an access token for a connection.
*
* This method performs a token exchange using the provided refresh token and connection details.
* It first checks if the refresh token is present in the `tokenSet`. If not, it returns an error.
* Then, it constructs the necessary parameters for the token exchange request and performs
* the request to the authorization server's token endpoint.
*
* @returns {Promise<[AccessTokenForConnectionError, null] | [null, ConnectionTokenSet]>} A promise that resolves to a tuple.
* The first element is either an `AccessTokenForConnectionError` if an error occurred, or `null` if the request was successful.
* The second element is either `null` if an error occurred, or a `ConnectionTokenSet` object
* containing the access token, expiration time, and scope if the request was successful.
*
* @throws {AccessTokenForConnectionError} If the refresh token is missing or if there is an error during the token exchange process.
*/
getConnectionTokenSet(tokenSet: TokenSet, connectionTokenSet: ConnectionTokenSet | undefined, options: AccessTokenForConnectionOptions): Promise<[
AccessTokenForConnectionError,
null
] | [null, ConnectionTokenSet]>;
/**
* Validates that subject_token_type is a valid URI.
*
* Validation rules:
* - Length: 10-100 characters
* - Format: Valid URL or URN
*
* Note: Reserved namespace validation is handled by Auth0 when creating CTE profiles.
*
* @param type - The subject_token_type to validate
* @returns CustomTokenExchangeError if invalid, null if valid
*/
private validateSubjectTokenType;
/**
* Exchanges an external token for Auth0 tokens via Custom Token Exchange (RFC 8693).
*
* This method allows you to exchange a token from an external identity provider
* for Auth0 tokens without a browser redirect. The external token is validated
* by your Auth0 Action with the Custom Token Exchange trigger.
*
* **Note**: CTE tokens are not cached per RWA SDK spec. The caller is responsible
* for token storage if needed.
*
* @param options - The custom token exchange options
* @returns A tuple of [error, null] or [null, response]
*
* @example
* ```typescript
* const [error, response] = await authClient.customTokenExchange({
* subjectToken: legacyIdToken,
* subjectTokenType: 'urn:acme:legacy-token',
* audience: 'https://api.example.com',
* scope: 'read:data'
* });
* ```
*
* @see {@link https://auth0.com/docs/authenticate/custom-token-exchange Auth0 CTE Documentation}
* @see {@link https://datatracker.ietf.org/doc/html/rfc8693 RFC 8693 Token Exchange}
*/
customTokenExchange(options: CustomTokenExchangeOptions): Promise<[
CustomTokenExchangeError,
null
] | [null, CustomTokenExchangeResponse]>;
/**
* Filters and processes ID token claims for a session.
*
* If a `beforeSessionSaved` callback is configured, it will be invoked to allow
* custom processing of the session and ID token. Otherwise, default filtering
* will be applied to remove standard ID token claims from the user object.
*/
finalizeSession(session: SessionData, idToken?: string): Promise<SessionData>;
/**
* Initiates the connect account flow for linking a third-party account to the user's profile.
* The user will be redirected to authorize the connection.
*/
connectAccount(options: ConnectAccountOptions & {
tokenSet: TokenSet;
}): Promise<[ConnectAccountError, null] | [null, NextResponse]>;
private createConnectAccountTicket;
private completeConnectAccount;
private getOpenIdClientConfig;
/**
* Creates a new Fetcher instance with DPoP support and authentication capabilities.
*
* This method creates fetcher-scoped DPoP handles via `oauth.DPoP(this.clientMetadata, this.dpopKeyPair!)`.
* Each fetcher instance maintains its own DPoP nonce state for isolation and security.
* It is recommended to create fetchers at module level and reuse them across requests
*
* @example Recommended fetcher reuse pattern
* ```typescript
* const managementApi = await auth0.fetcherFactory({
* baseUrl: `https://${process.env.AUTH0_DOMAIN}/api/v2/`,
* session: await getSession(req, res)
* });
*
* // Use the same fetcher for multiple requests
* const users = await managementApi.get('users');
* const roles = await managementApi.get('roles');
* ```
*
* **DPoP Nonce Management:**
* - Each fetcher learns and caches nonces from the authorization server
* - Failed nonce validation triggers automatic retry with updated nonce
* - Nonce state is isolated between fetcher instances for security
*
* @param options Configuration options for the fetcher
* @returns Promise resolving to a configured Fetcher instance
* @throws {DPoPError} When DPoP is enabled but no keypair is configured
*/
fetcherFactory<TOutput extends Response>(options: FetcherFactoryOptions<TOutput>): Promise<Fetcher<TOutput>>;
/**
* List enrolled MFA authenticators, filtered by allowed challenge types.
*
* @param encryptedToken - Encrypted MFA token from MfaRequiredError
* @returns Array of authenticators filtered by mfa_requirements
* @throws {MfaTokenInvalidError} If token cannot be decrypted
* @throws {MfaTokenExpiredError} If token expired (>5 min)
* @throws {MfaGetAuthenticatorsError} On Auth0 API failure
*/
mfaGetAuthenticators(encryptedToken: string): Promise<Authenticator[]>;
/**
* Initiate an MFA challenge.
*
* @param encryptedToken - Encrypted MFA token from MfaRequiredError
* @param challengeType - Challenge type (otp, oob)
* @param authenticatorId - Optional authenticator ID
* @returns Challenge response (oobCode, bindingMethod)
* @throws {MfaTokenInvalidError} If token cannot be decrypted
* @throws {MfaTokenExpiredError} If token expired
* @throws {MfaNoAvailableFactorsError} If no challenge types available
* @throws {MfaChallengeError} On Auth0 API failure
*/
mfaChallenge(encryptedToken: string, challengeType: string, authenticatorId?: string): Promise<ChallengeResponse>;
/**
* Enroll a new MFA authenticator during initial MFA setup.
* First-time enrollment only (requires mfa_token, not access token).
*
* @param encryptedToken - Encrypted MFA token from MfaRequiredError
* @param options - Enrollment options (otp | oob | email)
* @returns Enrollment response with authenticator details and optional recovery codes
* @throws {MfaTokenInvalidError} If token cannot be decrypted
* @throws {MfaTokenExpiredError} If token expired
* @throws {MfaEnrollmentError} On Auth0 API failure
*/
mfaEnroll(encryptedToken: string, options: Omit<EnrollOptions, "mfaToken">): Promise<EnrollmentResponse>;
/**
* Verify MFA code and complete authentication.
* Returns tokens without session persistence.
*
* @param options - Verification options (otp | oobCode+bindingCode | recoveryCode)
* @returns Token response
* @throws {MfaVerifyError} On API failure
* @throws {MfaRequiredError} For chained MFA (with encrypted token)
*/
mfaVerify(options: VerifyMfaOptions): Promise<MfaVerifyResponse>;
/**
* Cache access token from MFA verification in session.
*
* @param tokenResponse - Token response from mfaVerify
* @param encryptedMfaToken - Encrypted MFA token containing context
* @param reqCookies - Request cookies for session retrieval
* @param resCookies - Response cookies for session persistence
* @throws {MfaVerifyError} If session not found
*/
cacheTokenFromMfaVerify(tokenResponse: MfaVerifyResponse, encryptedMfaToken: string, reqCookies: RequestCookies, resCookies: ResponseCookies): Promise<void>;
}
type GetTokenSetResponse = {
tokenSet: TokenSet;
idTokenClaims?: {
[key: string]: any;
};
};
/**
* Options for creating a Fetcher instance via the factory method.
*
* Includes all FetcherMinimalConfig options plus internal session data.
* The `nonceStorageId` from FetcherMinimalConfig is included but currently ignored.
*/
export type FetcherFactoryOptions<TOutput extends Response> = {
useDPoP?: boolean;
getAccessToken: AccessTokenFactory;
} & FetcherMinimalConfig<TOutput>;
/**
* Builds a ConnectAccountError response based on the provided Response object and error code.
* @param res The Response object containing the error details.
* @param errorCode The ConnectAccountErrorCodes enum value representing the type of error.
* @returns
*/
export declare function buildConnectAccountErrorResponse(res: Response, errorCode: ConnectAccountErrorCodes): Promise<[ConnectAccountError, null]>;
export {};