Catalyst-Plugin-OAuth2-ResourceServer

 view release on metacpan or  search on metacpan

t/catalyst-app.t  view on Meta::CPAN

use v5.36;
use Test::More;
use lib 't/lib';
use HTTP::Request::Common qw/GET/;
use JSON::MaybeXS qw/decode_json/;
use Crypt::JWT qw/encode_jwt/;

use Catalyst::Test 'TestApp';

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

sub token (%claims) {
    return encode_jwt(
        payload => {
            sub => 'user-1', aud => 'https://rs/mcp', iss => 'https://as',
            scope => 'example:read', exp => time + 60, %claims,
        },
        alg => 'HS256', key => $KEY,
    );
}
sub authget ( $path, $tok ) {
    my @h = defined $tok ? ( Authorization => "Bearer $tok" ) : ();
    return request( GET $path, @h );
}

# --- protected route: valid token passes, identity + scope available ---
{
    my $res = authget( '/secure', token() );
    is( $res->code, 200, 'valid bearer -> 200' );
    is( $res->content, 'ok:user-1:example:read', 'identity + scope visible to action' );
}

# --- the Bearer scheme name is case-insensitive (RFC 7235) ---
{
    my $res = request( GET '/secure', Authorization => 'bearer ' . token() );
    is( $res->code, 200, 'lowercase "bearer" accepted' );
}

# --- missing token -> 401 plain challenge (no error=) + resource_metadata ---
{
    my $res = authget( '/secure', undef );
    is( $res->code, 401, 'missing token -> 401' );
    my $wa = $res->header('WWW-Authenticate');
    like( $wa, qr/^Bearer/, 'Bearer challenge' );
    like( $wa, qr/resource_metadata="[^"]+"/, 'carries resource_metadata' );
    # TestApp configures resource_metadata_url explicitly; the override is used
    # verbatim rather than derived from the resource (which would have given
    # https://rs/.well-known/oauth-protected-resource/mcp).
    like( $wa, qr{resource_metadata="\Qhttps://rs/.well-known/oauth-protected-resource\E"},
        'configured resource_metadata_url wins over derivation' );
    unlike( $wa, qr/error=/, 'no error= for a missing token' );
    is( $res->header('Cache-Control'), 'no-store', 'no-store' );
}

# --- bad token -> 401 with error="invalid_token" ---
{
    my $res = authget( '/secure', 'garbage' );
    is( $res->code, 401, 'bad token -> 401' );
    like( $res->header('WWW-Authenticate'), qr/error="invalid_token"/, 'invalid_token' );
}

# --- resolver rejects (subject not active) -> 401 ---
{
    my $res = authget( '/secure', token( sub => 'inactive' ) );



( run in 2.024 seconds using v1.01-cache-2.11-cpan-f0ff5d10edf )