Skip to content

Commit 4b5f82d

Browse files
committed
oidc: add JSON tags to ProviderConfig
This PR adds JSON tags to allow parsing a ProviderConfig directly from the OpenID Connect JSON metadata document. Since this is the preferred workaround for providers that don't support discovery in a spec-compliant way, such as returning the wrong issuer, or requiring a URL parameter, make this path easier and add an example to the godoc. Updates #445 Updates #444 Updates #439 Updates #442 Updates #344 Fixes #290
1 parent 0fe9887 commit 4b5f82d

File tree

2 files changed

+120
-10
lines changed

2 files changed

+120
-10
lines changed

oidc/oidc.go

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -154,40 +154,65 @@ var supportedAlgorithms = map[string]bool{
154154
EdDSA: true,
155155
}
156156

157-
// ProviderConfig allows creating providers when discovery isn't supported. It's
158-
// generally easier to use NewProvider directly.
157+
// ProviderConfig allows direct creation of a [Provider] from metadata
158+
// configuration. This is intended for interop with providers that don't support
159+
// discovery, or host the JSON discovery document at an off-spec path.
160+
//
161+
// The ProviderConfig struct specifies JSON struct tags to support document
162+
// parsing.
163+
//
164+
// // Directly fetch the metadata document.
165+
// resp, err := http.Get("https://login.example.com/custom-metadata-path")
166+
// if err != nil {
167+
// // ...
168+
// }
169+
// defer resp.Body.Close()
170+
//
171+
// // Parse config from JSON metadata.
172+
// config := &oidc.ProviderConfig{}
173+
// if err := json.NewDecoder(resp.Body).Decode(config); err != nil {
174+
// // ...
175+
// }
176+
// p := config.NewProvider(context.Background())
177+
//
178+
// For providers that implement discovery, use [NewProvider] instead.
179+
//
180+
// See: https://openid.net/specs/openid-connect-discovery-1_0.html
159181
type ProviderConfig struct {
160182
// IssuerURL is the identity of the provider, and the string it uses to sign
161183
// ID tokens with. For example "https://accounts.google.com". This value MUST
162184
// match ID tokens exactly.
163-
IssuerURL string
185+
IssuerURL string `json:"issuer"`
164186
// AuthURL is the endpoint used by the provider to support the OAuth 2.0
165187
// authorization endpoint.
166-
AuthURL string
188+
AuthURL string `json:"authorization_endpoint"`
167189
// TokenURL is the endpoint used by the provider to support the OAuth 2.0
168190
// token endpoint.
169-
TokenURL string
191+
TokenURL string `json:"token_endpoint"`
170192
// DeviceAuthURL is the endpoint used by the provider to support the OAuth 2.0
171193
// device authorization endpoint.
172-
DeviceAuthURL string
194+
DeviceAuthURL string `json:"device_authorization_endpoint"`
173195
// UserInfoURL is the endpoint used by the provider to support the OpenID
174196
// Connect UserInfo flow.
175197
//
176198
// https://openid.net/specs/openid-connect-core-1_0.html#UserInfo
177-
UserInfoURL string
199+
UserInfoURL string `json:"userinfo_endpoint"`
178200
// JWKSURL is the endpoint used by the provider to advertise public keys to
179201
// verify issued ID tokens. This endpoint is polled as new keys are made
180202
// available.
181-
JWKSURL string
203+
JWKSURL string `json:"jwks_uri"`
182204

183205
// Algorithms, if provided, indicate a list of JWT algorithms allowed to sign
184206
// ID tokens. If not provided, this defaults to the algorithms advertised by
185207
// the JWK endpoint, then the set of algorithms supported by this package.
186-
Algorithms []string
208+
Algorithms []string `json:"id_token_signing_alg_values_supported"`
187209
}
188210

