zitadel/internal/api/oidc/token_code.go
Livio Spring fb95f4a8a2
fix(oidc): only return state in access token response for implicit flow (#8108)
# Which Problems Are Solved

Introduced with #7822 the access token response incorrectly returned the
`state` parameter.

# How the Problems Are Solved

The `state` will only be returned for access token responses in an
implicit_flow.

# Additional Changes

None.

# Additional Context

- relates to #7822 
- relates to
https://github.com/zitadel/oidc/issues/446#issuecomment-2144999644
- backport to 2.53.x

---------

Co-authored-by: Tim Möhlmann <tim+github@zitadel.com>
2024-06-12 11:53:56 +00:00

135 lines
4.2 KiB
Go

package oidc
import (
"context"
"slices"
"strings"
"github.com/zitadel/oidc/v3/pkg/oidc"
"github.com/zitadel/oidc/v3/pkg/op"
"github.com/zitadel/zitadel/internal/api/http/middleware"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
"github.com/zitadel/zitadel/internal/zerrors"
)
func (s *Server) CodeExchange(ctx context.Context, r *op.ClientRequest[oidc.AccessTokenRequest]) (_ *op.Response, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() {
span.EndWithError(err)
err = oidcError(err)
}()
client, ok := r.Client.(*Client)
if !ok {
return nil, zerrors.ThrowInternal(nil, "OIDC-Ae2ph", "Error.Internal")
}
plainCode, err := s.decryptCode(ctx, r.Data.Code)
if err != nil {
return nil, zerrors.ThrowInvalidArgument(err, "OIDC-ahLi2", "Errors.User.Code.Invalid")
}
var (
session *command.OIDCSession
)
if strings.HasPrefix(plainCode, command.IDPrefixV2) {
session, _, err = s.command.CreateOIDCSessionFromAuthRequest(
setContextUserSystem(ctx),
plainCode,
codeExchangeComplianceChecker(client, r.Data),
slices.Contains(client.GrantTypes(), oidc.GrantTypeRefreshToken),
)
} else {
session, err = s.codeExchangeV1(ctx, client, r.Data, r.Data.Code)
}
if err != nil {
return nil, err
}
return response(s.accessTokenResponseFromSession(ctx, client, session, "", client.client.ProjectID, client.client.ProjectRoleAssertion, client.client.AccessTokenRoleAssertion, client.client.IDTokenRoleAssertion, client.client.IDTokenUserinfoAssertion))
}
// codeExchangeV1 creates a v2 token from a v1 auth request.
func (s *Server) codeExchangeV1(ctx context.Context, client *Client, req *oidc.AccessTokenRequest, code string) (session *command.OIDCSession, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
authReq, err := s.getAuthRequestV1ByCode(ctx, code)
if err != nil {
return nil, err
}
if challenge := authReq.GetCodeChallenge(); challenge != nil || client.AuthMethod() == oidc.AuthMethodNone {
if err = op.AuthorizeCodeChallenge(req.CodeVerifier, challenge); err != nil {
return nil, err
}
}
if req.RedirectURI != authReq.GetRedirectURI() {
return nil, oidc.ErrInvalidGrant().WithDescription("redirect_uri does not correspond")
}
scope := authReq.GetScopes()
session, err = s.command.CreateOIDCSession(ctx,
authReq.UserID,
authReq.UserOrgID,
client.client.ClientID,
scope,
authReq.Audience,
authReq.AuthMethods(),
authReq.AuthTime,
authReq.GetNonce(),
authReq.PreferredLanguage,
authReq.BrowserInfo.ToUserAgent(),
domain.TokenReasonAuthRequest,
nil,
slices.Contains(scope, oidc.ScopeOfflineAccess),
)
if err != nil {
return nil, err
}
return session, s.repo.DeleteAuthRequest(ctx, authReq.ID)
}
// getAuthRequestV1ByCode finds the v1 auth request by code.
// code needs to be the encrypted version of the ID,
// this is required by the underlying repo.
func (s *Server) getAuthRequestV1ByCode(ctx context.Context, code string) (*AuthRequest, error) {
authReq, err := s.repo.AuthRequestByCode(ctx, code)
if err != nil {
return nil, err
}
return AuthRequestFromBusiness(authReq)
}
func (s *Server) getAuthRequestV1ByID(ctx context.Context, id string) (*AuthRequest, error) {
userAgentID, ok := middleware.UserAgentIDFromCtx(ctx)
if !ok {
return nil, zerrors.ThrowPreconditionFailed(nil, "OIDC-TiTu7", "no user agent id")
}
resp, err := s.repo.AuthRequestByIDCheckLoggedIn(ctx, id, userAgentID)
if err != nil {
return nil, err
}
return AuthRequestFromBusiness(resp)
}
func codeExchangeComplianceChecker(client *Client, req *oidc.AccessTokenRequest) command.AuthRequestComplianceChecker {
return func(ctx context.Context, authReq *command.AuthRequestWriteModel) error {
if authReq.CodeChallenge != nil || client.AuthMethod() == oidc.AuthMethodNone {
err := op.AuthorizeCodeChallenge(req.CodeVerifier, CodeChallengeToOIDC(authReq.CodeChallenge))
if err != nil {
return err
}
}
if req.RedirectURI != authReq.RedirectURI {
return oidc.ErrInvalidGrant().WithDescription("redirect_uri does not correspond")
}
if err := authReq.CheckAuthenticated(); err != nil {
return err
}
return nil
}
}