NAME
Punk::OAuth2::Checker - resource server bearer token checkers
SYNOPSIS
use Punk::OAuth2::Checker;
my $jwt = Punk::OAuth2::Checker->jwt(
issuer => 'https://idp.example.com',
jwks_url => 'https://idp.example.com/oauth/jwks.json',
audience => 'https://api.example.com',
algs => ['RS256', 'ES256'],
);
# the OpenAPI security map (checker is the house contract)
$scope->api('openapi.json' => { security => { oauth => $jwt } });
# a plain route guard emitting RFC 6750 WWW-Authenticate
under '/api' => Punk::OAuth2::Checker->guard($jwt,
scopes => ['read:books']);
DESCRIPTION
Validates incoming Bearer access tokens, implemented in XS. The two factories return a checker coderef matching the OpenAPI security-map contract - $checker->($credential, $c, $operationId, $scopes) returning the claims hashref or false - and guard returns a route guard. This module is the loader and the manual; the methods and the checker/guard closures are XS.
jwt (%opts)
my $jwt = Punk::OAuth2::Checker->jwt(
issuer => 'https://idp.example.com',
jwks_url => 'https://idp.example.com/oauth/jwks.json',
audience => 'https://api.example.com',
algs => ['RS256', 'ES256'], # allowlist
leeway => 60,
);
# ...or a static key instead of jwks_url:
my $jwt = Punk::OAuth2::Checker->jwt(
issuer => ..., audience => ..., key => $pem_or_secret);
Local JWT validation: jwks_url (keys fetched and cached via Punk::OAuth2::JWKS) or a static key (a Crypt::JWS::Key, a PEM string, or an HS shared secret). Checks the algorithm allowlist before any crypto, then the signature, then issuer (exact), audience (the aud claim must contain it), exp/nbf with leeway, and the required scopes against the scope string or scp array. Returns a checker coderef.
introspect (%opts)
my $intro = Punk::OAuth2::Checker->introspect(
url => 'https://idp.example.com/oauth/introspect',
client_id => 'my-api',
client_secret => 's3cr3t',
cache_ttl => 60,
);
RFC 7662 introspection: POSTs the token to url with client credentials, requires active: true, and caches the result for cache_ttl seconds keyed by the token's SHA-256 (never the raw token). Returns a checker coderef.
guard ($checker, %opts)
under '/api' => Punk::OAuth2::Checker->guard($jwt,
scopes => ['read:books'], realm => 'books-api');
# in a handler behind the guard:
get '/api/books' => sub {
my ($c) = @_;
my $sub = $c->stash->{auth}{oauth}{sub};
...
};
Wraps a checker as a plain route guard. It extracts the Bearer credential itself and, on denial, returns a 401 (invalid_token) or 403 (insufficient_scope) with an RFC 6750 WWW-Authenticate header. scopes lists the required scopes; scheme names the stash slot (default oauth); realm sets the challenge realm.
On success the claims land in $c->stash->{auth}{$scheme}; on failure the reason is in $c->stash->{'punk.oauth2.error'}.
AUTHOR
LNATION, <email at lnation.org>
LICENSE AND COPYRIGHT
This software is Copyright (c) 2026 by LNATION <email@lnation.org>.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)