Catalyst-Plugin-OAuth2-ResourceServer
view release on metacpan or search on metacpan
lib/Catalyst/Plugin/OAuth2/ResourceServer.pm view on Meta::CPAN
# Write a WWW-Authenticate: Bearer challenge. $err is an optional ...::Error.
sub oauth_challenge ( $c, $err = undef ) {
my $status = ( $err && $err->http_status ) ? $err->http_status : 401;
my @p;
push @p, sprintf( 'error="%s"', _oauth_rs_quote_hval( $err->error ) )
if $err && defined $err->error;
push @p,
sprintf( 'error_description="%s"',
_oauth_rs_quote_hval( $err->error_description ) )
if $err && defined $err->error_description;
push @p, sprintf( 'scope="%s"', _oauth_rs_quote_hval( $err->scope ) )
if $err && defined $err->scope;
if ( $status == 401 ) {
my $cfg = $c->_oauth_rs_config;
my $base = ref $cfg->{resource} eq 'ARRAY'
? ( $cfg->{resource}[0] // '' )
: ( $cfg->{resource} // '' );
# An explicit override always wins; otherwise derive it, which only
# works for an http(s) resource id.
my $url = $cfg->{resource_metadata_url} // _oauth_rs_metadata_url($base);
if ( defined $url && length $url ) {
push @p,
sprintf( 'resource_metadata="%s"', _oauth_rs_quote_hval($url) );
}
else {
# Warn once per process: omitting the parameter is correct, but the
# deployer probably wants to set resource_metadata_url explicitly.
state $warned = 0;
$c->log->warn( 'OAuth2 RS: cannot derive resource_metadata from a '
. 'non-http(s) resource identifier; set resource_metadata_url '
. 'explicitly to advertise it in 401 challenges' )
if !$warned++ && $c->can('log');
}
}
my $hdr = 'Bearer' . ( @p ? ' ' . join( ', ', @p ) : '' );
my $res = $c->response;
$res->status($status);
$res->header( 'WWW-Authenticate' => $hdr );
$res->header( 'Cache-Control' => 'no-store' );
$res->content_type('application/json');
$res->body(
$JSON->encode( { ( $err && defined $err->error ) ? ( error => $err->error ) : () } ) );
return;
}
sub oauth_protect ( $c ) {
my $auth = $c->request->header('Authorization') // '';
# RFC 7235 auth-scheme names are case-insensitive.
my ($jwt) = $auth =~ /\A Bearer \s+ (\S+) \z/xi;
unless ( defined $jwt && length $jwt ) {
# RFC 6750 section 3: a malformed *Bearer* attempt is invalid_request; an
# absent header or a different/unsupported scheme gets a bare 401.
if ( $auth =~ /\A \s* Bearer \b/xi ) {
$c->oauth_challenge(
Catalyst::Plugin::OAuth2::ResourceServer::Error->new(
error => 'invalid_request', http_status => 400 ) );
}
else {
$c->oauth_challenge; # no credentials -> plain 401
}
return 0;
}
# Token verification: a structured Error is a token failure (-> its 401);
# anything else (engine misconfig, internal bug) is a 500, never invalid_token.
my $verified = 1;
my $claims = try {
$c->_oauth_rs_engine->verify_token($jwt);
}
catch {
if ( blessed $_
&& $_->isa('Catalyst::Plugin::OAuth2::ResourceServer::Error') )
{
$c->oauth_challenge($_);
}
else {
# Deliberately a fixed string: this path is credential-adjacent and
# the exception could carry token material.
$c->log->error('OAuth2 RS: token verification failed')
if $c->can('log');
$c->_oauth_server_error;
}
$verified = 0;
undef;
};
return 0 unless $verified;
# Subject resolution is an app hook; a die in it is an internal error (500),
# NOT an invalid_token. An undef return means the subject is rejected (401).
my $identity;
if ( $c->can('oauth_resolve_subject') ) {
my $resolved = 1;
$identity = try {
$c->oauth_resolve_subject($claims);
}
catch {
# Fixed string for the same reason as the verify_token catch above:
# the hook is handed the verified claims, so an exception raised in
# it can carry them into the log. The host app is better placed to
# log its own failure detail than we are to guess what is safe.
$c->log->error('OAuth2 RS: oauth_resolve_subject died')
if $c->can('log');
$c->_oauth_server_error;
$resolved = 0;
undef;
};
return 0 unless $resolved;
unless ($identity) {
# subject rejected -> generic 401 (no account-state leak)
$c->oauth_challenge(
Catalyst::Plugin::OAuth2::ResourceServer::Error->new(
error => 'invalid_token', http_status => 401 ) );
return 0;
}
}
$c->stash->{$SLOT} = { claims => $claims, identity => $identity };
return 1;
}
( run in 0.798 second using v1.01-cache-2.11-cpan-3f962061493 )