Catalyst-Plugin-OAuth2-AuthorizationServer
view release on metacpan or search on metacpan
lib/Catalyst/Plugin/OAuth2/AuthorizationServer/Server.pm view on Meta::CPAN
$self->_now + $self->refresh_ttl,
);
# The family was revoked while this rotation was in flight: a concurrent
# replay was detected. Same generic error as any other dead token.
$self->_grant_error('unknown or revoked refresh token') unless $created;
return {
access_token => $access,
token_type => 'Bearer',
expires_in => $self->access_ttl,
refresh_token => $refresh,
( defined $binding->{scope} ? ( scope => $binding->{scope} ) : () ),
};
}
sub exchange_authorization_code ( $self, $params ) {
my $code = $params->{code};
$self->_grant_error('code is required')
unless defined $code && length $code;
my $binding = $self->store->consume_auth_code($code);
$self->_grant_error('unknown or used authorization code') unless $binding;
# When the request identifies its client, it must match the code's bound
# client (RFC 6749 4.1.3 defence-in-depth on top of PKCE).
if ( defined $params->{client_id} && length $params->{client_id} ) {
$self->_grant_error('client_id mismatch')
unless $params->{client_id} eq $binding->{client_id};
}
$self->_grant_error('redirect_uri mismatch')
unless ( $params->{redirect_uri} // '' ) eq $binding->{redirect_uri};
my $verifier = $params->{code_verifier};
$self->_grant_error('code_verifier required')
unless defined $verifier && length $verifier;
# RFC 7636 4.1: 43-128 characters of the unreserved set.
$self->_grant_error(
'code_verifier must be 43-128 characters of [A-Za-z0-9._~-]')
unless $verifier =~ m{\A[A-Za-z0-9._~-]{43,128}\z};
$self->_grant_error('PKCE verification failed')
unless $self->_ct_eq( $self->_pkce_s256($verifier),
$binding->{code_challenge} );
# A code exchange births a new family; a rotation inherits one.
return $self->_issue_token_pair(
{ %$binding, family_id => $self->_random_token(16) } );
}
sub refresh ( $self, $params ) {
my $raw = $params->{refresh_token};
$self->_grant_error('refresh_token is required')
unless defined $raw && length $raw;
my $result = $self->store->rotate_refresh_token( $self->_hash_token($raw) );
$self->_grant_error('unknown or revoked refresh token') unless $result;
# RFC 9700: a replay means the chain is compromised and we cannot tell the
# legitimate client from the attacker, so the whole family goes. Revoke
# before erroring, and let a failing revoke_family surface as a 500: a
# Store that cannot revoke is broken, and answering invalid_grant while
# leaving the family alive fails the wrong way.
if ( $result->{reused} ) {
Carp::croak 'internal: reused refresh token binding has no family_id'
unless defined $result->{binding}{family_id}
&& length $result->{binding}{family_id};
$self->store->revoke_family( $result->{binding}{family_id} );
# Same error and description as an unknown token: telling an attacker
# that reuse was detected confirms they hold a real token.
$self->_grant_error('unknown or revoked refresh token');
}
my $binding = $result->{binding};
# Mirror the code-exchange client binding check (RFC 6749 6).
if ( defined $params->{client_id} && length $params->{client_id} ) {
$self->_grant_error('client_id mismatch')
unless $params->{client_id} eq $binding->{client_id};
}
return $self->_issue_token_pair($binding);
}
sub _invalid_metadata ( $self, $desc ) {
Catalyst::Plugin::OAuth2::AuthorizationServer::Error->throw(
error => 'invalid_client_metadata',
error_description => $desc,
http_status => 400,
);
}
sub metadata_document ( $self ) {
my %doc = (
issuer => $self->issuer,
authorization_endpoint => $self->authorize_endpoint,
token_endpoint => $self->token_endpoint,
registration_endpoint => $self->registration_endpoint,
response_types_supported => ['code'],
grant_types_supported => [ 'authorization_code', 'refresh_token' ],
code_challenge_methods_supported => ['S256'],
token_endpoint_auth_methods_supported => ['none'],
);
$doc{scopes_supported} = $self->scopes_supported if $self->scopes_supported;
return \%doc;
}
sub register_client ( $self, $metadata ) {
my $uris = $metadata->{redirect_uris};
$self->_invalid_metadata('redirect_uris is required')
unless ref $uris eq 'ARRAY' && @$uris;
$self->_invalid_metadata('too many redirect_uris')
if @$uris > $self->redirect_uris_max;
for my $u (@$uris) {
$self->_invalid_metadata('redirect_uri not a string')
if ref $u || !defined $u || !length $u;
$self->_invalid_metadata('redirect_uri too long')
if length $u > $self->redirect_uri_max_length;
my $parsed = URI->new($u);
my $scheme = lc( $parsed->scheme // '' );
my $ok_scheme =
$scheme eq 'https' ? 1
( run in 3.392 seconds using v1.01-cache-2.11-cpan-14f38c9f855 )