Authly::Handler::Bearer
Inherits HTTP::Handler < Reference < Object
Protects the routes behind it with OAuth 2.0 bearer tokens (RFC 6750). It is a plain
HTTP::Handler: it needs no web framework, so it goes into any handler chain.
It protects every request unless told otherwise. List what is public with except_exact
(just that path) or except (that path and everything below it, case-sensitively:
except: ["/health"] also exempts /health/anything). Prefer except_exact for a health check:
verifier = Authly::StrategyTokenVerifier.new(audience: "https://api.example.com")
bearer = Authly::Handler::Bearer.new(verifier, realm: "api", except_exact: ["/health"])
HTTP::Server.new([bearer, Authly::Handler::RequireScope.all("read:users"), app])
prefixes: turns that around into an allowlist (only those paths are protected, every other
path is open), which fails open for any route you forget to list: prefer except. See PathFilter
for how paths are compared.
Set an audience on the verifier (and config.access_token_audience on the issuer): without
it a token minted for one API is accepted by every other API that shares the key.
A verified token's claims are available downstream as context.authly_claims. Anything else
ends the request here: no credentials 401, a malformed header 400 invalid_request, a bad,
expired, revoked or wrongly addressed token 401 invalid_token, and a token store that cannot
be reached 503 with Retry-After (not 401, which would make clients re-authenticate for
nothing). The token is read from the Authorization header only, unless allow_query_token is
set (RFC 6750 section 2.3 discourages ?access_token=: URLs end up in logs, referrers and
browser history; such responses get Cache-Control: private and Referrer-Policy: no-referrer),
and it is never logged.
Put a CORS handler before this one, or browsers cannot read the 401. A CORS preflight
(OPTIONS with Access-Control-Request-Method) carries no credentials; pass_preflight: true
lets it through to the next handler instead of answering 401. That is decided by headers anyone
can forge: whatever answers OPTIONS behind it is public and must have no side effects. Give
RequireScope the same flag.