Relevant source files
The following files were used as context for generating this wiki page:
- src/middleware/bearer-auth/index.ts
- src/utils/jwt/jwt.ts
- src/middleware/secure-headers/secure-headers.ts
- src/adapter/aws-lambda/handler.ts
- src/middleware/basic-auth/index.ts
- src/middleware/jwt/jwt.ts
- src/middleware/jwk/jwk.ts
- src/utils/basic-auth.ts
- src/middleware/jwk/keys.test.json
- src/middleware/csrf/index.ts
- src/utils/jwt/jws.ts
- src/adapter/lambda-edge/handler.ts
- src/middleware/ip-restriction/index.ts
- src/utils/headers.ts
- src/utils/ipaddr.ts
- src/request.ts
- src/helper/proxy/index.ts
- src/utils/buffer.ts
- src/preset/quick.ts
- src/helper/streaming/text.ts
- src/types.ts
- src/middleware/etag/digest.ts
- src/utils/jwt/utf8.ts
- src/utils/encode.ts
- src/utils/cookie.ts
- src/utils/jwt/types.ts
- src/utils/accept.ts
- src/router/linear-router/router.ts
- src/adapter/netlify/handler.ts
- src/middleware/jwt/index.ts
Overview
Hono provides built-in middleware for HTTP Basic and Bearer Auth to secure API endpoints by validating credentials against static configurations or custom verification handlers. These middlewares inspect incoming request headers, parse authorization schemes, and dispatch standardized HTTP exceptions with 401 Unauthorized status codes and WWW-Authenticate challenge headers when verification fails.
Sources: src/middleware/basic-auth/index.ts:31-46, src/middleware/bearer-auth/index.ts:88-89
To prevent timing side-channel attacks during credential comparison, the system utilizes constant-time string and buffer comparison algorithms implemented within utility modules.
Sources: src/utils/buffer.ts:29-63
Basic Authentication Flow
Credential Matching Execution Walkthrough
The basicAuth middleware accepts an options object supporting either static username/password pairs or a custom verifyUser asynchronous callback function. The public configuration options also accept an optional realm, a hashFunction for credential normalization, an invalidUserMessage string, object, or message function, and an onAuthSuccess callback hook. If neither static credentials nor a verifyUser function are supplied, initialization throws an error.
Sources: src/middleware/basic-auth/index.ts:14-30, src/middleware/basic-auth/index.ts:80-91
| Option Key | Type | Default Value | Description |
|---|---|---|---|
username | string | — | Static username for authentication matching |
password | string | — | Static password for authentication matching |
verifyUser | Function | — | Custom verification handler taking (username, password, c) |
realm | string | "Secure Area" | Realm attribute populated in the WWW-Authenticate header |
hashFunction | Function | sha256 | Hash function utilized during secure credential comparison |
invalidUserMessage | string | object | Function | "Unauthorized" | Response body content returned on validation failure |
onAuthSuccess | Function | — | Callback function invoked upon successful authentication |
Sources: src/middleware/basic-auth/index.ts:36-43, src/middleware/basic-auth/index.ts:93-99
Note
When static username and password properties are specified in the configuration object alongside rest parameters (...users), they are prepended to the internal user verification array via users.unshift().
Sources: src/middleware/basic-auth/index.ts:81-83, src/middleware/basic-auth/index.ts:101-103
When an incoming request is intercepted, the handler extracts and parses authorization credentials from the request headers using the basic auth utility function, checking the provided username and password against configured accounts or the custom verification handler via the execution chain: basicAuth → timingSafeEqual → timingSafeEqualString → constantTimeEqualString.
Sources: src/middleware/basic-auth/index.ts:105-131, src/utils/buffer.ts:29-96
basicAuthinvokesauth(ctx.req.raw)to extract the decoded request username and password, then iterates through configured users to invoketimingSafeEqual(user.username, requestUser.username, options.hashFunction)andtimingSafeEqual(user.password, requestUser.password, options.hashFunction)viaPromise.all. Sources: src/middleware/basic-auth/index.ts:106-121timingSafeEqualchecks argument types and routes string inputs totimingSafeEqualString. Sources: src/utils/buffer.ts:76-83timingSafeEqualStringresolves the active hash function (defaulting tosha256), computes hashed byte representations of both strings in parallel viaPromise.all, and passes the resulting digests toconstantTimeEqualString. Sources: src/utils/buffer.ts:44-63constantTimeEqualStringevaluates character codes across the maximum length utilizing bitwise operations to accumulate differences without early exits. Sources: src/utils/buffer.ts:29-40
Sources: src/middleware/basic-auth/index.ts:118-121, src/utils/buffer.ts:76-83, src/utils/buffer.ts:44-63, src/utils/buffer.ts:29-40
Parsing Authorization Credentials
Credential Extraction and Decoding
The Utilities and Encoding page documents how low-level helpers manage character encoding src/utils/encode.ts:1-4. Incoming HTTP requests are processed by the auth helper, which reads the Authorization header and matches it against CREDENTIALS_REGEXP to isolate the Base64-encoded payload portion src/utils/basic-auth.ts:3-10. If the header is missing or fails the regular expression pattern, the function immediately returns undefined src/utils/basic-auth.ts:10-13.
Sources: src/utils/basic-auth.ts:3-13, src/utils/encode.ts:1-4
Execution Walkthrough and Sequence
basicAuthinvokesauth(ctx.req.raw)to retrieve credentials from the raw request object src/middleware/basic-auth/index.ts:106. Sources: src/middleware/basic-auth/index.ts:106authexecutesCREDENTIALS_REGEXP.execon the header string, captures group1, and passes it todecodeBase64[[src/utils/basic-auth.ts:10], [src/utils/basic-auth.ts:18]]. Sources: [src/utils/basic-auth.ts:10](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/utils/basic-auth.ts#L10], [src/utils/basic-auth.ts:18)decodeBase64invokesatob(str)and constructs aUint8Arrayusing byte character codes src/utils/encode.ts:25-33. Sources: src/utils/encode.ts:25-33
Sources: src/middleware/basic-auth/index.ts:106, src/utils/basic-auth.ts:10-18, src/utils/encode.ts:25-33
Regular Expressions and Sanitization
| Constant Name | Regular Expression Pattern | Purpose |
|---|---|---|
CREDENTIALS_REGEXP | /^ *(?:[Bb][Aa][Ss][Ii][Cc]) +([A-Za-z0-9._~+/-]+=*) *$/ | Matches and extracts the Base64 token following the Basic scheme prefix. |
USER_PASS_REGEXP | /^([^:]*):(.*)$/ | Splits the decoded string into username and password components across the first colon. |
Sources: src/utils/basic-auth.ts:3-4
Warning
If an invalid string is passed to atob(), a DOMException is thrown; the auth function catches this exception inside a try...catch block and safely returns undefined rather than crashing the request lifecycle.
Sources: src/utils/basic-auth.ts:15-20
Bearer Authentication Enforcement
Bearer Options and Header Processing
The bearerAuth middleware requires options containing either a token (string or array of strings) or a verifyToken function. Optional configurations include realm, prefix, headerName, hashFunction, and customized error responses via noAuthenticationHeader, invalidAuthenticationHeader, and invalidToken. If neither token nor verifyToken is provided, the middleware throws an initialization error.
Sources: src/middleware/bearer-auth/index.ts:22-50, src/middleware/bearer-auth/index.ts:107-109
| Option Name | Type | Default Value | Purpose |
|---|---|---|---|
token | string | string[] | undefined | The static token string or array of acceptable tokens. |
verifyToken | (token: string, c: Context) => boolean | Promise<boolean> | undefined | Custom token verification callback function. |
realm | string | '' | Authentication realm string included in challenge headers. |
prefix | string | 'Bearer' | Expected scheme prefix preceding the token. |
headerName | string | 'Authorization' | Custom header name to inspect instead of Authorization. |
hashFunction | Function | sha256 | Custom hashing function used during token comparison. |
Sources: src/middleware/bearer-auth/index.ts:22-50, src/middleware/bearer-auth/index.ts:110-115, src/utils/buffer.ts:50
Note
When prefix is set to an empty string '', scheme prefix validation is bypassed entirely, and the entire header value is treated directly as the token string.
Sources: src/middleware/bearer-auth/index.ts:172-174
Execution Walkthrough and Sequence
bearerAuthreceives the request and inspects the specified HTTP header usingc.req.header(options.headerName || HEADER). Sources: src/middleware/bearer-auth/index.ts:156-157- If the header is missing, it dispatches an unauthorized exception via
throwHTTPExceptionwith a 401 status. Sources: src/middleware/bearer-auth/index.ts:158-168 - If present, it validates the scheme prefix case-insensitively, trims leading whitespace, and tests the extracted token against
tokenRegexp(^[A-Za-z0-9._~+/-]+=*$). Sources: src/middleware/bearer-auth/index.ts:12-19, src/middleware/bearer-auth/index.ts:175-182 - If token validation fails, a 400 Bad Request exception is thrown. Sources: src/middleware/bearer-auth/index.ts:182-192
- Otherwise, credential matching delegates to
verifyTokenor callstimingSafeEqual. Sources: src/middleware/bearer-auth/index.ts:195-206 timingSafeEqualinvokestimingSafeEqualString. Sources: src/utils/buffer.ts:76-83timingSafeEqualStringinvokesconstantTimeEqualStringfor byte and hash verification. Sources: src/utils/buffer.ts:44-63
Sources: src/middleware/bearer-auth/index.ts:198, src/utils/buffer.ts:76-83, src/utils/buffer.ts:44-63, src/utils/buffer.ts:29-40
Timing-Attack Mitigation
Constant-Time Comparison Operations
Hono implements timing-attack mitigation utilities in src/utils/buffer.ts to prevent attackers from inferring secret tokens or passwords via response latency side channels. The lowest-level primitive, constantTimeEqualString, processes two strings character by character without short-circuiting on mismatch:
const constantTimeEqualString = (a: string, b: string): boolean => {
const aLen = a.length
const bLen = b.length
const maxLen = Math.max(aLen, bLen)
let out = aLen ^ bLen
for (let i = 0; i < maxLen; i++) {
const aChar = i < aLen ? a.charCodeAt(i) : 0
const bChar = i < bLen ? b.charCodeAt(i) : 0
out |= aChar ^ bChar
}
return out === 0
}Sources: src/utils/buffer.ts:29-40
Caution
Length differences between strings are accumulated into the XOR accumulator via aLen ^ bLen, preventing length-extension leaks from exiting early when string lengths diverge.
Sources: src/utils/buffer.ts:33
ArrayBuffer and String Equivalence
Higher-level comparison functions handle binary ArrayBuffer instances as well as hashed and unhashed strings. The equal function compares two ArrayBuffer instances using a DataView iteration loop:
export const equal = (a: ArrayBuffer, b: ArrayBuffer): boolean => {
if (a === b) {
return true
}
if (a.byteLength !== b.byteLength) {
return false
}
const va = new DataView(a)
const vb = new DataView(b)
let i = va.byteLength
while (i--) {
if (va.getUint8(i) !== vb.getUint8(i)) {
return false
}
}
return true
}Sources: src/utils/buffer.ts:8-27
The timingSafeEqualString function wraps string hashing and comparison. By default, if no custom hashFunction is provided, it falls back to sha256:
const timingSafeEqualString = async (
a: string,
b: string,
hashFunction?: StringHashFunction
): Promise<boolean> => {
if (!hashFunction) {
hashFunction = sha256
}
const [sa, sb] = await Promise.all([hashFunction(a), hashFunction(b)])
if (sa == null || sb == null || typeof sa !== 'string' || typeof sb !== 'string') {
return false
}
const hashEqual = constantTimeEqualString(sa, sb)
const originalEqual = constantTimeEqualString(a, b)
return hashEqual && originalEqual
}Sources: src/utils/buffer.ts:44-63
Note
timingSafeEqualString evaluates both constantTimeEqualString(sa, sb) on the hashed outputs and constantTimeEqualString(a, b) on the raw inputs concurrently, returning true only if both comparisons succeed.
Sources: src/utils/buffer.ts:53-62
The exported timingSafeEqual entry point normalizes inputs and routes execution depending on whether parameters are strings or generic objects/booleans:
export const timingSafeEqual: TimingSafeEqual = async (
a,
b,
hashFunction?: Function
): Promise<boolean> => {
if (typeof a === 'string' && typeof b === 'string') {
return timingSafeEqualString(a, b, hashFunction as StringHashFunction)
}
if (!hashFunction) {
hashFunction = sha256
}
const [sa, sb] = await Promise.all([hashFunction(a), hashFunction(b)])
if (!sa || !sb || typeof sa !== 'string' || typeof sb !== 'string') {
return false
}
return timingSafeEqualString(sa, sb)
}Sources: src/utils/buffer.ts:76-96
Authentication Challenges and Error Dispatch
HTTPException Instantiation and Response Generation
When authentication fails across Hono's authentication middlewares, error handling is dispatched via HTTPException combined with custom Response instances. The basicAuth middleware evaluates the parsed user credentials against configured options or user lists. Upon failure, it constructs a 401 Unauthorized status response containing the WWW-Authenticate header populated with the escaped realm name. If invalidUserMessage is configured as a function or an object, it is dynamically serialized into a text or JSON payload before throwing the HTTPException:
// Invalid user.
const status = 401
const headers = {
'WWW-Authenticate': 'Basic realm="' + options.realm?.replace(/"/g, '\\"') + '"',
}
const responseMessage =
typeof options.invalidUserMessage === 'function'
? await options.invalidUserMessage(ctx)
: options.invalidUserMessage
const res =
typeof responseMessage === 'string'
? new Response(responseMessage, { status, headers })
: new Response(JSON.stringify(responseMessage), {
status,
headers: {
...headers,
'content-type': 'application/json',
},
})
throw new HTTPException(status, { res })
}Sources: src/middleware/basic-auth/index.ts:132-152
Similarly, the bearerAuth middleware defines a private throwHTTPException helper function to encapsulate status codes, challenge headers, and message body construction. Depending on the specific failure branch—such as missing headers, malformed requests, or invalid tokens—it issues status codes 401, 400, or 401 respectively.
Sources: src/middleware/bearer-auth/index.ts:122-154
Warning
Realms containing double quotes are automatically escaped via options.realm?.replace(/"/g, '\\"') prior to header injection to prevent header injection vulnerabilities.
Sources: src/middleware/basic-auth/index.ts:135, src/middleware/bearer-auth/index.ts:117
WWW-Authenticate Challenge Construction
The construction of challenge headers differs between schemes based on whether options specify plain strings, objects, or callback functions. The bearerAuth middleware processes wwwAuthenticateHeader parameters by checking their type. If an object is supplied, its entries are mapped into comma-separated key-value pairs appended to the challenge prefix:
const wwwAuthenticateHeaderValue: string | object =
typeof wwwAuthenticateHeader === 'function'
? await wwwAuthenticateHeader(c)
: wwwAuthenticateHeader
const headers = {
'WWW-Authenticate':
typeof wwwAuthenticateHeaderValue === 'string'
? wwwAuthenticateHeaderValue
: `${wwwAuthenticatePrefix}${Object.entries(wwwAuthenticateHeaderValue)
.map(([key, value]) => `${key}="${value}"`)
.join(',')}`,
}Sources: src/middleware/bearer-auth/index.ts:128-140
For JSON Web Token workflows in jwt and jwk middlewares, failed verifications or missing tokens trigger unauthorizedResponse, which formats challenge headers including the current request URL, error codes, and detailed descriptions:
function unauthorizedResponse(opts: {
ctx: Context
error: string
errDescription: string
statusText?: string
}) {
return new Response('Unauthorized', {
status: 401,
statusText: opts.statusText,
headers: {
'WWW-Authenticate': `Bearer realm="${opts.ctx.req.url}",error="${opts.error}",error_description="${opts.errDescription}"`,
},
})
}Sources: src/middleware/jwt/jwt.ts:160-173, src/middleware/jwk/jwk.ts:170-182
| Middleware | Default Status Code | Default WWW-Authenticate Challenge | Message Payload Handling |
|---|---|---|---|
basicAuth | 401 | Basic realm="..." | Text or JSON via invalidUserMessage |
bearerAuth (Missing) | 401 | Bearer realm="..." | Text or JSON via noAuthenticationHeader.message |
bearerAuth (Invalid Request) | 400 | Bearer error="invalid_request" | Text or JSON via invalidAuthenticationHeader.message |
bearerAuth (Invalid Token) | 401 | Bearer error="invalid_token" | Text or JSON via invalidToken.message |
jwt / jwk | 401 | Bearer realm="...",error="...",error_description="..." | Static string 'Unauthorized' |
Sources: src/middleware/basic-auth/index.ts:133-151, src/middleware/bearer-auth/index.ts:160-215, src/middleware/jwt/jwt.ts:160-173, src/middleware/jwk/jwk.ts:170-182
Related
On This Page
OverviewBasic Authentication FlowCredential Matching Execution WalkthroughParsing Authorization CredentialsCredential Extraction and DecodingExecution Walkthrough and SequenceRegular Expressions and SanitizationBearer Authentication EnforcementBearer Options and Header ProcessingExecution Walkthrough and SequenceTiming-Attack MitigationConstant-Time Comparison OperationsArrayBuffer and String EquivalenceAuthentication Challenges and Error DispatchHTTPException Instantiation and Response GenerationWWW-Authenticate Challenge ConstructionRelated