# auth.access-token The access-token rules of a small API, so its request handlers are only I/O: ``` # login, after the password checked out token = issueAccessToken(str(user.id), user.name, user.email, user.token_version, random_jti, now, 3600, secret) # -> {accessToken, tokenType: "Bearer", expiresAt: "2026-09-26T13:00:00Z", expiresIn: 3600} # every authenticated request check = readAccessToken(headers.get("Authorization"), secret, now, 30) if check.ok: user = db.user(check.subject) # may be None check = confirmAccessToken(check, user and user.token_version, db.is_revoked(check.jti)) if not check.ok: answer 401 # check.error says why, for the log ``` It is a group because the three share one token layout: `issueAccessToken` writes the claims `sub`, `name`, `email`, `ver`, `jti`, `iat` and `exp`, signed HS256 with `auth.jwt`, and `readAccessToken` insists on finding `sub`, `jti`, `ver` and `exp` in anything it accepts. A token that is validly signed but lacks them (or has no `exp`, so would never expire) is `invalid_claims`. **Revocation** takes two forms, and `confirmAccessToken` applies both once the caller has done the lookups a pure function cannot: - **Log out** revokes one token: store its `jti` (until its `expiresAt`, after which it is dead anyway) and pass `revoked = true` when it comes back. - **Change password** revokes every token the user holds: increment the user's token version in the database. Every token carries the version it was issued at (`ver`), and one that no longer matches is refused. - A user who no longer exists (`currentTokenVersion` null) is `user_not_found`. A check that has already failed passes through `confirmAccessToken` unchanged, so the three calls can be chained without branching. Nothing here reads the clock or makes randomness: `now` is Unix seconds from the caller and `jti` is random text from the caller (for instance `secrets.token_urlsafe(16)`). `expiresAt` is written by `time.unix-to-iso`. Errors from `readAccessToken` are `auth.jwt`'s codes plus `missing_token` (no `Authorization: Bearer` header, read with `auth.bearer-token`); an API should answer all of them with the same 401 and keep the code for its logs. A secret shorter than 32 bytes, a negative leeway, an empty subject or jti, a negative token version or a lifetime under one second are the caller's mistakes and throw. Sources: RFC 7519 sections 4.1.2 (sub), 4.1.4 (exp), 4.1.6 (iat), 4.1.7 (jti) (https://www.rfc-editor.org/rfc/rfc7519); RFC 6750 section 3, the WWW-Authenticate response for 401 (https://www.rfc-editor.org/rfc/rfc6750).