From 9dd27cc44e7db675865e53b6faa85492f2e44ea2 Mon Sep 17 00:00:00 2001 From: Frank He Date: Thu, 7 Mar 2019 22:22:33 -0800 Subject: [PATCH 1/7] Add optional PrivateClaims to jwt.Config --- jwt/jwt.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/jwt/jwt.go b/jwt/jwt.go index 99f3e0a32..6d5fd617a 100644 --- a/jwt/jwt.go +++ b/jwt/jwt.go @@ -66,6 +66,9 @@ type Config struct { // request. If empty, the value of TokenURL is used as the // intended audience. Audience string + + // PrivateClaims optionally specifies private claims in the JWT. + PrivateClaims map[string]interface{} } // TokenSource returns a JWT TokenSource using the configuration @@ -100,6 +103,7 @@ func (js jwtSource) Token() (*oauth2.Token, error) { Iss: js.conf.Email, Scope: strings.Join(js.conf.Scopes, " "), Aud: js.conf.TokenURL, + PrivateClaims: js.conf.PrivateClaims, } if subject := js.conf.Subject; subject != "" { claimSet.Sub = subject From 8baca543ee3142a54061f817a96ae015de1d9580 Mon Sep 17 00:00:00 2001 From: "Wenlei (Frank) He" Date: Thu, 7 Mar 2019 23:30:59 -0800 Subject: [PATCH 2/7] Test PrivateClaims support in jwt.go. --- jwt/jwt_test.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/jwt/jwt_test.go b/jwt/jwt_test.go index 9dfa3b350..8bfa90882 100644 --- a/jwt/jwt_test.go +++ b/jwt/jwt_test.go @@ -11,6 +11,7 @@ import ( "fmt" "net/http" "net/http/httptest" + "reflect" "strings" "testing" @@ -221,6 +222,16 @@ func TestJWTFetch_AssertionPayload(t *testing.T) { TokenURL: ts.URL, Audience: "https://example.com", }, + { + Email: "aaa2@xxx.com", + PrivateKey: dummyPrivateKey, + PrivateKeyID: "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + TokenURL: ts.URL, + PrivateClaims: map[string]interface{}{ + "private0": "claim0", + "private1": "claim1", + }, + }, } { t.Run(conf.Email, func(t *testing.T) { _, err := conf.TokenSource(context.Background()).Token() @@ -261,6 +272,18 @@ func TestJWTFetch_AssertionPayload(t *testing.T) { if got, want := claimSet.Prn, conf.Subject; got != want { t.Errorf("payload prn = %q; want %q", got, want) } + if len(conf.PrivateClaims) > 0 { + var got interface{} + if err := json.Unmarshal(gotjson, &got); err != nil { + t.Errorf("failed to parse payload; err = %q", err) + } + m := got.(map[string]interface{}) + for v, k := range conf.PrivateClaims { + if !reflect.DeepEqual(m[v], k) { + t.Errorf("payload private claims key = %q: got %q; want %q", v, m[v], k) + } + } + } }) } } From 339f3641d99846f2e452f723def18b577f431ca0 Mon Sep 17 00:00:00 2001 From: "Wenlei (Frank) He" Date: Fri, 8 Mar 2019 09:20:43 -0800 Subject: [PATCH 3/7] Support using ID token as the ouath access token. --- jwt/jwt.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/jwt/jwt.go b/jwt/jwt.go index 6d5fd617a..ba71ab268 100644 --- a/jwt/jwt.go +++ b/jwt/jwt.go @@ -66,9 +66,12 @@ type Config struct { // request. If empty, the value of TokenURL is used as the // intended audience. Audience string - + // PrivateClaims optionally specifies private claims in the JWT. PrivateClaims map[string]interface{} + + // UseIDToken optionally uses ID token instead of access token. + UseIDToken bool } // TokenSource returns a JWT TokenSource using the configuration @@ -100,10 +103,10 @@ func (js jwtSource) Token() (*oauth2.Token, error) { } hc := oauth2.NewClient(js.ctx, nil) claimSet := &jws.ClaimSet{ - Iss: js.conf.Email, - Scope: strings.Join(js.conf.Scopes, " "), - Aud: js.conf.TokenURL, - PrivateClaims: js.conf.PrivateClaims, + Iss: js.conf.Email, + Scope: strings.Join(js.conf.Scopes, " "), + Aud: js.conf.TokenURL, + PrivateClaims: js.conf.PrivateClaims, } if subject := js.conf.Subject; subject != "" { claimSet.Sub = subject @@ -168,6 +171,9 @@ func (js jwtSource) Token() (*oauth2.Token, error) { if err != nil { return nil, fmt.Errorf("oauth2: error decoding JWT token: %v", err) } + if js.conf.UseIDToken { + token.AccessToken = tokenRes.IDToken + } token.Expiry = time.Unix(claimSet.Exp, 0) } return token, nil From b33c8d1d83081d64864eea3a535491bbbc27d711 Mon Sep 17 00:00:00 2001 From: "Wenlei (Frank) He" Date: Fri, 8 Mar 2019 15:08:54 -0800 Subject: [PATCH 4/7] Updated comments and format. --- jwt/jwt.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jwt/jwt.go b/jwt/jwt.go index ba71ab268..4caa964bc 100644 --- a/jwt/jwt.go +++ b/jwt/jwt.go @@ -67,7 +67,8 @@ type Config struct { // intended audience. Audience string - // PrivateClaims optionally specifies private claims in the JWT. + // See http://tools.ietf.org/html/draft-jones-json-web-token-10#section-4.3 + // PrivateClaims optionally specifies custome private claims in the JWT. PrivateClaims map[string]interface{} // UseIDToken optionally uses ID token instead of access token. From a5a809ae1217ee156ae601d192ac746c27c4fc28 Mon Sep 17 00:00:00 2001 From: "Wenlei (Frank) He" Date: Sun, 14 Apr 2019 15:11:36 -0700 Subject: [PATCH 5/7] Update docs in jwt.go. --- jwt/jwt.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/jwt/jwt.go b/jwt/jwt.go index 4caa964bc..1faa971e0 100644 --- a/jwt/jwt.go +++ b/jwt/jwt.go @@ -67,11 +67,15 @@ type Config struct { // intended audience. Audience string + // PrivateClaims optionally specifies custom private claims in the JWT. // See http://tools.ietf.org/html/draft-jones-json-web-token-10#section-4.3 - // PrivateClaims optionally specifies custome private claims in the JWT. + // + // Private claim values can be different types, therefore interface{} is + // used and marshalled using custom code. PrivateClaims map[string]interface{} - // UseIDToken optionally uses ID token instead of access token. + // UseIDToken optionally uses ID token instead of access token when + // server returns both 'access_token' and 'id_token'. UseIDToken bool } From ac8ecd8e45e246d461cee55932ae259d8bb22d3a Mon Sep 17 00:00:00 2001 From: "Wenlei (Frank) He" Date: Fri, 17 May 2019 10:50:36 -0700 Subject: [PATCH 6/7] Returns error when UseIDToken is true but response doesn't have ID token. Updated comments. --- jwt/jwt.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/jwt/jwt.go b/jwt/jwt.go index 1faa971e0..b2bf18298 100644 --- a/jwt/jwt.go +++ b/jwt/jwt.go @@ -69,13 +69,10 @@ type Config struct { // PrivateClaims optionally specifies custom private claims in the JWT. // See http://tools.ietf.org/html/draft-jones-json-web-token-10#section-4.3 - // - // Private claim values can be different types, therefore interface{} is - // used and marshalled using custom code. PrivateClaims map[string]interface{} - // UseIDToken optionally uses ID token instead of access token when - // server returns both 'access_token' and 'id_token'. + // UseIDToken optionally specifies whether ID token should be used instead + // of access token when the server returns both. UseIDToken bool } @@ -176,10 +173,13 @@ func (js jwtSource) Token() (*oauth2.Token, error) { if err != nil { return nil, fmt.Errorf("oauth2: error decoding JWT token: %v", err) } - if js.conf.UseIDToken { - token.AccessToken = tokenRes.IDToken - } token.Expiry = time.Unix(claimSet.Exp, 0) } + if js.conf.UseIDToken { + if tokenRes.IDToken == "" { + return nil, fmt.Errorf("oauth2: response doesn't have JWT token") + } + token.AccessToken = tokenRes.IDToken + } return token, nil } From 7a6e247e68f742129ac9a5d5a5f1a8ad428ccb09 Mon Sep 17 00:00:00 2001 From: "Wenlei (Frank) He" Date: Fri, 17 May 2019 10:55:44 -0700 Subject: [PATCH 7/7] Update print format in jwt_test.go. --- jwt/jwt_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jwt/jwt_test.go b/jwt/jwt_test.go index 8bfa90882..9772dc520 100644 --- a/jwt/jwt_test.go +++ b/jwt/jwt_test.go @@ -280,7 +280,7 @@ func TestJWTFetch_AssertionPayload(t *testing.T) { m := got.(map[string]interface{}) for v, k := range conf.PrivateClaims { if !reflect.DeepEqual(m[v], k) { - t.Errorf("payload private claims key = %q: got %q; want %q", v, m[v], k) + t.Errorf("payload private claims key = %q: got %#v; want %#v", v, m[v], k) } } }