open_id
The open_id library provides a portable OpenID Connect client for
the Authorization Code + PKCE flow. It supports provider discovery,
authorization URL construction, authorization response parsing and state
validation, authorization-code and refresh-token exchanges, UserInfo
requests, RP-initiated logout URL construction, JWKS retrieval and
caching, and ID-token verification for RS256 and ES256.
The library uses http_client with
transport(http_process_transport) for HTTP requests. HTTPS requests
are made using http_process_transport TLS connections, which require
the openssl command to be available unless overridden with the
openssl_executable/1 option.
This library can be used with backend Prolog systems that support
unbound integer arithmetic and the sockets library: ECLiPSe, SICStus
Prolog, SWI-Prolog, Trealla Prolog, and XVM.
Loading
To load the library, load the loader.lgt file:
| ?- logtalk_load(open_id(loader)).
Testing
To test this library, load the tester.lgt file:
| ?- logtalk_load(open_id(tester)).
To run the optional live tests against Google’s public OpenID Connect
discovery and JWKS endpoints, load the tester_live.lgt file:
| ?- logtalk_load(open_id(tester_live)).
The Google discovery and JWKS live tests do not require credentials. The optional Google ID-token verification live test requires a fresh Google-issued ID token and its expected audience:
$ export LOGTALK_OPEN_ID_GOOGLE_ID_TOKEN="..."
$ export LOGTALK_OPEN_ID_GOOGLE_AUDIENCE="..."
If the ID token includes a nonce claim, also define the
LOGTALK_OPEN_ID_GOOGLE_NONCE environment variable.
The live tests also include an end-to-end Authorization Code + PKCE flow
against the community-run https://oidctest.wsweet.org/ OpenID
Connect test provider, using its documented test account
dwho/dwho and pre-registered client private/tardis. This
provider has no availability guarantees.
Basic usage
| ?- open_id::discover('https://issuer.example', Provider, []),
open_id::authorization_url(
Provider,
authorization_request([
client_id('client-id'),
redirect_uri('https://client.example/callback'),
scope([openid, profile])
]),
URL,
Session,
[]
).
After redirecting the user to URL and receiving the callback URL:
| ?- open_id::authorization_code(CallbackURL, Session, Code, []),
open_id::exchange_code(
Provider,
Code,
Session,
Tokens,
[client_authentication(client_secret_basic('client-secret'))]
),
Tokens = tokens(Properties),
member(id_token(IDToken), Properties),
open_id::verify_id_token(IDToken, Provider, Claims,
[expected_audience('client-id')]).
To call the UserInfo endpoint with the resulting access token:
| ?- Tokens = tokens(Properties),
member(access_token(AccessToken), Properties),
open_id::userinfo(Provider, AccessToken, UserInfo, []).
To use a refresh token:
| ?- open_id::refresh_token(
Provider,
RefreshToken,
RefreshedTokens,
[
client_id('client-id'),
client_authentication(client_secret_basic('client-secret'))
]
).
To build an RP-initiated logout URL:
| ?- open_id::logout_url(
Provider,
logout_request([
id_token_hint(IDToken),
post_logout_redirect_uri('https://client.example/logout/callback'),
state('logout-state')
]),
LogoutURL,
[]
).
Current scope
OpenID Provider discovery.
Authorization Code + PKCE (
S256) URL construction.Authorization response parsing and state validation.
Authorization-code token exchange for public clients,
client_secret_post/1, andclient_secret_basic/1.Refresh-token exchange and UserInfo retrieval.
RP-initiated logout URL construction.
JWKS retrieval, caching, and refresh-on-unknown-
kidkey rotation.ID-token JWT verification for
RS256andES256using OpenSSL.Claim validation for issuer, subject, audience, authorized party, nonce, expiration, not-before, and issued-at tolerance.
Current limitations
Device authorization, dynamic client registration, browser launching, and callback server ownership are outside the current scope.
ID-token verification requires the
opensslcommand.