From 24120554e57c31f924e40b8617cf0c1d360c2fe6 Mon Sep 17 00:00:00 2001 From: Livio Amstutz Date: Thu, 26 Nov 2020 15:41:53 +0100 Subject: [PATCH] feat: add clock skew and option to put userinfo (profile, email, phone, address) into id_token --- example/internal/mock/storage.go | 8 ++++++++ pkg/oidc/token.go | 10 +++++----- pkg/op/client.go | 2 ++ pkg/op/mock/client.mock.go | 28 ++++++++++++++++++++++++++++ pkg/op/mock/storage.mock.impl.go | 8 ++++++++ pkg/op/token.go | 18 ++++++++++-------- 6 files changed, 61 insertions(+), 13 deletions(-) diff --git a/example/internal/mock/storage.go b/example/internal/mock/storage.go index aee9802..497c1b0 100644 --- a/example/internal/mock/storage.go +++ b/example/internal/mock/storage.go @@ -299,3 +299,11 @@ func (c *ConfClient) RestrictAdditionalAccessTokenScopes() func(scopes []string) func (c *ConfClient) IsScopeAllowed(scope string) bool { return false } + +func (c *ConfClient) UserInfoInIDToken() bool { + return false +} + +func (c *ConfClient) ClockSkew() time.Duration { + return 0 +} diff --git a/pkg/oidc/token.go b/pkg/oidc/token.go index bd84e64..ff8f33e 100644 --- a/pkg/oidc/token.go +++ b/pkg/oidc/token.go @@ -48,8 +48,8 @@ func EmptyAccessTokenClaims() AccessTokenClaims { return new(accessTokenClaims) } -func NewAccessTokenClaims(issuer, subject string, audience []string, expiration time.Time, id, clientID string) AccessTokenClaims { - now := time.Now().UTC() +func NewAccessTokenClaims(issuer, subject string, audience []string, expiration time.Time, id, clientID string, skew time.Duration) AccessTokenClaims { + now := time.Now().UTC().Add(-skew) if len(audience) == 0 { audience = append(audience, clientID) } @@ -203,14 +203,14 @@ func EmptyIDTokenClaims() IDTokenClaims { return new(idTokenClaims) } -func NewIDTokenClaims(issuer, subject string, audience []string, expiration, authTime time.Time, nonce string, acr string, amr []string, clientID string) IDTokenClaims { +func NewIDTokenClaims(issuer, subject string, audience []string, expiration, authTime time.Time, nonce string, acr string, amr []string, clientID string, skew time.Duration) IDTokenClaims { audience = AppendClientIDToAudience(clientID, audience) return &idTokenClaims{ Issuer: issuer, Audience: audience, Expiration: Time(expiration), - IssuedAt: Time(time.Now().UTC()), - AuthTime: Time(authTime), + IssuedAt: Time(time.Now().UTC().Add(-skew)), + AuthTime: Time(authTime.Add(-skew)), Nonce: nonce, AuthenticationContextClassReference: acr, AuthenticationMethodsReferences: amr, diff --git a/pkg/op/client.go b/pkg/op/client.go index ceca8b0..06fbcc3 100644 --- a/pkg/op/client.go +++ b/pkg/op/client.go @@ -37,6 +37,8 @@ type Client interface { RestrictAdditionalIdTokenScopes() func(scopes []string) []string RestrictAdditionalAccessTokenScopes() func(scopes []string) []string IsScopeAllowed(scope string) bool + UserInfoInIDToken() bool + ClockSkew() time.Duration } func ContainsResponseType(types []oidc.ResponseType, responseType oidc.ResponseType) bool { diff --git a/pkg/op/mock/client.mock.go b/pkg/op/mock/client.mock.go index df80bd0..5a89c34 100644 --- a/pkg/op/mock/client.mock.go +++ b/pkg/op/mock/client.mock.go @@ -77,6 +77,20 @@ func (mr *MockClientMockRecorder) AuthMethod() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthMethod", reflect.TypeOf((*MockClient)(nil).AuthMethod)) } +// ClockSkew mocks base method +func (m *MockClient) ClockSkew() time.Duration { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClockSkew") + ret0, _ := ret[0].(time.Duration) + return ret0 +} + +// ClockSkew indicates an expected call of ClockSkew +func (mr *MockClientMockRecorder) ClockSkew() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClockSkew", reflect.TypeOf((*MockClient)(nil).ClockSkew)) +} + // DevMode mocks base method func (m *MockClient) DevMode() bool { m.ctrl.T.Helper() @@ -216,3 +230,17 @@ func (mr *MockClientMockRecorder) RestrictAdditionalIdTokenScopes() *gomock.Call mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RestrictAdditionalIdTokenScopes", reflect.TypeOf((*MockClient)(nil).RestrictAdditionalIdTokenScopes)) } + +// UserInfoInIDToken mocks base method +func (m *MockClient) UserInfoInIDToken() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UserInfoInIDToken") + ret0, _ := ret[0].(bool) + return ret0 +} + +// UserInfoInIDToken indicates an expected call of UserInfoInIDToken +func (mr *MockClientMockRecorder) UserInfoInIDToken() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UserInfoInIDToken", reflect.TypeOf((*MockClient)(nil).UserInfoInIDToken)) +} diff --git a/pkg/op/mock/storage.mock.impl.go b/pkg/op/mock/storage.mock.impl.go index 92d5ad7..3accdef 100644 --- a/pkg/op/mock/storage.mock.impl.go +++ b/pkg/op/mock/storage.mock.impl.go @@ -184,3 +184,11 @@ func (c *ConfClient) RestrictAdditionalAccessTokenScopes() func(scopes []string) func (c *ConfClient) IsScopeAllowed(scope string) bool { return false } + +func (c *ConfClient) UserInfoInIDToken() bool { + return false +} + +func (c *ConfClient) ClockSkew() time.Duration { + return 0 +} diff --git a/pkg/op/token.go b/pkg/op/token.go index 057ab5b..cc6e11b 100644 --- a/pkg/op/token.go +++ b/pkg/op/token.go @@ -31,7 +31,7 @@ func CreateTokenResponse(ctx context.Context, authReq AuthRequest, client Client return nil, err } } - idToken, err := CreateIDToken(ctx, creator.Issuer(), authReq, client.IDTokenLifetime(), accessToken, code, creator.Storage(), creator.Signer(), client.RestrictAdditionalIdTokenScopes()) + idToken, err := CreateIDToken(ctx, creator.Issuer(), authReq, client.IDTokenLifetime(), accessToken, code, creator.Storage(), creator.Signer(), client) if err != nil { return nil, err } @@ -69,7 +69,7 @@ func CreateAccessToken(ctx context.Context, tokenRequest TokenRequest, accessTok if err != nil { return "", 0, err } - validity = exp.Sub(time.Now().UTC()) + validity = exp.Add(client.ClockSkew()).Sub(time.Now().UTC()) if accessTokenType == AccessTokenTypeJWT { token, err = CreateJWT(ctx, creator.Issuer(), tokenRequest, exp, id, creator.Signer(), client, creator.Storage()) return @@ -83,7 +83,7 @@ func CreateBearerToken(tokenID, subject string, crypto Crypto) (string, error) { } func CreateJWT(ctx context.Context, issuer string, tokenRequest TokenRequest, exp time.Time, id string, signer Signer, client Client, storage Storage) (string, error) { - claims := oidc.NewAccessTokenClaims(issuer, tokenRequest.GetSubject(), tokenRequest.GetAudience(), exp, id, client.GetID()) + claims := oidc.NewAccessTokenClaims(issuer, tokenRequest.GetSubject(), tokenRequest.GetAudience(), exp, id, client.GetID(), client.ClockSkew()) if client != nil { restrictedScopes := client.RestrictAdditionalAccessTokenScopes()(tokenRequest.GetScopes()) privateClaims, err := storage.GetPrivateClaimsFromScopes(ctx, tokenRequest.GetSubject(), client.GetID(), removeUserinfoScopes(restrictedScopes)) @@ -95,17 +95,19 @@ func CreateJWT(ctx context.Context, issuer string, tokenRequest TokenRequest, ex return utils.Sign(claims, signer.Signer()) } -func CreateIDToken(ctx context.Context, issuer string, authReq AuthRequest, validity time.Duration, accessToken, code string, storage Storage, signer Signer, restictAdditionalScopesFunc func([]string) []string) (string, error) { - exp := time.Now().UTC().Add(validity) - claims := oidc.NewIDTokenClaims(issuer, authReq.GetSubject(), authReq.GetAudience(), exp, authReq.GetAuthTime(), authReq.GetNonce(), authReq.GetACR(), authReq.GetAMR(), authReq.GetClientID()) - scopes := restictAdditionalScopesFunc(authReq.GetScopes()) +func CreateIDToken(ctx context.Context, issuer string, authReq AuthRequest, validity time.Duration, accessToken, code string, storage Storage, signer Signer, client Client) (string, error) { + exp := time.Now().UTC().Add(client.ClockSkew()).Add(validity) + claims := oidc.NewIDTokenClaims(issuer, authReq.GetSubject(), authReq.GetAudience(), exp, authReq.GetAuthTime(), authReq.GetNonce(), authReq.GetACR(), authReq.GetAMR(), authReq.GetClientID(), client.ClockSkew()) + scopes := client.RestrictAdditionalIdTokenScopes()(authReq.GetScopes()) if accessToken != "" { atHash, err := oidc.ClaimHash(accessToken, signer.SignatureAlgorithm()) if err != nil { return "", err } claims.SetAccessTokenHash(atHash) - scopes = removeUserinfoScopes(scopes) + if !client.UserInfoInIDToken() { + scopes = removeUserinfoScopes(scopes) + } } if len(scopes) > 0 { userInfo, err := storage.GetUserinfoFromScopes(ctx, authReq.GetSubject(), authReq.GetClientID(), scopes)