zitadel/internal/command/auth_request_model.go
Stefan Benz 0e10ed0e0b
fix: SAML and OIDC issuer (in proxied use cases) (#9638)
# Which Problems Are Solved

When using implicit flow through the session API and a login UI on a
custom domain (proxy), the tokens were signed by the API domain of the
instance, rather than the public (proxy) domain.
The SAML response had the same issue. Additionally, the saml library had
an issue and lost the issuer context. This prevented also a successful
login through the hosted login UI.

# How the Problems Are Solved

- The issuer of the SAML and Auth request is persisted to provide the
information when signing the responses and tokens.
- The SAML library is updated to the latest version.

# Additional Changes

None

# Additional Context

None
2025-03-26 17:08:13 +00:00

117 lines
3.7 KiB
Go

package command
import (
"context"
"time"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/authrequest"
"github.com/zitadel/zitadel/internal/zerrors"
)
type AuthRequestWriteModel struct {
eventstore.WriteModel
aggregate *eventstore.Aggregate
LoginClient string
ClientID string
RedirectURI string
State string
Nonce string
Scope []string
Audience []string
ResponseType domain.OIDCResponseType
ResponseMode domain.OIDCResponseMode
CodeChallenge *domain.OIDCCodeChallenge
Prompt []domain.Prompt
UILocales []string
MaxAge *time.Duration
LoginHint *string
HintUserID *string
SessionID string
UserID string
AuthTime time.Time
AuthMethods []domain.UserAuthMethodType
AuthRequestState domain.AuthRequestState
NeedRefreshToken bool
Issuer string
}
func NewAuthRequestWriteModel(ctx context.Context, id string) *AuthRequestWriteModel {
return &AuthRequestWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: id,
},
aggregate: &authrequest.NewAggregate(id, authz.GetInstance(ctx).InstanceID()).Aggregate,
}
}
func (m *AuthRequestWriteModel) Reduce() error {
for _, event := range m.Events {
switch e := event.(type) {
case *authrequest.AddedEvent:
m.LoginClient = e.LoginClient
m.ClientID = e.ClientID
m.RedirectURI = e.RedirectURI
m.State = e.State
m.Nonce = e.Nonce
m.Scope = e.Scope
m.Audience = e.Audience
m.ResponseType = e.ResponseType
m.ResponseMode = e.ResponseMode
m.CodeChallenge = e.CodeChallenge
m.Prompt = e.Prompt
m.UILocales = e.UILocales
m.MaxAge = e.MaxAge
m.LoginHint = e.LoginHint
m.HintUserID = e.HintUserID
m.AuthRequestState = domain.AuthRequestStateAdded
m.NeedRefreshToken = e.NeedRefreshToken
m.Issuer = e.Issuer
case *authrequest.SessionLinkedEvent:
m.SessionID = e.SessionID
m.UserID = e.UserID
m.AuthTime = e.AuthTime
m.AuthMethods = e.AuthMethods
case *authrequest.CodeAddedEvent:
m.AuthRequestState = domain.AuthRequestStateCodeAdded
case *authrequest.FailedEvent:
m.AuthRequestState = domain.AuthRequestStateFailed
case *authrequest.CodeExchangedEvent:
m.AuthRequestState = domain.AuthRequestStateCodeExchanged
case *authrequest.SucceededEvent:
m.AuthRequestState = domain.AuthRequestStateSucceeded
}
}
return m.WriteModel.Reduce()
}
func (m *AuthRequestWriteModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
AddQuery().
AggregateTypes(authrequest.AggregateType).
AggregateIDs(m.AggregateID).
Builder()
}
// CheckAuthenticated checks that the auth request exists, a session must have been linked
// and in case of a Code Flow the code must have been exchanged
func (m *AuthRequestWriteModel) CheckAuthenticated() error {
if m.SessionID == "" {
return zerrors.ThrowPreconditionFailed(nil, "AUTHR-SF2r2", "Errors.AuthRequest.NotAuthenticated")
}
// in case of OIDC Code Flow, the code must have been exchanged
if m.ResponseType == domain.OIDCResponseTypeCode && m.AuthRequestState == domain.AuthRequestStateCodeExchanged {
return nil
}
// in case of OIDC Implicit Flow, check that the requests exists, but has not succeeded yet
if (m.ResponseType == domain.OIDCResponseTypeIDToken || m.ResponseType == domain.OIDCResponseTypeIDTokenToken) &&
m.AuthRequestState == domain.AuthRequestStateAdded {
return nil
}
return zerrors.ThrowPreconditionFailed(nil, "AUTHR-sajk3", "Errors.AuthRequest.NotAuthenticated")
}