http_oauth
This library implements OAuth protected-resource support for normalized HTTP:
RFC 6750 Bearer Authorization and challenge parsing/generation
staged token authentication and scope authorization
resource-aware verifier and pluggable scope-checker protocols
RFC 9728 protected-resource metadata documents and public endpoint handling
RFC 7662 token introspection with Basic, Bearer, or custom-header client authentication
JWT access-token verification using the
jwtlibrarygeneric handler and
http_routerintegration
API documentation
Open the ../../apis/library_index.html#http-oauth link in a web browser.
Loading
To load the library, load the loader.lgt file:
| ?- logtalk_load(http_oauth(loader)).
Testing
To test this library, load the tester.lgt file:
| ?- logtalk_load(http_oauth(tester)).
Usage
Protected-resource and verifier contract
Every authentication call requires a canonical
protected_resource(Identifier) option. Use the externally visible
HTTPS URL that identifies the protected resource and use the same atom
consistently in metadata, token audiences, and protection options.
A verifier is any object implementing http_oauth_verifier_protocol.
Its verify/3 predicate receives the raw token and expected
protected-resource identifier. It must fail for an invalid, inactive,
expired, or wrong-audience token and return normalized information on
success:
:- object(my_token_verifier,
implements(http_oauth_verifier_protocol)).
verify(Token, ProtectedResource, oauth_token_info([
source(my_authorization_server),
scopes(Scopes),
audience_validation(exact(ProtectedResource)),
claims(Claims)
])) :-
% Verify Token and bind Scopes and Claims here.
...
:- end_object.
The library adds the resulting oauth_token_info/1 and
oauth_scopes/1 properties to the normalized request. The raw Bearer
token is deliberately not retained. Handlers can query these properties
using token_info/2, scopes/2, or the http_core::property/2
predicate.
Protecting a request with a static scope policy
Use protect_request/4 when the required scopes are known before
routing or dispatch. The default http_oauth_exact_scope_checker
requires every listed scope and compares scope atoms exactly:
http_oauth::protect_request(Request0, my_token_verifier, Action, [
protected_resource('https://api.example.com/orders'),
required_scopes([orders_read])
]),
( Action = continue(Request) ->
orders_handler::handle(Request, Response)
; Action = respond(Response)
).
Missing credentials produce a 401 response, malformed authorization data
a 400 response, invalid tokens a 401 response, and insufficient scopes a
403 response. Optional realm/1, resource_metadata/1,
headers/1, body/1, and properties/1 options customize
generated error responses. Supply a scope_checker/1 object
implementing http_oauth_scope_checker_protocol for policies other
than exact all-scopes matching.
Wrapping an HTTP handler
The parametric http_server_core_oauth_handler/3 object applies a
static policy around any object implementing http_handler_protocol:
http_server_core_oauth_handler(
my_token_verifier,
orders_handler,
[
protected_resource('https://api.example.com/orders'),
required_scopes([orders_read])
]
)::handle(Request, Response).
The wrapped handler receives the annotated request only after authentication and authorization succeed. Otherwise, the wrapper returns the generated OAuth response without calling the handler.
Applying route-specific policies
Objects importing http_router can also import the parametric
http_router_oauth/2 category. Base options define the protected
resource and shared policy; an oauth/1 route metadata property
overrides options for an individual route:
:- object(api,
implements(http_handler_protocol),
imports([
http_router,
http_router_oauth(my_token_verifier, [
protected_resource('https://api.example.com')
])
])).
route(list_orders, get, '/orders', list_orders).
route_metadata(list_orders, [oauth([required_scopes([orders_read])])]).
authorize_routed_request(Request, Action) :-
^^authorize_oauth_request(Request, Action).
Routes without oauth/1 metadata remain public. Route options
override base options by option name and arity.
Publishing protected-resource metadata
http_oauth_metadata::well_known_url/2 derives the RFC 9728 endpoint
from a canonical HTTPS resource identifier. For example,
https://api.example.com/orders maps to
https://api.example.com/.well-known/oauth-protected-resource/orders.
Use document/3-4 to build a JSON term or response/3-4 to build a
normalized HTTP response:
http_oauth_metadata::response(
'https://api.example.com/orders',
[
authorization_servers(['https://identity.example.com']),
scopes_supported([orders_read, orders_write]),
resource_name('Orders API'),
resource_documentation('https://api.example.com/docs/orders')
],
Response,
[required_members([authorization_servers])]
).
Identifiers and URL-valued descriptors must use HTTPS. Use
extension(Name, Value) for non-standard members; registered metadata
names cannot be supplied as extensions. The
http_server_core_oauth_endpoint_handler/6 wrapper combines public
metadata publication with a protected application handler. It serves
only GET at the exact derived well-known path, returns 405 for other
methods at that path, and protects every other request.
Verifying opaque tokens by introspection
Use the parametric http_oauth_introspection_verifier/2 when access
tokens are validated by an RFC 7662 endpoint:
Verifier = http_oauth_introspection_verifier(
'https://identity.example.com/oauth/introspect',
[authentication(basic(client_id, client_secret))]
),
http_oauth::protect_request(Request, Verifier, Action, [
protected_resource('https://api.example.com/orders'),
required_scopes([orders_read])
]).
The introspection client also supports authentication(bearer(Token))
and authentication(headers(Headers)), plus token_type_hint/1,
custom headers/1, and underlying http_options/1. Introspection
endpoints require HTTPS. The allow_insecure_localhost(true) option
exists only for local tests and accepts loopback HTTP endpoints.
By default, the verifier requires the introspection aud value to
contain the protected-resource identifier. Use
audience_validation(trust_active) only when the authenticated
introspection service guarantees that active=true is already
specific to the requested resource. The inbound Bearer token must never
be reused as client authentication or forwarded to downstream APIs.
Verifying JWT access tokens locally
Use http_oauth_jwt_verifier/2 for signed JWT access tokens. Its
first parameter is any symmetric key, public JWK, or JWK Set accepted by
jwt::verify/4; its second parameter is the corresponding JWT
verification option list:
Verifier = http_oauth_jwt_verifier(PublicJWKSet, [
allow_algorithms(['RS256']),
claim_policy([
claim(iss, expected('https://identity.example.com'))
])
]),
http_oauth::protect_request(Request, Verifier, Action, [
protected_resource('https://api.example.com/orders')
]).
The verifier requires an aud claim matching the protected resource.
A space-delimited scope claim is normalized to a list of atoms; if
it is absent, the granted scope list is empty. Signature,
registered-claim, and time validation behavior is configured using the
jwt library options.
Parsing headers and building OAuth responses
Use authorization/2 and challenge/2 with normalized HTTP
messages, or the parse_authorization/2,
generate_authorization/2, parse_challenge/2, and
generate_challenge/2 predicates with individual header values.
Bearer terms use the forms bearer_authorization(Token) and
bearer_challenge(Fields).
The unauthorized_response/3 and forbidden_response/3 predicates
construct new normalized responses. Their arity-four variants decorate
an existing response while preserving its HTTP version and any body,
headers, and properties not overridden by options. These helpers are
useful when an application needs to add OAuth challenges to its own
error representation.
MCP over HTTP
OAuth protection applies to MCP HTTP transports. It does not apply to MCP stdio transports, and an MCP session identifier never substitutes for per-request Bearer authentication. Every protected HTTP request must therefore carry credentials accepted by the configured verifier.
For MCP protected-resource metadata, use
required_members([authorization_servers]). The
http_server_core_oauth_endpoint_handler/6 wrapper is the simplest
way to serve that metadata publicly at the exact RFC 9728 well-known
path while protecting the MCP handler at all other paths. Use staged
authentication and authorization when the MCP method determines the
required scopes, so an insufficient-scope response can be returned
before starting a streamed MCP response.
Limitations
OAuth protection applies only to HTTP transports, not MCP stdio transports.
Bearer tokens are accepted only in the HTTP
Authorizationheader.The library does not implement authorization-server discovery, token acquisition, token refresh, or token revocation.
Token introspection is synchronous and does not provide built-in caching.
JWT verification is limited to the algorithms and capabilities supported by the
jwtlibrary and the selected backend.Protected-resource metadata and token introspection require HTTPS, except for the explicit introspection loopback override intended for local tests.
The default scope policy requires exact matching of all required scopes; alternative policies require a custom scope-checker object.