Skip to content

Commit ff35f6d

Browse files
committed
oidc: improve documentation for APIs
There are a number of open issues and PRs that can be already be served by existing APIs. Specifically, anyone can parse the discovery document from the Provider to support axillary specs. Update the documentation to hopefully make the Provider.Claims API more visiable. The logout example also uses this method. Fixes #458 Fixes #424 Fixes #226 Fixes #409
1 parent 0db9053 commit ff35f6d

5 files changed

Lines changed: 89 additions & 28 deletions

File tree

oidc/doc.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// Package oidc implements OpenID Connect client logic for the golang.org/x/oauth2 package.
2+
package oidc

oidc/jwks.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ import (
1717

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

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

126-
// paresdJWTKey is a context key that allows common setups to avoid parsing the
128+
// parsedJWTKey is a context key that allows common setups to avoid parsing the
127129
// JWT twice. It holds a *jose.JSONWebSignature value.
128130
var parsedJWTKey contextKey
129131

oidc/logout.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ type LogoutToken struct {
3838
Expiry time.Time
3939
// Optional session ID claim ("sid").
4040
//
41-
// The exact semantics of session IDs very between identity providers. Use
41+
// The exact semantics of session IDs vary between identity providers. Use
4242
// your provider's documentation to determine what this correlates to and
4343
// how it should be handled.
4444
SessionID string
@@ -99,7 +99,7 @@ type logoutTokenJSON struct {
9999
// }
100100
// verifier := provider.Verifier(oidcConfig)
101101
//
102-
// mux.HandleFunc("POST /logout", func(w http.ResponseWriter, r *http.Reequest) {
102+
// mux.HandleFunc("POST /logout", func(w http.ResponseWriter, r *http.Request) {
103103
// rawLogoutToken := r.PostFormValue("logout_token")
104104
// if rawLogoutToken == "" {
105105
// // ...

oidc/oidc.go

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
// Package oidc implements OpenID Connect client logic for the golang.org/x/oauth2 package.
21
package oidc
32

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

95-
// Provider represents an OpenID Connect server's configuration.
94+
// Provider represents an OpenID Connect server's configuration, fetched from
95+
// the discovery document.
96+
//
97+
// To access fields in the discovery document that aren't exposed directly
98+
// through this package's API, use the [Provider.Claims] method. For example, to
99+
// access the registration or end session endpoints:
100+
//
101+
// p, err := oidc.NewProvider(ctx, "https://issuer.example.com")
102+
// if err != nil {
103+
// // ...
104+
// }
105+
// var metadata struct {
106+
// EndSessionEndpoint string `json:"end_session_endpoint"`
107+
// RegistrationEndpoint string `json:"registration_endpoint"`
108+
// }
109+
// if err := p.Claims(&metadata); err != nil {
110+
// // ...
111+
// }
96112
type Provider struct {
97113
issuer string
98114
authURL string
@@ -213,6 +229,8 @@ type ProviderConfig struct {
213229
//
214230
// The provided context is only used for [http.Client] configuration through
215231
// [ClientContext], not cancelation.
232+
//
233+
// For providers that implement discovery, use [NewProvider] instead.
216234
func (p *ProviderConfig) NewProvider(ctx context.Context) *Provider {
217235
return &Provider{
218236
issuer: p.IssuerURL,
@@ -231,7 +249,7 @@ func (p *ProviderConfig) NewProvider(ctx context.Context) *Provider {
231249
// or "https://login.salesforce.com".
232250
//
233251
// OpenID Connect providers that don't implement discovery or host the discovery
234-
// document at a non-spec complaint path (such as requiring a URL parameter),
252+
// document at a non-spec compliant path (such as requiring a URL parameter),
235253
// should use [ProviderConfig] instead.
236254
//
237255
// See: https://openid.net/specs/openid-connect-discovery-1_0.html
@@ -348,6 +366,44 @@ func (u *UserInfo) Claims(v any) error {
348366
}
349367

350368
// UserInfo uses the token source to query the provider's user info endpoint.
369+
//
370+
// It's fewer round trips and better supported to validate the ID Token with
371+
// [Provider.Verifier], rather than using the UserInfo endpoint. The ID Token
372+
// contains all information [UserInfo] provides:
373+
//
374+
// p, err := oidc.NewProvider(ctx, "https://issuer.example.com")
375+
// if err != nil {
376+
// // ...
377+
// }
378+
// config := &oidc.Config{
379+
// ClientID: clientID,
380+
// }
381+
// v := p.Verifier(config)
382+
// http.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
383+
// oauth2Token, err := config.Exchange(ctx, r.URL.Query().Get("code"))
384+
// if err != nil {
385+
// // ...
386+
// }
387+
// rawIDToken, ok := oauth2Token.Extra("id_token").(string)
388+
// if !ok {
389+
// // ...
390+
// }
391+
// idToken, err := verifier.Verify(ctx, rawIDToken)
392+
// if err != nil {
393+
// // ...
394+
// }
395+
// // https://openid.net/specs/openid-connect-core-1_0.html#Claims
396+
// var claims struct {
397+
// Email string `json:"email"`
398+
// EmailVerified bool `json:"email_verified"`
399+
// Name string `json:"name"`
400+
// Picture string `json:"picture"`
401+
// }
402+
// if err := idToken.Claims(&claims); err != nil {
403+
// // ...
404+
// }
405+
// // Use claims...
406+
// })
351407
func (p *Provider) UserInfo(ctx context.Context, tokenSource oauth2.TokenSource) (*UserInfo, error) {
352408
if p.userInfoURL == "" {
353409
return nil, errors.New("oidc: user info endpoint is not supported by this provider")
@@ -425,15 +481,15 @@ type IDToken struct {
425481
// A unique string which identifies the end user.
426482
Subject string
427483

428-
// Expiry of the token. Ths package will not process tokens that have
484+
// Expiry of the token. This package will not process tokens that have
429485
// expired unless that validation is explicitly turned off.
430486
Expiry time.Time
431487
// When the token was issued by the provider.
432488
IssuedAt time.Time
433489

434490
// Initial nonce provided during the authentication redirect.
435491
//
436-
// This package does NOT provided verification on the value of this field
492+
// This package does NOT provide verification on the value of this field
437493
// and it's the user's responsibility to ensure it contains a valid value.
438494
Nonce string
439495

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

475-
// VerifyAccessToken verifies that the hash of the access token that corresponds to the iD token
476-
// matches the hash in the id token. It returns an error if the hashes don't match.
531+
// VerifyAccessToken verifies that the hash of the access token that corresponds to the ID token
532+
// matches the hash in the ID token. It returns an error if the hashes don't match.
477533
// It is the caller's responsibility to ensure that the optional access token hash is present for the ID token
478534
// before calling this method. See https://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken
479535
func (i *IDToken) VerifyAccessToken(accessToken string) error {

oidc/verify.go

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ const (
1818
issuerGoogleAccountsNoScheme = "accounts.google.com"
1919
)
2020

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

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

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

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

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

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

0 commit comments

Comments
 (0)