Catalyst-Plugin-OAuth2-AuthorizationServer

 view release on metacpan or  search on metacpan

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

use v5.36;
use Test::More;
use Test::Fatal;
use lib 't/lib';
use StubStore;
use Digest::SHA qw/sha256/;
use MIME::Base64 qw/encode_base64url/;
use Crypt::JWT qw/decode_jwt/;

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

my $key = 'k' x 32;

# RFC 7636 4.1 code verifiers: 43-128 chars of [A-Za-z0-9._~-]
my $VERIFIER  = 'pkce-verifier-' . ( '0' x 29 );
my $VERIFIER2 = 'other-verifier-' . ( '1' x 28 );
my $CHALLENGE = encode_base64url( sha256($VERIFIER) );

sub fresh_engine { return $class->new(
    store => StubStore->new, signing_key => $key,
    issuer => 'https://as', resource => 'https://rs/mcp',
) }

# helper: drive authorize -> issue_code with a real PKCE pair
sub mint_code ( $eng, $verifier ) {
    my $challenge = encode_base64url( sha256($verifier) );
    $eng->store->create_client({
        client_id => 'c1', redirect_uris => ['https://app/cb'] });
    my $rid = $eng->validate_authorize({
        client_id => 'c1', redirect_uri => 'https://app/cb',
        response_type => 'code', code_challenge => $challenge,
        code_challenge_method => 'S256', scope => 'example:read',
        resource => 'https://rs/mcp',
    })->{request_id};
    return $eng->issue_code( 'user-9', $rid )->{code};
}

# happy path: code -> Bearer token (claims) + refresh token
{
    my $eng  = fresh_engine();
    my $code = mint_code( $eng, $VERIFIER );
    my $tok  = $eng->exchange_authorization_code({
        grant_type   => 'authorization_code',
        code         => $code,
        redirect_uri => 'https://app/cb',
        code_verifier => $VERIFIER,
    });
    is( $tok->{token_type}, 'Bearer', 'Bearer token_type' );
    is( $tok->{expires_in}, 900,      'expires_in = access_ttl' );
    is( $tok->{scope},      'example:read', 'scope echoed' );
    ok( length $tok->{refresh_token}, 'refresh token issued' );

    my $claims = decode_jwt( token => $tok->{access_token}, key => $key );
    is( $claims->{sub},   'user-9',          'sub claim' );
    is( $claims->{aud},   'https://rs/mcp',  'aud = resource' );
    is( $claims->{scope}, 'example:read',      'scope claim' );
}

# PKCE mismatch -> invalid_grant
{
    my $eng  = fresh_engine();
    my $code = mint_code( $eng, $VERIFIER );



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