Catalyst-Plugin-OpenIDConnect
view release on metacpan or search on metacpan
CHANGELOG.md view on Meta::CPAN
Prefix matching and host-only matching are not permitted.
4. Any mismatch returns an `invalid_request` OAuth error; no redirect is
issued.
5. When a redirect is permitted, the optional `state` parameter is appended
verbatim to the redirect URI as required by the specification.
### Added
- **`JWT::decode_id_token_hint($token)`** â new method on
`Catalyst::Plugin::OpenIDConnect::Utils::JWT`. Verifies the token signature
against the configured public key and returns the decoded claims hashref, or
`undef` if the token is malformed or the signature is invalid. Distinct from
`verify_token` in that it does not reject expired tokens.
- **`Controller::Root::_allowed_post_logout_uris($client)`** â private helper
that normalises the `post_logout_redirect_uris` client config field from
either an arrayref (YAML/JSON config) or a whitespace-delimited string
(Config::General-style config) into a flat list of URIs.
- **`post_logout_redirect_uris` client config key** â each client may now
declare a list of permitted post-logout redirect URIs. This key is required
lib/Catalyst/Plugin/OpenIDConnect/Controller/Root.pm view on Meta::CPAN
if ($redirect_uri) {
# id_token_hint is required when a redirect is requested so that we
# can identify the client and verify the URI is registered for it.
# Without this check an attacker could redirect to any arbitrary URL.
unless ($id_token_hint) {
$c->log->warn('post_logout_redirect_uri provided without id_token_hint');
return $self->_json_error( $c, 'invalid_request',
'id_token_hint is required when post_logout_redirect_uri is provided' );
}
# $hint_claims was decoded above; reject if invalid.
unless ($hint_claims) {
$c->log->warn('Invalid id_token_hint provided at logout');
return $self->_json_error( $c, 'invalid_request', 'Invalid id_token_hint' );
}
# aud may be a string or an array per RFC 7519 §4.1.3.
my $aud = $hint_claims->{aud};
my $client_id = ref $aud eq 'ARRAY' ? $aud->[0] : $aud;
unless ($client_id) {
lib/Catalyst/Plugin/OpenIDConnect/Utils/JWT.pm view on Meta::CPAN
=head2 verify_token($token, %opts)
Verifies a JWT token with the configured public key.
Mandatory claims C<exp> and C<iss> are always validated. The C<nbf>
claim is validated when present. Pass C<expected_audience> to also
validate the C<aud> claim:
$jwt->verify_token($token, expected_audience => 'my-client-id');
Returns a hashref with decoded claims on success.
Raises an exception on verification failure.
=cut
sub verify_token {
my ( $self, $token, %opts ) = @_;
my $expected_audience = $opts{expected_audience};
$self->logger->debug('Verifying JWT token') if $self->logger;
lib/Catalyst/Plugin/OpenIDConnect/Utils/JWT.pm view on Meta::CPAN
$signing_input,
$signature
);
$self->logger->debug('JWT signature verified') if $self->logger;
# Decode payload
my $payload_json = _urlsafe_b64_decode($payload_b64);
my $payload = decode_json($payload_json);
$self->logger->debug('JWT payload decoded successfully') if $self->logger;
# --- Mandatory claim validation (RFC 7519 §4.1, OIDC Core §2) ---
# exp and iss must be present and valid; a token that omits them
# must be rejected regardless of its signature.
die 'Missing exp claim' unless defined $payload->{exp};
die 'Token expired' if $payload->{exp} < time();
die 'Missing iss claim' unless defined $payload->{iss};
die 'Invalid issuer' unless $payload->{iss} eq $self->issuer;
# nbf (not-before) is optional but must be honoured when present
lib/Catalyst/Plugin/OpenIDConnect/Utils/Store/Redis.pm view on Meta::CPAN
return $data;
}
=head2 consume_authorization_code($code)
Atomically fetches and deletes the authorization code from Redis using the
C<GETDEL> command (Redis E<ge> 6.2). Because C<GETDEL> is a single server-side
operation it is race-free: a second concurrent request carrying the same code
will receive C<nil> from Redis and be rejected.
Returns the decoded code data hashref on success, or C<undef> if the code
does not exist, has already been consumed, or cannot be decoded.
=cut
sub consume_authorization_code {
my ( $self, $code ) = @_;
$self->logger->debug("Consuming authorization code: $code") if $self->logger;
# GETDEL (Redis >= 6.2) fetches and deletes atomically in a single
# round-trip, eliminating the GET + DEL race condition (HIGH-4).
lib/Catalyst/Plugin/OpenIDConnect/Utils/Store/Redis.pm view on Meta::CPAN
$self->_redis->setex( $self->prefix . 'rt:' . $jti, $ttl, $data );
# Secondary index for bulk-revocation at logout.
my $set_key = $self->prefix . 'rt_sub:' . $sub;
$self->_redis->sadd( $set_key, $jti );
$self->_redis->expire( $set_key, $ttl );
}
=head2 consume_refresh_token($jti)
Atomically fetches and deletes the JTI entry using C<GETDEL> (Redis E<ge> 6.2).
Returns the decoded data hashref, or C<undef> if absent (already used, revoked,
or expired).
=cut
sub consume_refresh_token {
my ( $self, $jti ) = @_;
my $raw = $self->_redis->getdel( $self->prefix . 'rt:' . $jti );
return unless defined $raw;
my $data = try { decode_json($raw) } catch { undef };
if ($data) {
( run in 1.135 second using v1.01-cache-2.11-cpan-600a1bdf6e4 )