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.