Catalyst-Plugin-OAuth2-ResourceServer

 view release on metacpan or  search on metacpan

lib/Catalyst/Plugin/OAuth2/ResourceServer/Server.pm  view on Meta::CPAN

package Catalyst::Plugin::OAuth2::ResourceServer::Server;
use v5.36;
use Moo;
use Carp ();
use Try::Tiny;
use Crypt::JWT qw/decode_jwt/;
use Catalyst::Plugin::OAuth2::ResourceServer::Error;

our $VERSION = '0.003';

has signing_key => ( is => 'ro', required => 1 );
has resource    => ( is => 'ro', required => 1 );
has issuer      => ( is => 'ro', required => 1 );
has jwt_alg     => ( is => 'ro', default => 'HS256' );
has leeway      => ( is => 'ro', default => 0 );

use namespace::clean;
use MooX::StrictConstructor;

sub BUILD ( $self, $args ) {
    Carp::croak 'resource must be a non-empty scalar or arrayref'
        unless @{ $self->_resource_list };
    state %ALLOWED_ALG = map { $_ => 1 } qw/HS256 HS384 HS512/;
    Carp::croak 'jwt_alg must be one of HS256, HS384, HS512'
        unless $ALLOWED_ALG{ $self->jwt_alg };
    state %MIN_KEY_BYTES = ( HS256 => 32, HS384 => 48, HS512 => 64 );
    Carp::croak sprintf(
        'signing_key must be at least %d bytes for %s',
        $MIN_KEY_BYTES{ $self->jwt_alg }, $self->jwt_alg )
        if length( $self->signing_key ) < $MIN_KEY_BYTES{ $self->jwt_alg };
}

# resource may be a scalar or arrayref; normalise to a list.
sub _resource_list ( $self ) {
    my $r = $self->resource;
    return ref $r eq 'ARRAY' ? [ @$r ] : defined $r && length $r ? [$r] : [];
}

sub _invalid ( $self, $desc ) {
    Catalyst::Plugin::OAuth2::ResourceServer::Error->throw(
        error             => 'invalid_token',
        error_description => $desc,
        http_status       => 401,
    );
}

# Verify a bearer JWT: signature + alg allowlist + exp/nbf/iat (with leeway) +
# iss, then an explicit aud-membership check against our own resource(s).
# Returns the claims, or throws a 401 invalid_token (reason never leaked).
#
# Crypt::JWT verify_* semantics (see its POD): 1 = claim REQUIRED and valid,
# undef = "validate only if present". exp is required (a token with no expiry
# is never acceptable here). nbf and iat are optional per RFC 7519 4.1.5/4.1.6
# and the companion AuthorizationServer mints no nbf at all, so both are
# validate-if-present: requiring them would reject legitimate tokens. Note
# verify_iat is asymmetric in Crypt::JWT -- omitting the key entirely disables
# the iat check completely, so it must be passed explicitly as undef.
sub verify_token ( $self, $jwt ) {
    my $claims = try {
        decode_jwt(
            token        => $jwt,
            key          => $self->signing_key,
            accepted_alg => [ $self->jwt_alg ],
            verify_exp   => 1,
            verify_nbf   => undef,



( run in 0.712 second using v1.01-cache-2.11-cpan-5fbc6bb55f2 )