Middleware Reference
Every middleware exported by @riavzon/auth is documented here. Middleware functions follow the standard Express (req, res, next) signature unless noted otherwise. When using bootstrapApp(), the global middleware stack is already wired in the correct order. You only need to apply route-level middleware when building custom routes.
configuration() or bootstrapApp() before mounting any of these functions.For the full middleware stack order applied by bootstrapApp(), see the Service page. For route-level middleware chains per endpoint, see the Routes Reference.
Authentication Guards
These middleware functions enforce token presence and validity on protected routes. They don't verify the token contents themselves -- that responsibility belongs to protectRoute.
requireAccessToken
Extracts the Bearer token from the Authorization header and attaches it to req.token. If the header is missing or malformed, the request is rejected immediately.
Import
import { requireAccessToken } from '@riavzon/auth'
Reads
| Source | Value |
|---|---|
Authorization header | Must start with Bearer followed by the token string |
Sets
| Target | Value |
|---|---|
req.token | The raw access token string (header value after Bearer ) |
Responses on failure
| Status | Body |
|---|---|
401 | { ok: false, error: 'Missing Bearer token' } |
401 | { error: 'Access token missing' } (empty token after prefix) |
protectRoute downstream for full verification.checkForActiveMfa
Checks the anomaly cache for the current refresh token and short-circuits
requests that already have an unresolved MFA or re-login requirement. It hashes
req.cookies.session, looks up that key in anomaliesCache(), and returns the
cached response before the request reaches protectRoute or a sensitive
controller.
Import
import { checkForActiveMfa } from '@riavzon/auth'
Reads
| Source | Value |
|---|---|
req.cookies.session | The refresh token cookie |
Responses on failure
| Status | Body |
|---|---|
202 | { mfa: true, message: 'A login link has been sent to your email.' } |
401 | { error: 'Re-login is required', message: cached.anomalyType } |
session cookie is missing, or if the cached anomaly is already
resolved, this middleware calls next() and leaves the request untouched.getFingerPrint and before protectRoute or the
controller.requireRefreshToken
Checks that the session cookie containing the refresh token is present. Rejects the request if the cookie is missing.
Import
import { requireRefreshToken } from '@riavzon/auth'
Responses on failure
| Status | Body |
|---|---|
401 | { error: 'Re-login is required', message: cached.anomalyType } |
202 | { mfa: true, message: 'A login link has been sent to your email.' } |
Reads
| Source | Value |
|---|---|
req.cookies.session | The refresh token cookie |
Responses on failure
| Status | Body |
|---|---|
401 | { error: 'Refresh token missing' } |
protectRoute or the rotation controllers handle the actual verification.protectRoute
The primary authentication middleware. Verifies the access token signature, decodes the payload, runs the full anomaly detection pipeline against the session context, and enriches the request with the authenticated user's identity. If anomalies trigger MFA, the middleware sends the challenge email and responds with 202.
Import
import { protectRoute } from '@riavzon/auth'
Reads
| Source | Value |
|---|---|
req.token | The raw access token (set by requireAccessToken) |
req.cookies.session | The refresh token cookie |
req.cookies.canary_id | The canary cookie for anomaly detection |
req.ip | Client IP address |
req.fingerPrint | Device fingerprint (set by getFingerPrint) |
User-Agent header | Browser and device identification |
Sets
| Target | Value |
|---|---|
req.user.userId | The authenticated user's numeric ID |
req.user.visitor_id | The visitor identifier string |
req.user.accessTokenId | The access token's jti claim (useful for rate limiter keying) |
req.user.roles | Array of role strings from the token payload |
req.user.payload | The full decoded JWT payload object |
Responses on failure
| Status | Body | Meaning |
|---|---|---|
202 | MFA challenge email sent | Anomaly detection triggered adaptive MFA |
401 | { error: '...' } or { error: '...', reason: '...' } | Missing token, missing refresh cookies, malformed payload, verification failed, or re-login required (includes reason field with the anomaly description) |
500 | Internal error | MFA dispatch failure or verification flow error |
Anomaly detection
The middleware calls strangeThings() internally, which runs nine checks including cookie binding, IP drift, user-agent drift, and geo anomalies. When anomalies are detected but the session is recoverable, the middleware automatically sends an MFA challenge email to the user and responds with 202 Accepted. The client must then complete the MFA flow before retrying the original request.
See the full Anomaly Detection pipeline for details on each check.
requireAccessToken and requireRefreshToken before protectRoute in the middleware chain. The protectRoute middleware expects req.token and the refresh cookies to already be present.Recommended chain
router.get('/protected',
requireAccessToken,
requireRefreshToken,
getFingerPrint,
protectRoute,
yourHandler
)
acceptCookieOnly
A strict input guard for cookie-only endpoints such as token rotation and logout. Rejects any request that carries a body, query string, or Content-Type header. This prevents injection vectors on endpoints that should only read from cookies.
Import
import { acceptCookieOnly } from '@riavzon/auth'
Reads
| Source | Value |
|---|---|
req.cookies.session | Must be present (string) |
req.body | Must be empty or absent |
content-length header | Must be 0 or absent |
transfer-encoding header | Must not contain chunked |
| Query string | Must be empty |
Content-Type header | Must be absent |
Responses on failure
| Status | Body |
|---|---|
401 | { error: 'Refresh token missing' } |
400 | { error: 'Request body not allowed' } |
400 | { error: 'Query string not allowed' } |
400 | { error: 'Content-Type not allowed' } |
requireRefreshToken but before the controller. The middleware validates that only cookies are used for authentication data -- no body, no query parameters, no content type.Security Middleware
These middleware functions are applied globally by bootstrapApp(). You typically don't need to add them manually unless you're building a custom Express app without bootstrapApp().
helmet
Applies the Helmet security headers to every response. Pre-configured with the service's security policy.
Headers set
| Header | Value |
|---|---|
X-Frame-Options | DENY |
Content-Security-Policy | frame-ancestors 'none' |
Referrer-Policy | origin |
Cross-Origin-Embedder-Policy | Enabled |
| Standard Helmet defaults | X-Content-Type-Options, X-DNS-Prefetch-Control, etc. |
@riavzon/auth. It is applied automatically by bootstrapApp().headers
Sets standard no-cache response headers on every response. This prevents browsers and proxies from caching sensitive authentication responses.
Headers set
| Header | Value |
|---|---|
Cache-Control | no-cache, private, max-age=0 |
Pragma | no-cache |
Expires | 0 |
@riavzon/auth. It is applied automatically by bootstrapApp().validateIp
Validates that req.ip resolves to a valid IP address using Node's net.isIP(). If the IP is missing or invalid, the request is rejected with 403 Forbidden.
Responses on failure
| Status | Body |
|---|---|
403 | Forbidden (plain text) |
@riavzon/auth. It is applied automatically by bootstrapApp().hmacAuth
Verifies inbound requests using HMAC headers and a shared secret. Only active when service.Hmac is configured. Validates the client identity, timestamp freshness, signature integrity, and request uniqueness (replay protection via nonce cache).
Required headers
| Header | Description |
|---|---|
X-Client-Id | The client identifier matching the configured clientId |
X-Timestamp | Client timestamp in milliseconds |
X-Signature | HMAC-SHA256 hex digest of clientId:timestamp:method:url:requestId |
X-Request-ID | A unique request identifier (nonce) |
Validation steps
- Checks that all four headers are present
- Verifies the
X-Client-Idmatches the configured value - Rejects requests with clock skew exceeding the configured threshold (default: 5 minutes)
- Rejects replayed request IDs using an LRU nonce cache
- Recomputes the HMAC-SHA256 signature and compares using
crypto.timingSafeEqual()
Bypass
- Local
GET /healthrequests (from127.0.0.1or::1) are allowed without HMAC headers
Responses on failure
| Status | Body |
|---|---|
401 | Reason string (missing headers, unknown client, timestamp skew, replay detected, or signature mismatch) |
See the HMAC Authentication page for integration details and client-side signing examples.
@riavzon/auth. It is applied automatically by bootstrapApp() when service.Hmac is configured.validateContentType
A factory function that returns middleware enforcing a specific Content-Type header. If the request's content type does not match the expected value, the request is rejected with 403 Forbidden.
Import
import { validateContentType } from '@riavzon/auth'
Signature
function validateContentType(expected: string): RequestHandler
Parameters
'application/json'.Usage
router.post('/data',
validateContentType('application/json'),
express.json(),
yourHandler
)
Responses on failure
| Status | Body |
|---|---|
403 | { error: 'not allowed.' } |
Verification Middleware
These middleware functions handle magic link and MFA verification on specific routes. They are used in the magic link route chains and are also exported for custom route composition.
verifyMFA
Verifies a one-time MFA code submitted via email. On success, issues new access and refresh tokens and calls next(). On failure, responds with the appropriate error status.
Import
import { verifyMFA } from '@riavzon/auth'
Reads
| Source | Value |
|---|---|
req.body.code | The MFA code submitted by the user |
req.link.purpose | Must be MAGIC_LINK_MFA_CHECKS (set by upstream link verification) |
req.link.subject | Must match MAGIC_LINK_MFA_CHECKS_{visitor} |
req.link.visitor | The visitor ID from the verified magic link |
Responses
| Status | Meaning |
|---|---|
200 | Code verified, new tokens issued |
400 | Malformed input or purpose/subject mismatch |
401 | Invalid or expired code |
403 | User is banned |
500 | Internal error |
See MFA for the full adaptive MFA flow.
verifyNewPassword
Handles the password reset completion step after a user clicks the magic link. Validates the new password against the Zod schema, checks for data breach exposure, hashes the password, and updates it in the database.
Import
import { verifyNewPassword } from '@riavzon/auth'
Reads
| Source | Value |
|---|---|
req.body.password | The new password |
req.body.confirmedPassword | Password confirmation (must match) |
req.link.purpose | Must be PASSWORD_RESET (set by upstream link verification) |
req.link.subject | Must match PASSWORD_RESET_{visitor} |
req.link.jti | The link's unique identifier for rate limiting |
Responses
| Status | Meaning |
|---|---|
200 | Password updated |
400 | Validation error, password mismatch, or password found in data breaches |
403 | XSS attempt detected (user banned) |
Rate limiting
This middleware applies per-IP and per-composite-key (ip_visitor) rate limiting with consecutive failure tracking. See Rate Limiting for details.
customMfaFlowsVerification
Verifies custom MFA magic links used for sensitive actions such as account deletion, payment confirmation, or email changes. Validates the temporary JWT token, checks the random hash using crypto.timingSafeEqual(), enforces single-use semantics, and populates req.link on success.
Import
import { customMfaFlowsVerification } from '@riavzon/auth'
Reads
| Source | Value |
|---|---|
req.query.token | The temporary JWT token from the magic link URL |
req.query.random | The random hash for cryptographic verification |
req.query.reason | The MFA flow reason (for example, PAYMENT_CONFIRM) |
req.query.visitor | The visitor identifier |
Sets
| Target | Value |
|---|---|
req.link.visitor | The verified visitor ID |
req.link.subject | The link subject string |
req.link.purpose | The link purpose/reason |
req.link.jti | The link's unique identifier |
Behavior by HTTP method
| Method | Behavior |
|---|---|
GET | Preview the link status. Limited to a configurable number of previews. Returns 200 with link metadata. |
POST | Consume the link. Limited to a single use. On success, populates req.link and calls next(). |
Responses on failure
| Status | Meaning |
|---|---|
400 | Invalid, expired, or already-used link; validation errors |
401 | Payload mismatch or hash verification failure |
403 | XSS attempt detected |
Rate limiting
Applies per-IP rate limiting with consecutive failure tracking. See Rate Limiting.
See also: Magic Links for the full custom MFA flow documentation.
Request Enrichment
getFingerPrint
Collects device, browser, geo, and network information from the incoming request and attaches it to req.fingerPrint. Uses @riavzon/bot-detector for User-Agent parsing and IP geolocation.
Import
import { getFingerPrint } from '@riavzon/auth'
Sets
| Target | Type | Description |
|---|---|---|
req.fingerPrint.userAgent | string | Raw User-Agent header |
req.fingerPrint.ipAddress | string | Client IP address |
req.fingerPrint.country | string? | Country name |
req.fingerPrint.countryCode | string? | ISO country code |
req.fingerPrint.region | string? | Region/state code |
req.fingerPrint.regionName | string? | Region/state full name |
req.fingerPrint.city | string? | City name |
req.fingerPrint.district | string? | City district or subdivision |
req.fingerPrint.lat | string? | Latitude |
req.fingerPrint.lon | string? | Longitude |
req.fingerPrint.timezone | string? | IANA timezone |
req.fingerPrint.currency | string? | Local currency code |
req.fingerPrint.isp | string? | Internet service provider |
req.fingerPrint.org | string? | Organization name |
req.fingerPrint.as_org | string? | Autonomous system organization |
req.fingerPrint.proxy | boolean? | Whether the IP is a known proxy |
req.fingerPrint.hosting | boolean? | Whether the IP belongs to a hosting provider |
req.fingerPrint.device | string | Device type |
req.fingerPrint.deviceVendor | string? | Device manufacturer |
req.fingerPrint.deviceModel | string? | Device model name |
req.fingerPrint.browser | string? | Browser name |
req.fingerPrint.browserType | string? | Browser category |
req.fingerPrint.browserVersion | string? | Browser version |
req.fingerPrint.os | string? | Operating system |
req.fingerPrint.bot | boolean | Whether the client is a known bot |
req.fingerPrint.botAI | boolean | Whether the client is an AI crawler |
Error behavior
If fingerprinting fails (for example, malformed User-Agent or geolocation service unavailable), the middleware logs the error and calls next() without setting req.fingerPrint. Downstream middleware should handle a potentially undefined fingerprint gracefully.
See Fingerprinting for how fingerprint data is used in anomaly detection.
Error Handling
These middleware functions are registered at the end of the middleware stack by bootstrapApp(). They catch unmatched routes and unhandled errors.
notFoundHandler
Returns a standardized JSON 404 response for any request that doesn't match a registered route. Registered after all route handlers.
Response
| Status | Body |
|---|---|
404 | { error: "The page you are looking for doesn't exists" } |
@riavzon/auth. It is applied automatically by bootstrapApp().finalUnHandledErrors
The last-resort Express error handler. Catches any unhandled error that propagates through the middleware stack, logs it, and returns a normalized JSON error response.
Error handler signature
(err: any, req: Request, res: Response, next: NextFunction) => void
Status code logic
- Uses the existing
res.statusCodeif it is greater than415 - Defaults to
500otherwise
Response
| Status | Body |
|---|---|
| Varies | { error: string } |
@riavzon/auth. It is applied automatically by bootstrapApp().Logging
httpLogger
A pino-http middleware that logs every HTTP request and response. Writes structured JSON logs to auth-logs/http.log. Automatically redacts the Authorization header and session cookie values in log output.
Features
- Generates a unique
X-Request-Idheader for each request (or uses the existing one) - Redacts
req.headers.authorizationandreq.cookies.sessionin logs - Skips logging for static asset requests (
.css,.js,.png, etc.) and.well-known/paths - Logs IP address, User-Agent, full URL, and cookies as structured context
- Auto-selects log level based on response status:
infofor 2xx/3xx,warnfor 4xx,errorfor 5xx
@riavzon/auth. It is applied automatically by bootstrapApp().Internal Link Verification
The following middleware functions handle magic link verification for built-in flows (MFA and password reset). They are not exported individually but are used internally by the magic link route handlers.
linkMfaVerification
Verifies MFA magic links. On GET, previews the link status and allows a configurable number of views. On POST, consumes the link (single-use) and populates req.link for the downstream verifyMFA handler.
Validation pipeline
- Validates query parameters (
token,random,reason,visitor) against a Zod schema - Applies per-IP rate limiting with consecutive failure tracking
- Verifies the temporary JWT signature and expiry
- Checks visitor identity match between URL and token payload
- Verifies the random hash using
crypto.timingSafeEqual() - Enforces configurable GET/POST usage limits per JTI
linkPasswordVerification
Verifies password reset magic links. Follows the same validation pipeline as linkMfaVerification but uses separate rate limiter instances and threshold configuration (magic_links.thresholds.linkPasswordVerification).
Middleware Stack Order
When bootstrapApp() wires the Express app, middleware is applied in this exact order:
httpLogger
Structured request/response logging with automatic redaction.
Disable x-powered-by
Removes the default Express X-Powered-By header.
helmet
Security headers (CSP, X-Frame-Options, Referrer-Policy, etc.).
headers
No-cache headers for authentication responses.
validateIp
Rejects requests with invalid or missing IP addresses.
hmacAuth (conditional)
HMAC signature verification. Only applied when service.Hmac is configured.
apiVerificationRoute()
Mounts GET /api/public/verify before JSON parsing, cookie parsing, and the
bot-detector middleware.
express.json()
Global JSON body parser.
cookieParser()
Cookie parsing.
ApiResponse (Bot Detector)
Mounts the bot detection endpoint at /check.
Route handlers
authenticationRoutes -> tokenRotationRoutes -> magicLinks -> bffAccessRoute -> apiProtectedRoutes -> /operational/config
notFoundHandler
Catches unmatched routes.
finalUnHandledErrors
Last-resort error handler.