NAME

WWW::Keycloak::OIDC - OpenID Connect against one Keycloak realm

VERSION

version 0.001

SYNOPSIS

my $oidc = WWW::Keycloak->new( base_url => $url, realm => 'main' )->oidc;

my $claims = $oidc->verify_token( $jwt, audience => 'my-api' );
my $tokens = $oidc->password_token( client_id => 'cli', username => 'alice', password => $pw, totp => '123456' );

DESCRIPTION

The OpenID Connect side of a realm: discovery, the signing keys, token verification, and the token endpoint in all the grant types Keycloak offers.

Endpoints come from the realm's discovery document, fetched once and kept. An error from the token endpoint is a WWW::Keycloak::Error::API whose oauth_error carries the OAuth code, so a device-flow poll can tell authorization_pending from a real failure.

issuer

Required. <base_url>/realms/<realm>.

ua

Required. The LWP::UserAgent to use.

algorithms

Signature algorithms "verify_token" accepts. Never none, never HMAC.

jwks_min_age

Seconds that have to pass before "verify_token" fetches the keys again for a token signed with an unknown key. Default 60, so that a stream of forged tokens does not become a stream of requests to Keycloak.

now

Coderef returning the current epoch. For tests.

discovery

The discovery document, fetched on first use.

endpoint

my $url = $oidc->endpoint('device_authorization_endpoint');

A URL from the discovery document. Throws when it is missing.

jwks

my $keys = $oidc->jwks;
my $keys = $oidc->jwks( force_refresh => 1 );

The realm's public signing keys, kept after the first fetch.

verify_token

my $claims = $oidc->verify_token( $jwt, audience => 'my-api', type => 'Bearer' );

Checks signature, issuer and expiry, the audience when one is given, and the typ claim when type is given. Keycloak puts Bearer into access tokens and ID into ID tokens; an API that accepts access tokens should say type => 'Bearer', or an ID token issued to any client of the realm passes as well. When the signing key is unknown the keys are fetched again, at most once per "jwks_min_age". Returns the claims, or throws a validation error saying why the token was rejected.

userinfo

my $info = $oidc->userinfo($access_token);

introspect

my $state = $oidc->introspect( $token, client_id => 'api', client_secret => $secret );

Needs a confidential client.

password_token

my $tokens = $oidc->password_token( client_id => 'cli', username => 'alice', password => $pw, totp => '123456', scope => 'openid' );

The direct grant. totp is needed for users with a one-time password.

client_credentials_token

my $tokens = $oidc->client_credentials_token( client_id => 'svc', client_secret => $secret );

refresh_token

my $tokens = $oidc->refresh_token( $refresh, client_id => 'cli' );

exchange_authorization_code

my $tokens = $oidc->exchange_authorization_code( code => $code, redirect_uri => $uri, client_id => 'web', client_secret => $secret );

device_token

my $tokens = eval { $oidc->device_token( device_code => $start->{device_code}, client_id => 'cli' ) };
# $@->oauth_error eq 'authorization_pending' while nobody has approved

One poll of the device flow. The loop is the caller's, or Airlock::Client's.

device_authorization

my $start = $oidc->device_authorization( client_id => 'cli', scope => 'openid' );

Starts a device flow: device_code, user_code, verification_uri, verification_uri_complete, expires_in, interval.

logout

$oidc->logout( refresh_token => $refresh, client_id => 'cli' );

Ends the session the refresh token belongs to.

SUPPORT

Issues

Please report bugs and feature requests on GitHub at https://github.com/Getty/p5-www-keycloak/issues.

IRC

Join #kubernetes on irc.perl.org or message Getty directly.

CONTRIBUTING

Contributions are welcome! Please fork the repository and submit a pull request.

AUTHOR

Torsten Raudssus <getty@cpan.org>

COPYRIGHT AND LICENSE

This software is copyright (c) 2026 by Torsten Raudssus <torsten@raudssus.de> https://raudssus.de/.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.