Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions oidc/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Package oidc implements OpenID Connect client logic for the golang.org/x/oauth2 package.
package oidc
12 changes: 7 additions & 5 deletions oidc/jwks.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import (

// StaticKeySet is a verifier that validates JWT against a static set of public keys.
type StaticKeySet struct {
// PublicKeys used to verify the JWT. Supported types are *rsa.PublicKey and
// *ecdsa.PublicKey.
// PublicKeys used to verify the JWT. Supported types are *rsa.PublicKey,
// *ecdsa.PublicKey, and ed25519.PublicKey.
PublicKeys []crypto.PublicKey
}

Expand Down Expand Up @@ -53,8 +53,10 @@ func (s *StaticKeySet) VerifySignature(ctx context.Context, jwt string) ([]byte,
// exposed for providers that don't support discovery or to prevent round trips to the
// discovery URL.
//
// The returned KeySet is a long lived verifier that caches keys based on any
// keys change. Reuse a common remote key set instead of creating new ones as needed.
// The returned KeySet is a long lived verifier that caches keys in memory,
// re-fetching from the remote URL when it encounters a key ID it hasn't seen.
// Reuse a single remote key set rather than creating a new one for each
// verification.
func NewRemoteKeySet(ctx context.Context, jwksURL string) *RemoteKeySet {
return newRemoteKeySet(ctx, jwksURL)
}
Expand Down Expand Up @@ -123,7 +125,7 @@ func (i *inflight) result() ([]jose.JSONWebKey, error) {
return i.keys, i.err
}

// paresdJWTKey is a context key that allows common setups to avoid parsing the
// parsedJWTKey is a context key that allows common setups to avoid parsing the
// JWT twice. It holds a *jose.JSONWebSignature value.
var parsedJWTKey contextKey

Expand Down
4 changes: 2 additions & 2 deletions oidc/logout.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ type LogoutToken struct {
Expiry time.Time
// Optional session ID claim ("sid").
//
// The exact semantics of session IDs very between identity providers. Use
// The exact semantics of session IDs vary between identity providers. Use
// your provider's documentation to determine what this correlates to and
// how it should be handled.
SessionID string
Expand Down Expand Up @@ -99,7 +99,7 @@ type logoutTokenJSON struct {
// }
// verifier := provider.Verifier(oidcConfig)
//
// mux.HandleFunc("POST /logout", func(w http.ResponseWriter, r *http.Reequest) {
// mux.HandleFunc("POST /logout", func(w http.ResponseWriter, r *http.Request) {
// rawLogoutToken := r.PostFormValue("logout_token")
// if rawLogoutToken == "" {
// // ...
Expand Down
70 changes: 63 additions & 7 deletions oidc/oidc.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// Package oidc implements OpenID Connect client logic for the golang.org/x/oauth2 package.
package oidc

import (
Expand Down Expand Up @@ -92,7 +91,24 @@ func doRequest(ctx context.Context, req *http.Request) (*http.Response, error) {
return client.Do(req.WithContext(ctx))
}

// Provider represents an OpenID Connect server's configuration.
// Provider represents an OpenID Connect server's configuration, fetched from
// the discovery document.
//
// To access fields in the discovery document that aren't exposed directly
// through this package's API, use the [Provider.Claims] method. For example, to
// access the registration or end session endpoints:
//
// p, err := oidc.NewProvider(ctx, "https://issuer.example.com")
// if err != nil {
// // ...
// }
// var metadata struct {
// EndSessionEndpoint string `json:"end_session_endpoint"`
// RegistrationEndpoint string `json:"registration_endpoint"`
// }
// if err := p.Claims(&metadata); err != nil {
// // ...
// }
type Provider struct {
issuer string
authURL string
Expand Down Expand Up @@ -213,6 +229,8 @@ type ProviderConfig struct {
//
// The provided context is only used for [http.Client] configuration through
// [ClientContext], not cancelation.
//
// For providers that implement discovery, use [NewProvider] instead.
func (p *ProviderConfig) NewProvider(ctx context.Context) *Provider {
return &Provider{
issuer: p.IssuerURL,
Expand All @@ -231,7 +249,7 @@ func (p *ProviderConfig) NewProvider(ctx context.Context) *Provider {
// or "https://login.salesforce.com".
//
// OpenID Connect providers that don't implement discovery or host the discovery
// document at a non-spec complaint path (such as requiring a URL parameter),
// document at a non-spec compliant path (such as requiring a URL parameter),
// should use [ProviderConfig] instead.
//
// See: https://openid.net/specs/openid-connect-discovery-1_0.html
Expand Down Expand Up @@ -348,6 +366,44 @@ func (u *UserInfo) Claims(v any) error {
}

// UserInfo uses the token source to query the provider's user info endpoint.
//
// It's fewer round trips and better supported to validate the ID Token with
// [Provider.Verifier], rather than using the UserInfo endpoint. The ID Token
// contains all information [UserInfo] provides:
//
// p, err := oidc.NewProvider(ctx, "https://issuer.example.com")
// if err != nil {
// // ...
// }
// config := &oidc.Config{
// ClientID: clientID,
// }
// v := p.Verifier(config)
// http.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
// oauth2Token, err := config.Exchange(ctx, r.URL.Query().Get("code"))
// if err != nil {
// // ...
// }
// rawIDToken, ok := oauth2Token.Extra("id_token").(string)
// if !ok {
// // ...
// }
// idToken, err := verifier.Verify(ctx, rawIDToken)
// if err != nil {
// // ...
// }
// // https://openid.net/specs/openid-connect-core-1_0.html#Claims
// var claims struct {
// Email string `json:"email"`
// EmailVerified bool `json:"email_verified"`
// Name string `json:"name"`
// Picture string `json:"picture"`
// }
// if err := idToken.Claims(&claims); err != nil {
// // ...
// }
// // Use claims...
// })
func (p *Provider) UserInfo(ctx context.Context, tokenSource oauth2.TokenSource) (*UserInfo, error) {
if p.userInfoURL == "" {
return nil, errors.New("oidc: user info endpoint is not supported by this provider")
Expand Down Expand Up @@ -425,15 +481,15 @@ type IDToken struct {
// A unique string which identifies the end user.
Subject string

// Expiry of the token. Ths package will not process tokens that have
// Expiry of the token. This package will not process tokens that have
// expired unless that validation is explicitly turned off.
Expiry time.Time
// When the token was issued by the provider.
IssuedAt time.Time

// Initial nonce provided during the authentication redirect.
//
// This package does NOT provided verification on the value of this field
// This package does NOT provide verification on the value of this field
// and it's the user's responsibility to ensure it contains a valid value.
Nonce string

Expand Down Expand Up @@ -472,8 +528,8 @@ func (i *IDToken) Claims(v any) error {
return json.Unmarshal(i.claims, v)
}

// VerifyAccessToken verifies that the hash of the access token that corresponds to the iD token
// matches the hash in the id token. It returns an error if the hashes don't match.
// VerifyAccessToken verifies that the hash of the access token that corresponds to the ID token
// matches the hash in the ID token. It returns an error if the hashes don't match.
// It is the caller's responsibility to ensure that the optional access token hash is present for the ID token
// before calling this method. See https://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken
func (i *IDToken) VerifyAccessToken(accessToken string) error {
Expand Down
29 changes: 15 additions & 14 deletions oidc/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ const (
issuerGoogleAccountsNoScheme = "accounts.google.com"
)

// TokenExpiredError indicates that Verify or VerifyLogout failed because the
// token was expired. This error does NOT indicate that the token is not also
// invalid for other reasons. Other checks might have failed if the expiration
// check had not failed.
// TokenExpiredError indicates that [IDTokenVerifier.Verify] or
// [IDTokenVerifier.VerifyLogout] failed because the token was expired. This
// error does NOT indicate that the token is not also invalid for other reasons.
// Other checks might have failed if the expiration check had not failed.
type TokenExpiredError struct {
// Expiry is the time when the token expired.
Expiry time.Time
Expand All @@ -31,7 +31,7 @@ func (e *TokenExpiredError) Error() string {
return fmt.Sprintf("oidc: token is expired (Token Expiry: %v)", e.Expiry)
}

// KeySet is a set of publc JSON Web Keys that can be used to validate the signature
// KeySet is a set of public JSON Web Keys that can be used to validate the signature
// of JSON web tokens. This is expected to be backed by a remote key set through
// provider metadata discovery or an in-memory set of keys delivered out-of-band.
type KeySet interface {
Expand All @@ -56,8 +56,8 @@ type IDTokenVerifier struct {
// NewVerifier returns a verifier manually constructed from a key set and issuer URL.
//
// It's easier to use provider discovery to construct an IDTokenVerifier than creating
// one directly. This method is intended to be used with provider that don't support
// metadata discovery, or avoiding round trips when the key set URL is already known.
// one directly. This method is intended to be used with providers that don't support
// metadata discovery, or to avoid round trips when the key set URL is already known.
//
// This constructor can be used to create a verifier directly using the issuer URL and
// JSON Web Key Set URL without using discovery:
Expand All @@ -83,8 +83,8 @@ type Config struct {
ClientID string
// If specified, only this set of algorithms may be used to sign the JWT.
//
// If the IDTokenVerifier is created from a provider with (*Provider).Verifier, this
// defaults to the set of algorithms the provider supports. Otherwise this values
// If the IDTokenVerifier is created from a provider with [Provider.Verifier], this
// defaults to the set of algorithms the provider supports. Otherwise this value
// defaults to RS256.
SupportedSigningAlgs []string

Expand All @@ -93,7 +93,7 @@ type Config struct {
// If true, token expiry is not checked.
SkipExpiryCheck bool

// SkipIssuerCheck is intended for specialized cases where the the caller wishes to
// SkipIssuerCheck is intended for specialized cases where the caller wishes to
// defer issuer validation. When enabled, callers MUST independently verify the Token's
// Issuer is a known good value.
//
Expand All @@ -118,16 +118,17 @@ type Config struct {
}

// VerifierContext returns an IDTokenVerifier that uses the provider's key set to
// verify JWTs. As opposed to Verifier, the context is used to configure requests
// to the upstream JWKs endpoint. The provided context's cancellation is ignored.
// verify JWTs. As opposed to [Provider.Verifier], the context is used to configure
// requests to the upstream JWKs endpoint. The provided context's cancellation is
// ignored.
func (p *Provider) VerifierContext(ctx context.Context, config *Config) *IDTokenVerifier {
return p.newVerifier(NewRemoteKeySet(ctx, p.jwksURL), config)
}

// Verifier returns an IDTokenVerifier that uses the provider's key set to verify JWTs.
//
// The returned verifier uses a background context for all requests to the upstream
// JWKs endpoint. To control that context, use VerifierContext instead.
// JWKs endpoint. To control that context, use [Provider.VerifierContext] instead.
func (p *Provider) Verifier(config *Config) *IDTokenVerifier {
return p.newVerifier(p.remoteKeySet(), config)
}
Expand Down Expand Up @@ -179,7 +180,7 @@ func resolveDistributedClaim(ctx context.Context, verifier *IDTokenVerifier, src
// Verify parses a raw ID Token, verifies it's been signed by the provider, performs
// any additional checks depending on the Config, and returns the payload.
//
// Verify does NOT do nonce validation, which is the callers responsibility.
// Verify does NOT do nonce validation, which is the caller's responsibility.
//
// See: https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
//
Expand Down
Loading