mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-04 15:35:10 +00:00
fix(oidc): do not return access token for response type id_token (#8777)
# Which Problems Are Solved Do not return an access token for implicit flow from v1 login, if the `response_type` is `id_token` # How the Problems Are Solved Do not create the access token event if if the `response_type` is `id_token`. # Additional Changes Token endpoint calls without auth request, such as machine users, token exchange and refresh token, do not have a `response_type`. For such calls the `OIDCResponseTypeUnspecified` enum is added at a `-1` offset, in order not to break existing client configs. # Additional Context - https://discord.com/channels/927474939156643850/1294001717725237298 - Fixes https://github.com/zitadel/zitadel/issues/8776
This commit is contained in:
parent
69e9926bcc
commit
778b4041ca
@ -600,6 +600,7 @@ func (s *Server) authResponseToken(authReq *AuthRequest, authorizer op.Authorize
|
||||
nil,
|
||||
slices.Contains(scope, oidc.ScopeOfflineAccess),
|
||||
authReq.SessionID,
|
||||
authReq.oidc().ResponseType,
|
||||
)
|
||||
if err != nil {
|
||||
op.AuthRequestError(w, r, authReq, err, authorizer)
|
||||
|
@ -47,6 +47,7 @@ func (s *Server) ClientCredentialsExchange(ctx context.Context, r *op.ClientRequ
|
||||
nil,
|
||||
false,
|
||||
"",
|
||||
domain.OIDCResponseTypeUnspecified,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -87,6 +87,7 @@ func (s *Server) codeExchangeV1(ctx context.Context, client *Client, req *oidc.A
|
||||
nil,
|
||||
slices.Contains(scope, oidc.ScopeOfflineAccess),
|
||||
authReq.SessionID,
|
||||
authReq.oidc().ResponseType,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -300,6 +300,7 @@ func (s *Server) createExchangeAccessToken(
|
||||
actor,
|
||||
slices.Contains(scope, oidc.ScopeOfflineAccess),
|
||||
"",
|
||||
domain.OIDCResponseTypeUnspecified,
|
||||
)
|
||||
if err != nil {
|
||||
return "", "", "", 0, err
|
||||
@ -346,6 +347,7 @@ func (s *Server) createExchangeJWT(
|
||||
actor,
|
||||
slices.Contains(scope, oidc.ScopeOfflineAccess),
|
||||
"",
|
||||
domain.OIDCResponseTypeUnspecified,
|
||||
)
|
||||
accessToken, err = s.createJWT(ctx, client, session, getUserInfo, roleAssertion, getSigner)
|
||||
if err != nil {
|
||||
|
@ -57,6 +57,7 @@ func (s *Server) JWTProfile(ctx context.Context, r *op.Request[oidc.JWTProfileGr
|
||||
nil,
|
||||
false,
|
||||
"",
|
||||
domain.OIDCResponseTypeUnspecified,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -69,6 +69,7 @@ func (s *Server) refreshTokenV1(ctx context.Context, client *Client, r *op.Clien
|
||||
refreshToken.Actor,
|
||||
true,
|
||||
"",
|
||||
domain.OIDCResponseTypeUnspecified,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -147,6 +147,7 @@ func (c *Commands) CreateOIDCSession(ctx context.Context,
|
||||
actor *domain.TokenActor,
|
||||
needRefreshToken bool,
|
||||
sessionID string,
|
||||
responseType domain.OIDCResponseType,
|
||||
) (session *OIDCSession, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
@ -164,8 +165,10 @@ func (c *Commands) CreateOIDCSession(ctx context.Context,
|
||||
|
||||
cmd.AddSession(ctx, userID, resourceOwner, sessionID, clientID, audience, scope, authMethods, authTime, nonce, preferredLanguage, userAgent)
|
||||
cmd.RegisterLogout(ctx, sessionID, userID, clientID, backChannelLogoutURI)
|
||||
if err = cmd.AddAccessToken(ctx, scope, userID, resourceOwner, reason, actor); err != nil {
|
||||
return nil, err
|
||||
if responseType != domain.OIDCResponseTypeIDToken {
|
||||
if err = cmd.AddAccessToken(ctx, scope, userID, resourceOwner, reason, actor); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if needRefreshToken {
|
||||
if err = cmd.AddRefreshToken(ctx, userID); err != nil {
|
||||
|
@ -749,6 +749,7 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
|
||||
actor *domain.TokenActor
|
||||
needRefreshToken bool
|
||||
sessionID string
|
||||
responseType domain.OIDCResponseType
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@ -788,6 +789,7 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
|
||||
Issuer: "foo.com",
|
||||
},
|
||||
needRefreshToken: false,
|
||||
responseType: domain.OIDCResponseTypeUnspecified,
|
||||
},
|
||||
wantErr: io.ErrClosedPipe,
|
||||
},
|
||||
@ -844,6 +846,7 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
|
||||
Issuer: "foo.com",
|
||||
},
|
||||
needRefreshToken: false,
|
||||
responseType: domain.OIDCResponseTypeUnspecified,
|
||||
},
|
||||
wantErr: zerrors.ThrowPreconditionFailed(nil, "OIDCS-kj3g2", "Errors.User.NotActive"),
|
||||
},
|
||||
@ -918,6 +921,7 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
|
||||
Issuer: "foo.com",
|
||||
},
|
||||
needRefreshToken: false,
|
||||
responseType: domain.OIDCResponseTypeUnspecified,
|
||||
},
|
||||
want: &OIDCSession{
|
||||
TokenID: "V2_oidcSessionID-at_accessTokenID",
|
||||
@ -943,6 +947,87 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ID token only",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
user.NewHumanAddedEvent(
|
||||
context.Background(),
|
||||
&user.NewAggregate("userID", "org1").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"nickname",
|
||||
"displayname",
|
||||
language.Afrikaans,
|
||||
domain.GenderUnspecified,
|
||||
"email",
|
||||
false,
|
||||
),
|
||||
),
|
||||
expectFilter(), // token lifetime
|
||||
expectPush(
|
||||
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||
"userID", "org1", "", "clientID", []string{"audience"}, []string{"openid", "offline_access"},
|
||||
[]domain.UserAuthMethodType{domain.UserAuthMethodTypePassword}, testNow, "nonce", &language.Afrikaans,
|
||||
&domain.UserAgent{
|
||||
FingerprintID: gu.Ptr("fp1"),
|
||||
IP: net.ParseIP("1.2.3.4"),
|
||||
Description: gu.Ptr("firefox"),
|
||||
Header: http.Header{"foo": []string{"bar"}},
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
idGenerator: mock.NewIDGeneratorExpectIDs(t, "oidcSessionID"),
|
||||
defaultAccessTokenLifetime: time.Hour,
|
||||
defaultRefreshTokenLifetime: 7 * 24 * time.Hour,
|
||||
defaultRefreshTokenIdleLifetime: 24 * time.Hour,
|
||||
keyAlgorithm: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args: args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instanceID"),
|
||||
userID: "userID",
|
||||
resourceOwner: "org1",
|
||||
clientID: "clientID",
|
||||
audience: []string{"audience"},
|
||||
scope: []string{"openid", "offline_access"},
|
||||
authMethods: []domain.UserAuthMethodType{domain.UserAuthMethodTypePassword},
|
||||
authTime: testNow,
|
||||
nonce: "nonce",
|
||||
preferredLanguage: &language.Afrikaans,
|
||||
userAgent: &domain.UserAgent{
|
||||
FingerprintID: gu.Ptr("fp1"),
|
||||
IP: net.ParseIP("1.2.3.4"),
|
||||
Description: gu.Ptr("firefox"),
|
||||
Header: http.Header{"foo": []string{"bar"}},
|
||||
},
|
||||
reason: domain.TokenReasonAuthRequest,
|
||||
actor: &domain.TokenActor{
|
||||
UserID: "user2",
|
||||
Issuer: "foo.com",
|
||||
},
|
||||
needRefreshToken: false,
|
||||
responseType: domain.OIDCResponseTypeIDToken,
|
||||
},
|
||||
want: &OIDCSession{
|
||||
ClientID: "clientID",
|
||||
UserID: "userID",
|
||||
Audience: []string{"audience"},
|
||||
Scope: []string{"openid", "offline_access"},
|
||||
AuthMethods: []domain.UserAuthMethodType{domain.UserAuthMethodTypePassword},
|
||||
AuthTime: testNow,
|
||||
Nonce: "nonce",
|
||||
PreferredLanguage: &language.Afrikaans,
|
||||
UserAgent: &domain.UserAgent{
|
||||
FingerprintID: gu.Ptr("fp1"),
|
||||
IP: net.ParseIP("1.2.3.4"),
|
||||
Description: gu.Ptr("firefox"),
|
||||
Header: http.Header{"foo": []string{"bar"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "disable user token event",
|
||||
fields: fields{
|
||||
@ -1018,6 +1103,7 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
|
||||
Issuer: "foo.com",
|
||||
},
|
||||
needRefreshToken: false,
|
||||
responseType: domain.OIDCResponseTypeUnspecified,
|
||||
},
|
||||
want: &OIDCSession{
|
||||
TokenID: "V2_oidcSessionID-at_accessTokenID",
|
||||
@ -1115,6 +1201,7 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
|
||||
Issuer: "foo.com",
|
||||
},
|
||||
needRefreshToken: true,
|
||||
responseType: domain.OIDCResponseTypeUnspecified,
|
||||
},
|
||||
want: &OIDCSession{
|
||||
TokenID: "V2_oidcSessionID-at_accessTokenID",
|
||||
@ -1213,6 +1300,7 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
|
||||
},
|
||||
needRefreshToken: false,
|
||||
sessionID: "sessionID",
|
||||
responseType: domain.OIDCResponseTypeUnspecified,
|
||||
},
|
||||
want: &OIDCSession{
|
||||
TokenID: "V2_oidcSessionID-at_accessTokenID",
|
||||
@ -1594,6 +1682,7 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
|
||||
Issuer: "foo.com",
|
||||
},
|
||||
needRefreshToken: false,
|
||||
responseType: domain.OIDCResponseTypeUnspecified,
|
||||
},
|
||||
wantErr: zerrors.ThrowPermissionDenied(nil, "test", "test"),
|
||||
},
|
||||
@ -1675,6 +1764,7 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
|
||||
Issuer: "foo.com",
|
||||
},
|
||||
needRefreshToken: false,
|
||||
responseType: domain.OIDCResponseTypeUnspecified,
|
||||
},
|
||||
want: &OIDCSession{
|
||||
TokenID: "V2_oidcSessionID-at_accessTokenID",
|
||||
@ -1729,6 +1819,7 @@ func TestCommands_CreateOIDCSession(t *testing.T) {
|
||||
tt.args.actor,
|
||||
tt.args.needRefreshToken,
|
||||
tt.args.sessionID,
|
||||
tt.args.responseType,
|
||||
)
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
if got != nil {
|
||||
|
@ -79,7 +79,8 @@ const (
|
||||
type OIDCResponseType int32
|
||||
|
||||
const (
|
||||
OIDCResponseTypeCode OIDCResponseType = iota
|
||||
OIDCResponseTypeUnspecified OIDCResponseType = iota - 1 // Negative offset not to break existing configs.
|
||||
OIDCResponseTypeCode
|
||||
OIDCResponseTypeIDToken
|
||||
OIDCResponseTypeIDTokenToken
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user