Documentation
¶
Overview ¶
Package ssoadapter provides a hardened, generic OpenID Connect implementation of the credbound.SSOProvider port, so hosts do not have to hand-roll the network side of SSO. Any spec-compliant OIDC issuer works through the generic adapter; Google and Microsoft Entra ID are plain OIDC issuers and need no dedicated code.
Responsibilities ¶
The adapter owns the protocol exchange only: discovery, the authorization code redirect (with PKCE S256, state, and nonce), the code exchange, and ID token verification. Credbound keeps everything else — the sealed continuation carrying the adapter's opaque session, ceremony TTL, identity linking, persistence, audit, and revocation.
Registration ¶
Register a provider by wiring it into credbound.Config.SSOProviders. Google through the generic adapter looks like this:
provider, err := ssoadapter.New(ssoadapter.Config{
ConfigurationID: "0198b463-51a2-7cde-8000-0123456789ab", // UUIDv7 chosen by the host
Kind: credbound.SSOProviderGoogle,
IssuerURL: "https://accounts.google.com",
ClientID: os.Getenv("GOOGLE_CLIENT_ID"),
ClientSecret: os.Getenv("GOOGLE_CLIENT_SECRET"),
RedirectURL: "https://app.example.com/sso/callback",
})
if err != nil {
log.Fatal(err)
}
manager, err := credbound.New(credbound.Config{
Store: store,
Passwords: hasher,
SecretKey: secretKey,
SSOProviders: []credbound.SSOProvider{provider},
})
For Microsoft Entra ID use the tenant-specific issuer (https://login.microsoftonline.com/{tenant-id}/v2.0) with credbound.SSOProviderMicrosoft. The multi-tenant "common" endpoint is not supported because its issuer varies per tenant, which defeats strict issuer validation.
Callback handling ¶
The host's HTTP callback handler forwards the provider response verbatim to credbound's FinishSSO. The adapter accepts the full callback URL, the bare query string, or a JSON object with "code" and "state" fields:
func callback(w http.ResponseWriter, r *http.Request) {
continuation := readContinuationCookie(r)
auth, err := manager.FinishSSO(r.Context(), continuation, []byte(r.URL.String()))
// ...
}
Email trust ¶
The adapter forwards the email claim only when the issuer asserts email_verified=true. An unverified email is attacker-controlled input at many IdPs (anyone can type an address into a profile), so surfacing it would let a hostile account impersonate a victim's address in credbound's identity records. Issuers that never send email_verified (Microsoft Entra ID among them) therefore produce identities without an email; linking and login still work because credbound keys SSO identities on issuer and subject, never on email.
Index ¶
- Variables
- type Config
- type Provider
- func (p *Provider) Begin(ctx context.Context, request credbound.SSORequest) (credbound.SSOProviderChallenge, error)
- func (p *Provider) ConfigurationID() credbound.UUID
- func (p *Provider) Finish(ctx context.Context, sessionBytes, response []byte) (credbound.SSOClaims, error)
- func (p *Provider) Kind() credbound.SSOProviderKind
Constants ¶
This section is empty.
Variables ¶
var ( // ErrStateMismatch reports that the state parameter returned by the // issuer does not match the one issued in Begin. ErrStateMismatch = errors.New("ssoadapter: state parameter mismatch") // ErrNonceMismatch reports that the ID token nonce is missing or does // not match the one issued in Begin. ErrNonceMismatch = errors.New("ssoadapter: id token nonce mismatch") )
Sentinel errors for the two verification failures hosts most often want to distinguish in logs. Credbound maps every Finish error to ErrInvalidCredentials before it reaches the end user.
Functions ¶
This section is empty.
Types ¶
type Config ¶
type Config struct {
// ConfigurationID is the host-chosen UUIDv7 under which credbound
// indexes this provider and its linked identities. Required.
ConfigurationID credbound.UUID
// Kind is the credbound provider kind. Defaults to
// credbound.SSOProviderOIDC; credbound.SSOProviderGoogle and
// credbound.SSOProviderMicrosoft are also accepted since both speak
// plain OIDC. Other kinds are rejected.
Kind credbound.SSOProviderKind
// IssuerURL is the OIDC issuer used for discovery and strict issuer
// validation. Required. HTTPS is mandatory except for loopback hosts,
// which may use HTTP for local development and tests.
IssuerURL string
// ClientID is the OAuth 2.0 client identifier. Required.
ClientID string
// ClientSecret authenticates the client at the token endpoint. Required
// unless PublicClient is set; the default posture is a confidential
// client.
ClientSecret string
// PublicClient marks this registration as a PKCE-only public client
// without a client secret. When set, ClientSecret must be empty.
PublicClient bool
// RedirectURL is the callback URL registered at the issuer. Required.
// HTTPS is mandatory except for loopback hosts.
RedirectURL string
// Scopes defaults to "openid email profile". The openid scope is always
// enforced.
Scopes []string
// HTTPClient is used for discovery, key fetching, and the code
// exchange. Defaults to a client with a 10 second timeout; a supplied
// client without a timeout is shallow-copied and given the default.
HTTPClient *http.Client
// MetadataRefreshInterval bounds how long a discovered issuer document is
// cached before it is re-discovered, so a rotated endpoint or jwks_uri is
// picked up without a redeploy — the same posture as the SAML adapter's
// metadata TTL. Zero uses a default of 12 hours.
MetadataRefreshInterval time.Duration
// Clock supplies the current time for token validation and step-up
// freshness checks. Defaults to time.Now.
Clock func() time.Time
}
Config describes one OIDC provider registration.
type Provider ¶
type Provider struct {
// contains filtered or unexported fields
}
Provider is a generic OIDC implementation of credbound.SSOProvider. It is stateless across ceremonies: everything a Finish needs travels inside the opaque Session bytes that credbound seals into its continuation.
func New ¶
New validates the configuration and returns a Provider ready to register in credbound.Config.SSOProviders. Discovery is performed lazily on first use so construction does not require the issuer to be reachable.
func (*Provider) Begin ¶
func (p *Provider) Begin(ctx context.Context, request credbound.SSORequest) (credbound.SSOProviderChallenge, error)
Begin implements credbound.SSOProvider. It generates fresh state, nonce, and PKCE S256 verifier material, builds the authorization code URL, and returns the material as opaque Session bytes for credbound to seal into its continuation. When ForceReauthentication is set the URL additionally carries prompt=login and max_age=0 so the issuer re-runs its own authentication (and MFA) policy.
func (*Provider) ConfigurationID ¶
ConfigurationID implements credbound.SSOProvider.
func (*Provider) Finish ¶
func (p *Provider) Finish(ctx context.Context, sessionBytes, response []byte) (credbound.SSOClaims, error)
Finish implements credbound.SSOProvider. sessionBytes is the Session issued by Begin (returned by credbound from its sealed continuation) and response is the raw callback payload from the host: the full callback URL, the bare query string, or a JSON object with code and state fields.
Finish verifies the state with a constant-time comparison, exchanges the code with the PKCE verifier, verifies the ID token (issuer, audience, expiry, RS256/ES256 allowlist — "none" and HMAC algorithms are rejected by construction), and requires a constant-time nonce match. Replay of a callback is bounded by credbound's continuation TTL and by the issuer's single-use authorization code; the adapter itself keeps no state.
func (*Provider) Kind ¶
func (p *Provider) Kind() credbound.SSOProviderKind
Kind implements credbound.SSOProvider.