189211
// NewProvider initializes a provider from a set of endpoints, rather than
190212
// through discovery.
213+
//
214+
// The provided context is only used for [http.Client] configuration through
215+
// [ClientContext], not cancelation.
191216
func (p *ProviderConfig) NewProvider(ctx context.Context) *Provider {
192217
return &Provider{
193218
issuer: p.IssuerURL,
@@ -202,9 +227,14 @@ func (p *ProviderConfig) NewProvider(ctx context.Context) *Provider {
202227
}
203228

204229
// NewProvider uses the OpenID Connect discovery mechanism to construct a Provider.
205-
//
206230
// The issuer is the URL identifier for the service. For example: "https://accounts.google.com"
207231
// or "https://login.salesforce.com".
232+
//
233+
// 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),
235+
// should use [ProviderConfig] instead.
236+
//
237+
// See: https://openid.net/specs/openid-connect-discovery-1_0.html
208238
func NewProvider(ctx context.Context, issuer string) (*Provider, error) {
209239
wellKnown := strings.TrimSuffix(issuer, "/") + "/.well-known/openid-configuration"
210240
req, err := http.NewRequest("GET", wellKnown, nil)

oidc/oidc_test.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,86 @@ func TestNewProvider(t *testing.T) {
344344
}
345345
}
346346

347+
func TestProviderConfigJSON(t *testing.T) {
348+
// https://accounts.google.com/.well-known/openid-configuration
349+
testCase := `
350+
{
351+
"issuer": "https://accounts.google.com",
352+
"authorization_endpoint": "https://accounts.google.com/o/oauth2/v2/auth",
353+
"device_authorization_endpoint": "https://oauth2.googleapis.com/device/code",
354+
"token_endpoint": "https://oauth2.googleapis.com/token",
355+
"userinfo_endpoint": "https://openidconnect.googleapis.com/v1/userinfo",
356+
"revocation_endpoint": "https://oauth2.googleapis.com/revoke",
357+
"jwks_uri": "https://www.googleapis.com/oauth2/v3/certs",
358+
"response_types_supported": [
359+
"code",
360+
"token",
361+
"id_token",
362+
"code token",
363+
"code id_token",
364+
"token id_token",
365+
"code token id_token",
366+
"none"
367+
],
368+
"subject_types_supported": [
369+
"public"
370+
],
371+
"id_token_signing_alg_values_supported": [
372+
"RS256"
373+
],
374+
"scopes_supported": [
375+
"openid",
376+
"email",
377+
"profile"
378+
],
379+
"token_endpoint_auth_methods_supported": [
380+
"client_secret_post",
381+
"client_secret_basic"
382+
],
383+
"claims_supported": [
384+
"aud",
385+
"email",
386+
"email_verified",
387+
"exp",
388+
"family_name",
389+
"given_name",
390+
"iat",
391+
"iss",
392+
"name",
393+
"picture",
394+
"sub"
395+
],
396+
"code_challenge_methods_supported": [
397+
"plain",
398+
"S256"
399+
],
400+
"grant_types_supported": [
401+
"authorization_code",
402+
"refresh_token",
403+
"urn:ietf:params:oauth:grant-type:device_code",
404+
"urn:ietf:params:oauth:grant-type:jwt-bearer"
405+
]
406+
}
407+
`
408+
config := &ProviderConfig{}
409+
if err := json.Unmarshal([]byte(testCase), config); err != nil {
410+
t.Fatalf("Parsing provider config: %v", err)
411+
}
412+
413+
want := &ProviderConfig{
414+
IssuerURL: "https://accounts.google.com",
415+
AuthURL: "https://accounts.google.com/o/oauth2/v2/auth",
416+
TokenURL: "https://oauth2.googleapis.com/token",
417+
DeviceAuthURL: "https://oauth2.googleapis.com/device/code",
418+
UserInfoURL: "https://openidconnect.googleapis.com/v1/userinfo",
419+
JWKSURL: "https://www.googleapis.com/oauth2/v3/certs",
420+
Algorithms: []string{"RS256"},
421+
}
422+
if !reflect.DeepEqual(config, want) {
423+
t.Errorf("Parsing provider config returned unexpected result, got=%#v, want=%#v", config, want)
424+
}
425+
}
426+
347427
func TestGetClient(t *testing.T) {
348428
ctx := context.Background()
349429
if c := getClient(ctx); c != nil {

0 commit comments

Comments
 (0)