Skip to content

Bearer Auth

Deserve ships Basic Auth but no Bearer middleware, and the split between the two is the whole point.

Why It Is Not Built In

Bearer is only an envelope. The Authorization: Bearer <token> header carries a token, and what counts as a valid token changes with the ecosystem. One service verifies a JWT signature, another fetches a rotating public key from a JWKS endpoint, another calls an introspection API for an opaque token, and the signing algorithm can be RS256, ES256, or HS256.

Baking one of those choices in would lock every project into a single scheme. When the spec moves or a team rotates keys a different way, that built-in answer turns into a cage rather than a help. So the decision is to leave the verification open and let the developer bring the scheme the use case needs.

Why Basic Auth Ships But Bearer Does Not

Basic Auth is one fixed scheme. The header carries a base64 username and password, the check is a constant-time compare against a list, and there is nothing to choose. That stability is why it fits inside the framework.

Bearer is the opposite. The token format, the signature, and the trust source all vary, so there is no single check to ship. Both schemes read the same Authorization header, but only one has a single correct answer.

The Pieces Already in Place

A token guard is a small composition over parts that already ship:

A Bearer Guard

This middleware pulls the token out of the header, verifies it, and stores the result for later handlers. The verifyToken placeholder stands in for the scheme of choice, a JWT check, a JWKS lookup, or an introspection call.

typescript
router
.
use
(async (
ctx
,
next
) => {
const
header
=
ctx
.
header
('authorization')
const
spaceIndex
=
header
?
header
.
indexOf
(' ') : -1
const
scheme
=
spaceIndex
> 0 ?
header
!.
slice
(0,
spaceIndex
) : ''
// Reject anything that is not Bearer if (
scheme
.
toLowerCase
() !== 'bearer') {
return await
ctx
.
handleError
(401, new
Error
('Missing Bearer token'))
} // Verify with the scheme of choice const
token
=
header
!.
slice
(
spaceIndex
+ 1).
trim
()
const
claims
= await
verifyToken
(
token
)
if (!
claims
) {
return await
ctx
.
handleError
(401, new
Error
('Invalid token'))
} // Hand the identity to handlers
ctx
.
state
.
userId
=
claims
.
userId
return await
next
()
}) await
router
.
serve
(8000)

The handler then reads the identity straight from state, with no token parsing of its own.

typescript
export function 
GET
(
ctx
:
Context
): Response {
// Read what the guard stored const
userId
=
ctx
.
state
.
userId
return
ctx
.
send
.
json
({
userId
})
}

Routing Failures Through One Handler

The guard above returns the 401 from inside the middleware. To send every auth failure through one place, wrap the middleware with WrapMware and throw on rejection, then shape the reply with router.catch().

typescript
// Throws reach router.catch when wrapped
const 
bearer
=
WrapMware
('Bearer', async (
ctx
:
Context
,
next
) => {
const
header
=
ctx
.
header
('authorization')
if (!
header
?.
toLowerCase
().
startsWith
('bearer ')) {
throw new
Error
('Missing Bearer token')
} const
claims
= await
verifyToken
(
header
.
slice
(7).
trim
())
if (!
claims
) {
throw new
Error
('Invalid token')
}
ctx
.
state
.
userId
=
claims
.
userId
return await
next
()
}) // Apply the guard and error shape
router
.
use
(
bearer
)
router
.
catch
((
ctx
,
err
) =>
ctx
.
send
.
json
({
error
:
err
.
error
?.
message
}, {
status
: 401 }))

This is the same wrapping pattern Basic Auth uses internally, now carrying a token check instead of a credential compare.

Guarding Only Some Routes

A token guard often belongs on an API prefix while public pages stay open. Path-specific middleware scopes the check to one prefix, the same form shown in global middleware.

typescript
// Guard only the /api routes
router
.
use
('/api', async (
ctx
,
next
) => {
const
header
=
ctx
.
header
('authorization')
const
claims
=
header
?.
toLowerCase
().
startsWith
('bearer ')
? await
verifyToken
(
header
.
slice
(7).
trim
())
: null if (!
claims
) {
return await
ctx
.
handleError
(401, new
Error
('Invalid token'))
}
ctx
.
state
.
userId
=
claims
.
userId
return await
next
()
})

For a server-side session signed by the framework instead of a token, see the session middleware.

Released under the MIT License.