Catalyst-Plugin-OAuth2-ResourceServer

 view release on metacpan or  search on metacpan

t/server-verify.t  view on Meta::CPAN

use v5.36;
use Test::More;
use Test::Fatal;
use Crypt::JWT qw/encode_jwt/;

my $class = 'Catalyst::Plugin::OAuth2::ResourceServer::Server';
require_ok($class);

my $KEY = 'resource-server-secret-key-0123456789';

sub engine (%over) {
    return $class->new(
        signing_key => $KEY,
        resource    => 'https://rs.example/mcp',
        issuer      => 'https://as.example',
        %over,
    );
}

# a helper to mint a token the way the AS would
sub token (%claims) {
    return encode_jwt(
        payload => {
            sub   => 'user-1',
            aud   => 'https://rs.example/mcp',
            iss   => 'https://as.example',
            scope => 'example:read',
            exp   => time + 60,
            %claims,
        },
        alg => 'HS256',
        key => $KEY,
    );
}

# BUILD validation
like( exception { engine( resource => [] ) }, qr/resource/, 'empty resource rejected' );
like( exception { engine( jwt_alg => 'RS256' ) }, qr/jwt_alg/, 'non-HS alg rejected' );
like(
    exception {
        $class->new( signing_key => 'short', resource => 'https://rs.example/mcp',
            issuer => 'https://as.example' );
    },
    qr/signing_key must be at least/,
    'short signing_key rejected'
);

# happy path -> claims
{
    my $claims = engine->verify_token( token() );
    is( $claims->{sub},   'user-1',     'sub claim' );
    is( $claims->{scope}, 'example:read', 'scope claim' );
}

# array aud containing our resource is accepted
{
    my $jwt = token( aud => [ 'https://other', 'https://rs.example/mcp' ] );
    ok( engine->verify_token($jwt), 'array aud incl. our resource accepted' );
}

my %fail = (
    expired      => token( exp => time - 10 ),
    wrong_aud    => token( aud => 'https://other/api' ),



( run in 1.079 second using v1.01-cache-2.11-cpan-54e63673c56 )