For AI agents: the complete documentation index is available at https://dotpress.dev/llms.txt, the full documentation bundle is available at https://dotpress.dev/llms-full.txt, and this page is available as Markdown at https://dotpress.dev/reference/auth.md.

Auth

The settings, routes, errors and contracts of @dotpress/auth. For a guide, see Authentication.

createAuth()

import { createAuth } from '@dotpress/auth'

const auth = createAuth({ keys, issuer: 'https://api.example.com', dataProvider })
SettingDefaultDescription
keysrequiredsecretKey (PKCS#8 PEM) and publicKey (SPKI PEM), plus optional kid and additionalPublicKeys. RS256. See Keys and rotation.
issuerrequiredThe iss claim, checked on every request: the URL of the API, one value per environment and application.
dataProviderrequiredThe storage of users and refresh tokens: see DataProvider.
audienceissuerThe aud claim: the API that accepts the tokens. Checked on every request.
accessTokenTTL900Lifetime of the access tokens, in seconds.
session.idleTimeout14 daysThe session ends when it is not refreshed during this delay, in seconds.
session.absoluteTimeoutnoneThe session ends this delay after login, in seconds, even when active.
clients—{ allowed, required?, isUserRoleAllowed?(role, clientId), sessions? }: see Clients.
cookiesdisabledThe cookie mode: see Cookie settings.
loginThrottleenabled, in memory{ store?, limits? }, or false. See Brute force protection.
beforeLogin—({ username, clientId, ipAddress, req }) => …, after the throttle and before the password check. Return an HttpError to reject the attempt.
onLoginSuccess—Called with { userId, username, clientId, ipAddress }.
onLoginFailed—Called with { username, errorType, clientId, ipAddress }. errorType: invalid_username, invalid_password, invalid_role or too_many_attempts.
passwordHashRounds12bcrypt cost of hashPassword(), and of the dummy hash compared for unknown users.
usernameClaimfalseWrites the username claim in the access tokens.
getUserRolesuser.roles, or user.roleThe roles checked by requireRole().
refreshTokenReuseInterval10Seconds after its first use during which a refresh token presented again gets a new pair in the same session. 0 disables it.
onRefreshTokenReuse—Called with { userId, familyId, jwtId } when a session is revoked because a used refresh token came back.

Invalid settings make createAuth() throw; a missing issuer, a publicKey that does not match the secretKey, or a PKCS#1 key make the app fail at startup, with an explicit message.

SettingDefaultDescription
enabledfalseEnables the cookie mode: login with mode: 'cookie'.
accessTokenInCookiefalseAlso sets the access token as a cookie. Recommended for browsers.
httpOnlytrueHides the cookies from the JavaScript of the page.
securetrueHTTPS only. Browsers accept secure cookies on http://localhost.
sameSite'lax'SameSite of the access token cookie.
refreshTokenSameSite'strict'SameSite of the refresh token cookie; 'none' when sameSite is 'none'.
trustedOriginsthe cors origins with credentials, else []Other origins allowed to send authenticated cookie requests.
domain—Domain of the cookies. Without it, the host only.
path'/'Path of the access token cookie.
refreshTokenPaththe path of the auth routesPath of the refresh token cookie.
accessTokenName__Host-dotpress_access_tokenName of the access token cookie.
refreshTokenName__Secure-dotpress_refresh_tokenName of the refresh token cookie.

See Cookie mode and CSRF.

authPlugin()

authPlugin(auth, { prefix: '/auth', routes: true, jwks: false })
OptionDefaultDescription
prefix'/auth'Prefix of the routes.
routestruefalse keeps only the authentication of the requests.
jwksfalsePublishes the public keys at GET /.well-known/jwks.json, cached 5 minutes.

The plugin is named auth, provides ctx.user and ctx.auth, and declares the bearerAuth security scheme (AUTH_SECURITY_SCHEMES) for OpenAPI.

Routes

RouteBodyResponse
POST /auth/login{ username, password, clientId?, mode? }The tokens (see below), userId, expiresIn, expiresAt, sessionExpiresAt?
POST /auth/refresh{ accessToken, refreshToken }, or cookiesA new pair, same fields
GET /auth/me—The user (AuthUser)
POST /auth/restore— (cookies)userId, user, expiresIn, expiresAt, sessionExpiresAt?. Requires cookies.accessTokenInCookie.
POST /auth/logout{ refreshToken?, scope? }200, and the cookies cleared
ConfigurationCookiesTokens in the body
Body mode—accessToken, refreshToken
cookies: { enabled: true }, mode: 'cookie'refresh tokenaccessToken
cookies: { enabled: true, accessTokenInCookie: true }, mode: 'cookie'bothnone

expiresAt is the expiration of the access token; sessionExpiresAt is set when an absolute timeout applies.

Errors

The routes answer the standard error format; the message carries the reason:

StatusmessageWhen
400Validation failedInvalid body, or a clientId outside clients.allowed
401INVALID_LOGINUnknown username, wrong password, account without password, or a role not allowed for the client
429TOO_MANY_LOGIN_ATTEMPTSOver a limit of the login throttle, with Retry-After
401NO_TOKENS_PROVIDED/auth/refresh without tokens, in the body or as cookies
401INVALID_REFRESH_TOKENUnknown, revoked or reused refresh token, an access token that does not match, or a user findUserById() no longer returns
401EXPIRED_REFRESH_TOKENThe session reached its inactivity or absolute timeout
401COOKIES_NOT_ENABLED, ACCESS_TOKEN_NOT_IN_COOKIE/auth/restore without the cookie mode, or without accessTokenInCookie
401NO_REFRESH_TOKEN, NO_ACCESS_TOKEN/auth/restore without one of the cookies
403CROSS_ORIGIN_REQUESTA cookie request from an untrusted origin, on /auth/refresh, /auth/restore or /auth/logout
401Unauthorized/auth/logout with scope: 'all', without a valid access token or an active refresh token

Outside the routes, the service throws an AuthError whose code is INVALID_ACCESS_TOKEN, INVALID_REFRESH_TOKEN, EXPIRED_REFRESH_TOKEN, REFRESH_TOKEN_REUSED or SESSION_REVOKED.

Guards

MiddlewareAnswersGuarantees
requireAuth401 without userctx.user: AuthUser, ctx.auth: AuthSession
requireRole(...roles)401 without user, 403 without one of the rolesthe same
requireClient(...clientIds)401 without user, 403 for a token of another clientthe same

A 401 gets a WWW-Authenticate: Bearer header (RFC 6750), with error="invalid_token" when the request carried an expired, revoked or invalid token. See Protecting routes.

The service

MethodDescription
generateTokensForUser({ userId, username?, clientId? })Generates and stores a token pair, starting a session.
refreshTokens({ accessToken, refreshToken })Rotates a pair. Returns the new pair, userId and user.
verifyAccessToken(accessToken)Verifies a valid access token, and returns its AuthSession.
revokeSession(refreshToken, { scope? })Revokes the session of a refresh token, or every session of the user with scope: 'all'. Returns { userId, sessionId }, or undefined when nothing was revoked.
hashPassword(password, saltRounds?)bcrypt hash. Throws a RangeError beyond 72 bytes.
verifyPassword(password, hash)Compares with bcrypt; a missing hash (unknown user, account without password) is compared with a dummy one, in the same time, and gives false.
passwordNeedsRehash(hash), rehashPassword(password)Upgrade the cost of a hash.
setAuthCookies(res, tokens), clearAuthCookies(res)Set or clear the cookies, in a route.
isCookieAuthenticationAllowed(req)Promise<boolean>: the protection against cross-site requests of the plugin.
getSessionExpiresAt(session)End of a session with an absolute timeout.
getJwks()The public keys, as a JSON Web Key Set.
getUserRoles(user)The roles of a user.

generatePasswordHash(password, rounds?) and isPasswordTooLong(password) are also exported, without a service.

AuthSession

ctx.auth, the data of the access token:

FieldClaimDescription
userIdsubThe user.
tokenIdjtiThe access token.
sessionIdsidThe session: the familyId of its refresh tokens.
clientIdclient_idThe client, with the clients setting.
usernameusernameWith usernameClaim: true only.
roles—The roles of the user.
claims—Every claim of the token.

Access tokens follow RFC 9068: typ: at+jwt, RS256, and the iss, aud, sub, jti, iat, exp and auth_time claims. auth_time is the start of the session.

DataProvider

interface DataProvider {
  storeRefreshToken(payload: {
    token: string
    tokenHash: string // SHA-256 of the token: store this one only
    userId: string
    jwtId: string // the access token issued with it
    familyId: string // the session
    expiresAt: Date
  }): Promise<void>
  findRefreshToken(tokenHash: string): Promise<
    | {
        userId: string
        jwtId: string
        familyId?: string | null
        expiresAt: Date
        isUsed: boolean
        usedAt?: Date | null
        revokedAt?: Date | null
      }
    | undefined
  >
  markRefreshTokenAsUsed(tokenHash: string, usedAt: Date): Promise<void>
  revokeTokens(userId: string): Promise<void> // every session of a user
  revokeTokenFamily(familyId: string): Promise<void> // one session
  findUserById(userId: string): Promise<AuthUser | undefined> // undefined for deleted or disabled accounts
  findUserIdentifiers(username: string): Promise<
    { userId: string; passwordHash: string | null; role?: string } | undefined
  >
  isSessionRevoked?(sessionId: string): Promise<boolean> // optional: immediate revocation
  updatePasswordHash?(userId: string, passwordHash: string): Promise<void> // optional: hash upgrade
}

isUsed can be derived from usedAt. Refresh tokens are 128 random bytes; only their hash is stored. See Data provider.