diff --git a/pkg/op/token.go b/pkg/op/token.go index 68e19d7..4d3e620 100644 --- a/pkg/op/token.go +++ b/pkg/op/token.go @@ -20,6 +20,13 @@ type TokenRequest interface { GetScopes() []string } +type AccessTokenClient interface { + GetID() string + ClockSkew() time.Duration + RestrictAdditionalAccessTokenScopes() func(scopes []string) []string + GrantTypes() []oidc.GrantType +} + func CreateTokenResponse(ctx context.Context, request IDTokenRequest, client Client, creator TokenCreator, createAccessToken bool, code, refreshToken string) (*oidc.AccessTokenResponse, error) { var accessToken, newRefreshToken string var validity time.Duration @@ -55,7 +62,7 @@ func CreateTokenResponse(ctx context.Context, request IDTokenRequest, client Cli }, nil } -func createTokens(ctx context.Context, tokenRequest TokenRequest, storage Storage, refreshToken string, client Client) (id, newRefreshToken string, exp time.Time, err error) { +func createTokens(ctx context.Context, tokenRequest TokenRequest, storage Storage, refreshToken string, client AccessTokenClient) (id, newRefreshToken string, exp time.Time, err error) { if needsRefreshToken(tokenRequest, client) { return storage.CreateAccessAndRefreshTokens(ctx, tokenRequest, refreshToken) } @@ -63,7 +70,7 @@ func createTokens(ctx context.Context, tokenRequest TokenRequest, storage Storag return } -func needsRefreshToken(tokenRequest TokenRequest, client Client) bool { +func needsRefreshToken(tokenRequest TokenRequest, client AccessTokenClient) bool { switch req := tokenRequest.(type) { case AuthRequest: return strings.Contains(req.GetScopes(), oidc.ScopeOfflineAccess) && req.GetResponseType() == oidc.ResponseTypeCode && ValidateGrantType(client, oidc.GrantTypeRefreshToken) @@ -74,7 +81,7 @@ func needsRefreshToken(tokenRequest TokenRequest, client Client) bool { } } -func CreateAccessToken(ctx context.Context, tokenRequest TokenRequest, accessTokenType AccessTokenType, creator TokenCreator, client Client, refreshToken string) (accessToken, newRefreshToken string, validity time.Duration, err error) { +func CreateAccessToken(ctx context.Context, tokenRequest TokenRequest, accessTokenType AccessTokenType, creator TokenCreator, client AccessTokenClient, refreshToken string) (accessToken, newRefreshToken string, validity time.Duration, err error) { id, newRefreshToken, exp, err := createTokens(ctx, tokenRequest, creator.Storage(), refreshToken, client) if err != nil { return "", "", 0, err @@ -96,7 +103,7 @@ func CreateBearerToken(tokenID, subject string, crypto Crypto) (string, error) { return crypto.Encrypt(tokenID + ":" + subject) } -func CreateJWT(ctx context.Context, issuer string, tokenRequest TokenRequest, exp time.Time, id string, client Client, storage Storage) (string, error) { +func CreateJWT(ctx context.Context, issuer string, tokenRequest TokenRequest, exp time.Time, id string, client AccessTokenClient, storage Storage) (string, error) { claims := oidc.NewAccessTokenClaims(issuer, tokenRequest.GetSubject(), tokenRequest.GetAudience(), exp, id, client.GetID(), client.ClockSkew()) if client != nil { restrictedScopes := client.RestrictAdditionalAccessTokenScopes()(tokenRequest.GetScopes()) diff --git a/pkg/op/token_jwt_profile.go b/pkg/op/token_jwt_profile.go index 1c18660..da09411 100644 --- a/pkg/op/token_jwt_profile.go +++ b/pkg/op/token_jwt_profile.go @@ -53,21 +53,40 @@ func ParseJWTProfileGrantRequest(r *http.Request, decoder httphelper.Decoder) (* return tokenReq, nil } -//CreateJWTTokenResponse creates +type JWTProfileTokenStorage interface { + JWTProfileTokenType(ctx context.Context, request TokenRequest) (AccessTokenType, error) +} + +// CreateJWTTokenResponse creates an access_token response for a JWT Profile Grant request +// by default the access_token is an opaque string, but can be specified by implementing the JWTProfileTokenStorage interface func CreateJWTTokenResponse(ctx context.Context, tokenRequest TokenRequest, creator TokenCreator) (*oidc.AccessTokenResponse, error) { - id, exp, err := creator.Storage().CreateAccessToken(ctx, tokenRequest) - if err != nil { - return nil, err - } - accessToken, err := CreateBearerToken(id, tokenRequest.GetSubject(), creator.Crypto()) - if err != nil { - return nil, err + // return an opaque token as default to not break current implementations + tokenType := AccessTokenTypeBearer + + // the current CreateAccessToken function, esp. CreateJWT requires an implementation of an AccessTokenClient + client := &jwtProfileClient{ + id: tokenRequest.GetSubject(), } + // by implementing the JWTProfileTokenStorage the storage can specify the AccessTokenType to be returned + tokenStorage, ok := creator.Storage().(JWTProfileTokenStorage) + if ok { + var err error + tokenType, err = tokenStorage.JWTProfileTokenType(ctx, tokenRequest) + if err != nil { + return nil, err + } + } + + accessToken, _, validity, err := CreateAccessToken(ctx, tokenRequest, tokenType, creator, client, "") + if err != nil { + return nil, err + } + exp := uint64(validity.Seconds()) return &oidc.AccessTokenResponse{ AccessToken: accessToken, TokenType: oidc.BearerToken, - ExpiresIn: uint64(exp.Sub(time.Now().UTC()).Seconds()), + ExpiresIn: exp, }, nil } @@ -77,3 +96,27 @@ func CreateJWTTokenResponse(ctx context.Context, tokenRequest TokenRequest, crea func ParseJWTProfileRequest(r *http.Request, decoder httphelper.Decoder) (*oidc.JWTProfileGrantRequest, error) { return ParseJWTProfileGrantRequest(r, decoder) } + +type jwtProfileClient struct { + id string +} + +func (j *jwtProfileClient) GetID() string { + return j.id +} + +func (j *jwtProfileClient) ClockSkew() time.Duration { + return 0 +} + +func (j *jwtProfileClient) RestrictAdditionalAccessTokenScopes() func(scopes []string) []string { + return func(scopes []string) []string { + return scopes + } +} + +func (j *jwtProfileClient) GrantTypes() []oidc.GrantType { + return []oidc.GrantType{ + oidc.GrantTypeBearer, + } +} diff --git a/pkg/op/token_request.go b/pkg/op/token_request.go index ade6130..54e79f0 100644 --- a/pkg/op/token_request.go +++ b/pkg/op/token_request.go @@ -129,8 +129,8 @@ func AuthorizePrivateJWTKey(ctx context.Context, clientAssertion string, exchang return client, nil } -//ValidateGrantType ensures that the requested grant_type is allowed by the Client -func ValidateGrantType(client Client, grantType oidc.GrantType) bool { +//ValidateGrantType ensures that the requested grant_type is allowed by the client +func ValidateGrantType(client interface{ GrantTypes() []oidc.GrantType }, grantType oidc.GrantType) bool { if client == nil { return false }