diff --git a/pkg/oidc/token.go b/pkg/oidc/token.go index 784684d..f6b2000 100644 --- a/pkg/oidc/token.go +++ b/pkg/oidc/token.go @@ -31,6 +31,7 @@ type AccessTokenClaims interface { GetSubject() string GetTokenID() string SetPrivateClaims(map[string]interface{}) + GetClaims() map[string]interface{} } type IDTokenClaims interface { @@ -151,6 +152,11 @@ func (a *accessTokenClaims) SetPrivateClaims(claims map[string]interface{}) { a.claims = claims } +// GetClaims implements the AccessTokenClaims interface +func (a *accessTokenClaims) GetClaims() map[string]interface{} { + return a.claims +} + func (a *accessTokenClaims) MarshalJSON() ([]byte, error) { type Alias accessTokenClaims s := &struct { @@ -612,3 +618,12 @@ func GenerateJWTProfileToken(assertion JWTProfileAssertionClaims) (string, error } return signedAssertion.CompactSerialize() } + +type TokenExchangeResponse struct { + AccessToken string `json:"access_token"` // Can be access token or ID token + IssuedTokenType TokenType `json:"issued_token_type"` + TokenType string `json:"token_type"` + ExpiresIn uint64 `json:"expires_in,omitempty"` + Scopes SpaceDelimitedArray `json:"scope,omitempty"` + RefreshToken string `json:"refresh_token,omitempty"` +} diff --git a/pkg/oidc/token_request.go b/pkg/oidc/token_request.go index ec11057..a3a9eaa 100644 --- a/pkg/oidc/token_request.go +++ b/pkg/oidc/token_request.go @@ -40,6 +40,29 @@ var AllGrantTypes = []GrantType{ type GrantType string +const ( + AccessTokenType TokenType = "urn:ietf:params:oauth:token-type:access_token" + RefreshTokenType TokenType = "urn:ietf:params:oauth:token-type:refresh_token" + IDTokenType TokenType = "urn:ietf:params:oauth:token-type:id_token" + JWTTokenType TokenType = "urn:ietf:params:oauth:token-type:jwt" +) + +var AllTokenTypes = []TokenType{ + AccessTokenType, RefreshTokenType, IDTokenType, JWTTokenType, +} + +type TokenType string + +func (t TokenType) IsValid() bool { + for _, tt := range AllTokenTypes { + if t == tt { + return true + } + } + + return false +} + type TokenRequest interface { // GrantType GrantType `schema:"grant_type"` GrantType() GrantType @@ -203,14 +226,15 @@ func (j *JWTTokenRequest) GetScopes() []string { } type TokenExchangeRequest struct { - subjectToken string `schema:"subject_token"` - subjectTokenType string `schema:"subject_token_type"` - actorToken string `schema:"actor_token"` - actorTokenType string `schema:"actor_token_type"` - resource []string `schema:"resource"` - audience Audience `schema:"audience"` - Scope SpaceDelimitedArray `schema:"scope"` - requestedTokenType string `schema:"requested_token_type"` + GrantType GrantType `schema:"grant_type"` + SubjectToken string `schema:"subject_token"` + SubjectTokenType TokenType `schema:"subject_token_type"` + ActorToken string `schema:"actor_token"` + ActorTokenType TokenType `schema:"actor_token_type"` + Resource []string `schema:"resource"` + Audience Audience `schema:"audience"` + Scopes SpaceDelimitedArray `schema:"scope"` + RequestedTokenType TokenType `schema:"requested_token_type"` } type ClientCredentialsRequest struct { diff --git a/pkg/op/op.go b/pkg/op/op.go index 8a0dc46..804a9f0 100644 --- a/pkg/op/op.go +++ b/pkg/op/op.go @@ -190,7 +190,7 @@ type openidProvider struct { interceptors []HttpInterceptor timer <-chan time.Time accessTokenVerifierOpts []AccessTokenVerifierOpt - idTokenHintVerifierOpts []IDTokenHintVerifierOpt + idTokenHintVerifierOpts []IDTokenHintVerifierOpt } func (o *openidProvider) Issuer() string { @@ -246,7 +246,8 @@ func (o *openidProvider) GrantTypeRefreshTokenSupported() bool { } func (o *openidProvider) GrantTypeTokenExchangeSupported() bool { - return false + _, ok := o.storage.(TokenExchangeStorage) + return ok } func (o *openidProvider) GrantTypeJWTAuthorizationSupported() bool { diff --git a/pkg/op/storage.go b/pkg/op/storage.go index 2b3c93f..aaeba8c 100644 --- a/pkg/op/storage.go +++ b/pkg/op/storage.go @@ -24,6 +24,8 @@ type AuthStorage interface { // // * *oidc.JWTTokenRequest from a JWT that is the assertion value of a JWT Profile // Grant: https://datatracker.ietf.org/doc/html/rfc7523#section-2.1 + // + // * TokenExchangeRequest CreateAccessToken(context.Context, TokenRequest) (accessTokenID string, expiration time.Time, err error) // The TokenRequest parameter of CreateAccessAndRefreshTokens can be any of: @@ -35,6 +37,8 @@ type AuthStorage interface { // * AuthRequest as by returned by the AuthRequestByID or AuthRequestByCode (above). // Used for the authorization code flow which requested offline_access scope and // registered the refresh_token grant type in advance + // + // * TokenExchangeRequest CreateAccessAndRefreshTokens(ctx context.Context, request TokenRequest, currentRefreshToken string) (accessTokenID string, newRefreshTokenID string, expiration time.Time, err error) TokenRequestByRefreshToken(ctx context.Context, refreshTokenID string) (RefreshTokenRequest, error) @@ -54,6 +58,45 @@ type ClientCredentialsStorage interface { ClientCredentialsTokenRequest(ctx context.Context, clientID string, scopes []string) (TokenRequest, error) } +type TokenExchangeStorage interface { + // ValidateTokenExchangeRequest will be called to validate parsed (including tokens) Token Exchange Grant request. + // + // Important validations can include: + // - permissions + // - set requested token type to some default value if it is empty (rfc 8693 allows it) using SetRequestedTokenType method. + // Depending on RequestedTokenType - the following tokens will be issued: + // - RefreshTokenType - both access and refresh tokens + // - AccessTokenType - only access token + // - IDTokenType - only id token + // - validation of subject's token type on possibility to be exchanged to the requested token type (according to your requirements) + // - scopes (and update them using SetCurrentScopes method) + // - set new subject if it differs from exchange subject (impersonation flow) + // + // Request will include subject's and/or actor's token claims if correspinding tokens are access/id_token issued by op + // or third party tokens parsed by TokenExchangeTokensVerifierStorage interface methods. + ValidateTokenExchangeRequest(ctx context.Context, request TokenExchangeRequest) error + + // CreateTokenExchangeRequest will be called after parsing and validating token exchange request. + // Stored request is not accessed later by op - so it is up to implementer to decide + // should this method actually store the request or not (common use case - store for it for audit purposes) + CreateTokenExchangeRequest(ctx context.Context, request TokenExchangeRequest) error + + // GetPrivateClaimsFromTokenExchangeRequest will be called during access token creation. + // Claims evaluation can be based on all validated request data available, including: scopes, resource, audience, etc. + GetPrivateClaimsFromTokenExchangeRequest(ctx context.Context, request TokenExchangeRequest) (claims map[string]interface{}, err error) + + // SetUserinfoFromTokenExchangeRequest will be called during id token creation. + // Claims evaluation can be based on all validated request data available, including: scopes, resource, audience, etc. + SetUserinfoFromTokenExchangeRequest(ctx context.Context, userinfo oidc.UserInfoSetter, request TokenExchangeRequest) error +} + +// TokenExchangeTokensVerifierStorage is an optional interface used in token exchange process to verify tokens +// issued by third-party applications. If interface is not implemented - only tokens issued by op will be exchanged. +type TokenExchangeTokensVerifierStorage interface { + VerifyExchangeSubjectToken(ctx context.Context, token string, tokenType oidc.TokenType) (tokenIDOrToken string, subject string, tokenClaims map[string]interface{}, err error) + VerifyExchangeActorToken(ctx context.Context, token string, tokenType oidc.TokenType) (tokenIDOrToken string, actor string, tokenClaims map[string]interface{}, err error) +} + type OPStorage interface { GetClientByClientID(ctx context.Context, clientID string) (Client, error) AuthorizeClientIDSecret(ctx context.Context, clientID, clientSecret string) error diff --git a/pkg/op/token.go b/pkg/op/token.go index 3a72261..790bea7 100644 --- a/pkg/op/token.go +++ b/pkg/op/token.go @@ -69,6 +69,8 @@ func needsRefreshToken(tokenRequest TokenRequest, client Client) bool { switch req := tokenRequest.(type) { case AuthRequest: return strings.Contains(req.GetScopes(), oidc.ScopeOfflineAccess) && req.GetResponseType() == oidc.ResponseTypeCode && ValidateGrantType(client, oidc.GrantTypeRefreshToken) + case TokenExchangeRequest: + return req.GetRequestedTokenType() == oidc.RefreshTokenType case RefreshTokenRequest: return true default: @@ -102,7 +104,20 @@ func CreateJWT(ctx context.Context, issuer string, tokenRequest TokenRequest, ex 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)) + + var ( + privateClaims map[string]interface{} + err error + ) + if tokenExchangeRequest, ok := tokenRequest.(TokenExchangeRequest); ok { + privateClaims, err = storage.(TokenExchangeStorage).GetPrivateClaimsFromTokenExchangeRequest( + ctx, + tokenExchangeRequest, + ) + } else { + privateClaims, err = storage.GetPrivateClaimsFromScopes(ctx, tokenRequest.GetSubject(), client.GetID(), removeUserinfoScopes(restrictedScopes)) + } + if err != nil { return "", err } @@ -139,7 +154,16 @@ func CreateIDToken(ctx context.Context, issuer string, request IDTokenRequest, v scopes = removeUserinfoScopes(scopes) } } - if len(scopes) > 0 { + + if tokenExchangeRequest, ok := request.(TokenExchangeRequest); ok { + userInfo := oidc.NewUserInfo() + teStorage := storage.(TokenExchangeStorage) + err := teStorage.SetUserinfoFromTokenExchangeRequest(ctx, userInfo, tokenExchangeRequest) + if err != nil { + return "", err + } + claims.SetUserinfo(userInfo) + } else if len(scopes) > 0 { userInfo := oidc.NewUserInfo() err := storage.SetUserinfoFromScopes(ctx, userInfo, request.GetSubject(), request.GetClientID(), scopes) if err != nil { diff --git a/pkg/op/token_exchange.go b/pkg/op/token_exchange.go index 7bb6e42..fc2db30 100644 --- a/pkg/op/token_exchange.go +++ b/pkg/op/token_exchange.go @@ -1,11 +1,399 @@ package op import ( - "errors" + "context" "net/http" + "net/url" + "strings" + "time" + + httphelper "github.com/zitadel/oidc/pkg/http" + "github.com/zitadel/oidc/pkg/oidc" ) -// TokenExchange will handle the OAuth 2.0 token exchange grant ("urn:ietf:params:oauth:grant-type:token-exchange") -func TokenExchange(w http.ResponseWriter, r *http.Request, exchanger Exchanger) { - RequestError(w, r, errors.New("unimplemented")) +type TokenExchangeRequest interface { + GetAMR() []string + GetAudience() []string + GetResourses() []string + GetAuthTime() time.Time + GetClientID() string + GetScopes() []string + GetSubject() string + GetRequestedTokenType() oidc.TokenType + + GetExchangeSubject() string + GetExchangeSubjectTokenType() oidc.TokenType + GetExchangeSubjectTokenIDOrToken() string + GetExchangeSubjectTokenClaims() map[string]interface{} + + GetExchangeActor() string + GetExchangeActorTokenType() oidc.TokenType + GetExchangeActorTokenIDOrToken() string + GetExchangeActorTokenClaims() map[string]interface{} + + SetCurrentScopes(scopes []string) + SetRequestedTokenType(tt oidc.TokenType) + SetSubject(subject string) +} + +type tokenExchangeRequest struct { + exchangeSubjectTokenIDOrToken string + exchangeSubjectTokenType oidc.TokenType + exchangeSubject string + exchangeSubjectTokenClaims map[string]interface{} + + exchangeActorTokenIDOrToken string + exchangeActorTokenType oidc.TokenType + exchangeActor string + exchangeActorTokenClaims map[string]interface{} + + resource []string + audience oidc.Audience + scopes oidc.SpaceDelimitedArray + requestedTokenType oidc.TokenType + clientID string + authTime time.Time + subject string +} + +func (r *tokenExchangeRequest) GetAMR() []string { + return []string{} +} + +func (r *tokenExchangeRequest) GetAudience() []string { + return r.audience +} + +func (r *tokenExchangeRequest) GetResourses() []string { + return r.resource +} + +func (r *tokenExchangeRequest) GetAuthTime() time.Time { + return r.authTime +} + +func (r *tokenExchangeRequest) GetClientID() string { + return r.clientID +} + +func (r *tokenExchangeRequest) GetScopes() []string { + return r.scopes +} + +func (r *tokenExchangeRequest) GetRequestedTokenType() oidc.TokenType { + return r.requestedTokenType +} + +func (r *tokenExchangeRequest) GetExchangeSubject() string { + return r.exchangeSubject +} + +func (r *tokenExchangeRequest) GetExchangeSubjectTokenType() oidc.TokenType { + return r.exchangeSubjectTokenType +} + +func (r *tokenExchangeRequest) GetExchangeSubjectTokenIDOrToken() string { + return r.exchangeSubjectTokenIDOrToken +} + +func (r *tokenExchangeRequest) GetExchangeSubjectTokenClaims() map[string]interface{} { + return r.exchangeSubjectTokenClaims +} + +func (r *tokenExchangeRequest) GetExchangeActor() string { + return r.exchangeActor +} + +func (r *tokenExchangeRequest) GetExchangeActorTokenType() oidc.TokenType { + return r.exchangeActorTokenType +} + +func (r *tokenExchangeRequest) GetExchangeActorTokenIDOrToken() string { + return r.exchangeActorTokenIDOrToken +} + +func (r *tokenExchangeRequest) GetExchangeActorTokenClaims() map[string]interface{} { + return r.exchangeActorTokenClaims +} + +func (r *tokenExchangeRequest) GetSubject() string { + return r.subject +} + +func (r *tokenExchangeRequest) SetCurrentScopes(scopes []string) { + r.scopes = scopes +} + +func (r *tokenExchangeRequest) SetRequestedTokenType(tt oidc.TokenType) { + r.requestedTokenType = tt +} + +func (r *tokenExchangeRequest) SetSubject(subject string) { + r.subject = subject +} + +// TokenExchange handles the OAuth 2.0 token exchange grant ("urn:ietf:params:oauth:grant-type:token-exchange") +func TokenExchange(w http.ResponseWriter, r *http.Request, exchanger Exchanger) { + tokenExchangeReq, clientID, clientSecret, err := ParseTokenExchangeRequest(r, exchanger.Decoder()) + if err != nil { + RequestError(w, r, err) + } + + tokenExchangeRequest, client, err := ValidateTokenExchangeRequest(r.Context(), tokenExchangeReq, clientID, clientSecret, exchanger) + if err != nil { + RequestError(w, r, err) + return + } + resp, err := CreateTokenExchangeResponse(r.Context(), tokenExchangeRequest, client, exchanger) + if err != nil { + RequestError(w, r, err) + return + } + httphelper.MarshalJSON(w, resp) +} + +// ParseTokenExchangeRequest parses the http request into oidc.TokenExchangeRequest +func ParseTokenExchangeRequest(r *http.Request, decoder httphelper.Decoder) (_ *oidc.TokenExchangeRequest, clientID, clientSecret string, err error) { + err = r.ParseForm() + if err != nil { + return nil, "", "", oidc.ErrInvalidRequest().WithDescription("error parsing form").WithParent(err) + } + + request := new(oidc.TokenExchangeRequest) + err = decoder.Decode(request, r.Form) + if err != nil { + return nil, "", "", oidc.ErrInvalidRequest().WithDescription("error decoding form").WithParent(err) + } + + var ok bool + if clientID, clientSecret, ok = r.BasicAuth(); ok { + clientID, err = url.QueryUnescape(clientID) + if err != nil { + return nil, "", "", oidc.ErrInvalidClient().WithDescription("invalid basic auth header").WithParent(err) + } + + clientSecret, err = url.QueryUnescape(clientSecret) + if err != nil { + return nil, "", "", oidc.ErrInvalidClient().WithDescription("invalid basic auth header").WithParent(err) + } + } + + return request, clientID, clientSecret, nil +} + +// ValidateTokenExchangeRequest validates the token exchange request parameters including authorization check of the client, +// subject_token and actor_token +func ValidateTokenExchangeRequest( + ctx context.Context, + oidcTokenExchangeRequest *oidc.TokenExchangeRequest, + clientID, clientSecret string, + exchanger Exchanger, +) (TokenExchangeRequest, Client, error) { + if oidcTokenExchangeRequest.SubjectToken == "" { + return nil, nil, oidc.ErrInvalidRequest().WithDescription("subject_token missing") + } + + if oidcTokenExchangeRequest.SubjectTokenType == "" { + return nil, nil, oidc.ErrInvalidRequest().WithDescription("subject_token_type missing") + } + + storage := exchanger.Storage() + teStorage, ok := storage.(TokenExchangeStorage) + if !ok { + return nil, nil, oidc.ErrUnsupportedGrantType().WithDescription("token_exchange grant not supported") + } + + client, err := AuthorizeTokenExchangeClient(ctx, clientID, clientSecret, exchanger) + if err != nil { + return nil, nil, err + } + + if oidcTokenExchangeRequest.RequestedTokenType != "" && !oidcTokenExchangeRequest.RequestedTokenType.IsValid() { + return nil, nil, oidc.ErrInvalidRequest().WithDescription("requested_token_type is not supported") + } + + if !oidcTokenExchangeRequest.SubjectTokenType.IsValid() { + return nil, nil, oidc.ErrInvalidRequest().WithDescription("subject_token_type is not supported") + } + + if oidcTokenExchangeRequest.ActorTokenType != "" && !oidcTokenExchangeRequest.ActorTokenType.IsValid() { + return nil, nil, oidc.ErrInvalidRequest().WithDescription("actor_token_type is not supported") + } + + exchangeSubjectTokenIDOrToken, exchangeSubject, exchangeSubjectTokenClaims, ok := GetTokenIDAndSubjectFromToken(ctx, exchanger, + oidcTokenExchangeRequest.SubjectToken, oidcTokenExchangeRequest.SubjectTokenType, false) + if !ok { + return nil, nil, oidc.ErrInvalidRequest().WithDescription("subject_token is invalid") + } + + var ( + exchangeActorTokenIDOrToken, exchangeActor string + exchangeActorTokenClaims map[string]interface{} + ) + if oidcTokenExchangeRequest.ActorToken != "" { + exchangeActorTokenIDOrToken, exchangeActor, exchangeActorTokenClaims, ok = GetTokenIDAndSubjectFromToken(ctx, exchanger, + oidcTokenExchangeRequest.ActorToken, oidcTokenExchangeRequest.ActorTokenType, true) + if !ok { + return nil, nil, oidc.ErrInvalidRequest().WithDescription("actor_token is invalid") + } + } + + req := &tokenExchangeRequest{ + exchangeSubjectTokenIDOrToken: exchangeSubjectTokenIDOrToken, + exchangeSubjectTokenType: oidcTokenExchangeRequest.SubjectTokenType, + exchangeSubject: exchangeSubject, + exchangeSubjectTokenClaims: exchangeSubjectTokenClaims, + + exchangeActorTokenIDOrToken: exchangeActorTokenIDOrToken, + exchangeActorTokenType: oidcTokenExchangeRequest.ActorTokenType, + exchangeActor: exchangeActor, + exchangeActorTokenClaims: exchangeActorTokenClaims, + + subject: exchangeSubject, + resource: oidcTokenExchangeRequest.Resource, + audience: oidcTokenExchangeRequest.Audience, + scopes: oidcTokenExchangeRequest.Scopes, + requestedTokenType: oidcTokenExchangeRequest.RequestedTokenType, + clientID: client.GetID(), + authTime: time.Now(), + } + + err = teStorage.ValidateTokenExchangeRequest(ctx, req) + if err != nil { + return nil, nil, err + } + + err = teStorage.CreateTokenExchangeRequest(ctx, req) + if err != nil { + return nil, nil, err + } + + return req, client, nil +} + +func GetTokenIDAndSubjectFromToken( + ctx context.Context, + exchanger Exchanger, + token string, + tokenType oidc.TokenType, + isActor bool, +) (tokenIDOrToken, subject string, claims map[string]interface{}, ok bool) { + switch tokenType { + case oidc.AccessTokenType: + var accessTokenClaims oidc.AccessTokenClaims + tokenIDOrToken, subject, accessTokenClaims, ok = getTokenIDAndClaims(ctx, exchanger, token) + claims = accessTokenClaims.GetClaims() + case oidc.RefreshTokenType: + refreshTokenRequest, err := exchanger.Storage().TokenRequestByRefreshToken(ctx, token) + if err != nil { + break + } + + tokenIDOrToken, subject, ok = token, refreshTokenRequest.GetSubject(), true + case oidc.IDTokenType: + idTokenClaims, err := VerifyIDTokenHint(ctx, token, exchanger.IDTokenHintVerifier()) + if err != nil { + break + } + + tokenIDOrToken, subject, claims, ok = token, idTokenClaims.GetSubject(), idTokenClaims.GetClaims(), true + } + + if !ok { + if verifier, ok := exchanger.Storage().(TokenExchangeTokensVerifierStorage); ok { + var err error + if isActor { + tokenIDOrToken, subject, claims, err = verifier.VerifyExchangeActorToken(ctx, token, tokenType) + } else { + tokenIDOrToken, subject, claims, err = verifier.VerifyExchangeSubjectToken(ctx, token, tokenType) + } + if err != nil { + return "", "", nil, false + } + + return tokenIDOrToken, subject, claims, true + } + + return "", "", nil, false + } + + return tokenIDOrToken, subject, claims, true +} + +// AuthorizeTokenExchangeClient authorizes a client by validating the client_id and client_secret +func AuthorizeTokenExchangeClient(ctx context.Context, clientID, clientSecret string, exchanger Exchanger) (client Client, err error) { + if err := AuthorizeClientIDSecret(ctx, clientID, clientSecret, exchanger.Storage()); err != nil { + return nil, err + } + + client, err = exchanger.Storage().GetClientByClientID(ctx, clientID) + if err != nil { + return nil, oidc.ErrInvalidClient().WithParent(err) + } + + return client, nil +} + +func CreateTokenExchangeResponse( + ctx context.Context, + tokenExchangeRequest TokenExchangeRequest, + client Client, + creator TokenCreator, +) (_ *oidc.TokenExchangeResponse, err error) { + + var ( + token, refreshToken, tokenType string + validity time.Duration + ) + + switch tokenExchangeRequest.GetRequestedTokenType() { + case oidc.AccessTokenType, oidc.RefreshTokenType: + token, refreshToken, validity, err = CreateAccessToken(ctx, tokenExchangeRequest, client.AccessTokenType(), creator, client, "") + if err != nil { + return nil, err + } + + tokenType = oidc.BearerToken + case oidc.IDTokenType: + token, err = CreateIDToken(ctx, creator.Issuer(), tokenExchangeRequest, client.IDTokenLifetime(), "", "", creator.Storage(), creator.Signer(), client) + if err != nil { + return nil, err + } + + // not applicable (see https://datatracker.ietf.org/doc/html/rfc8693#section-2-2-1-2-6) + tokenType = "N_A" + default: + // oidc.JWTTokenType and other custom token types are not supported for issuing. + // In the future it can be considered to have custom tokens generation logic injected via op configuration + // or via expanding Storage interface + oidc.ErrInvalidRequest().WithDescription("requested_token_type is invalid") + } + + exp := uint64(validity.Seconds()) + return &oidc.TokenExchangeResponse{ + AccessToken: token, + IssuedTokenType: tokenExchangeRequest.GetRequestedTokenType(), + TokenType: tokenType, + ExpiresIn: exp, + RefreshToken: refreshToken, + Scopes: tokenExchangeRequest.GetScopes(), + }, nil +} + +func getTokenIDAndClaims(ctx context.Context, userinfoProvider UserinfoProvider, accessToken string) (string, string, oidc.AccessTokenClaims, bool) { + tokenIDSubject, err := userinfoProvider.Crypto().Decrypt(accessToken) + if err == nil { + splitToken := strings.Split(tokenIDSubject, ":") + if len(splitToken) != 2 { + return "", "", nil, false + } + + return splitToken[0], splitToken[1], nil, true + } + accessTokenClaims, err := VerifyAccessToken(ctx, accessToken, userinfoProvider.AccessTokenVerifier()) + if err != nil { + return "", "", nil, false + } + + return accessTokenClaims.GetTokenID(), accessTokenClaims.GetSubject(), accessTokenClaims, true } diff --git a/pkg/op/token_request.go b/pkg/op/token_request.go index 6ccd489..ef96fae 100644 --- a/pkg/op/token_request.go +++ b/pkg/op/token_request.go @@ -21,6 +21,8 @@ type Exchanger interface { GrantTypeTokenExchangeSupported() bool GrantTypeJWTAuthorizationSupported() bool GrantTypeClientCredentialsSupported() bool + AccessTokenVerifier() AccessTokenVerifier + IDTokenHintVerifier() IDTokenHintVerifier } func tokenHandler(exchanger Exchanger) func(w http.ResponseWriter, r *http.Request) {