This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
make # build to ~/go/bin/openid-client
go build -o ~/go/bin/openid-client openid-client/openid-client.go
go test ./... # run all tests
go test ./pkg/client/... # run a single package's testsSingle-binary Go CLI. All flag parsing and command dispatch lives in openid-client/openid-client.go (main()). Shared logic lives in two packages:
pkg/client/— all OAuth2/OIDC flow handlersclient.go—authorization_code,refresh,client_credentials,passwordflows; private_key_jwt helpersexchange.go—token-exchange,jwt-bearer,saml-bearer,passcode,introspect,revoke,userinfo,token-list; defines theagentHTTP user-agent variable used everywheretoken.go— sharedOpenIdTokenstruct{IdToken, AccessToken, RefreshToken}
pkg/cf/— reads/writes~/.cf/config.jsonfor CF UAA simulation (-cfflag)
Command dispatch: main() checks if os.Args[1] starts with -; if not, it's treated as a command name and remaining args are parsed by flag. Empty command defaults to authorization_code.
Request building: main() builds a shared url.Values (requestMap) with client_id, client_secret, client_assertion, and token_format, then passes it to most handlers. The authorization_code handler uses requestMap only as a source of optional params, and builds its own POST body url.Values; the refresh handler builds its own url.Values inside HandleRefreshFlow. Optional params forwarded to the authorization endpoint include: login_hint, nonce, prompt, max_age, sso_token, app_tid, resource, post_logout_redirect_uri, logout_uri, refresh_expiry, and idp.
Client auth precedence (resolved in main before any handler is called):
-client_assertion→ external JWT (privateKeyJwt)-client_jwtP12 +-pin→CreatePrivateKeyJwt(x5t thumbprint as kid)-client_tlsP12 +-pin→ mutual TLShttp.Client-client_jwt_keyPEM +-client_jwt_kid→CreatePrivateKeyJwtKid-client_secret→ plain secret in form body (exception:HandleCorpIdpExchangeFlowuses HTTP Basic Auth instead)
OIDC discovery: oidc.NewProvider fetches .well-known/openid-configuration. If discovery fails and -url is set with a non-empty command, endpoints fall back to the -url value directly (does not apply to the default authorization_code flow).
The decode command is local-only (no network, no -issuer/-client_id needed). It supports signed JWTs only (JWS compact serialization, 3 parts), base64url-decodes header and payload, and pretty-prints them with jq-style ANSI colors (implemented in pkg/client/exchange.go with no external deps). It does not verify the signature or validate any claims. Flags: -header (header only), -payload (payload only), -raw (plain JSON without colors or labels — requires -header or -payload). The command exits before OIDC discovery.
IAS-specific URL rewrites (done inside handlers):
HandleCorpIdpExchangeFlow:/oauth2/token→/oauth2/exchange/corporateidpssocommand:/oauth2/authorize→/saml2/idp/ssoHandleTokenRevocation:/oauth2/token→/oauth2/revokeHandlePasscode: callsGET {issuer}/service/users/passcode
Environment variables fall back when the corresponding flag is empty: OPENID_ISSUER, OPENID_ID, OPENID_SECRET, OPENID_PIN, OPENID_USER, OPENID_PASSWORD, OPENID_FORMAT, OPENID_QUERY.
See AGENTS.md for deeper detail on token-exchange parameter mapping, SSO token flow, and passcode flow.