VYPR
Moderate severityNVD Advisory· Published Jan 11, 2023· Updated Mar 10, 2025

RefreshToken invalidation vulnerability

CVE-2023-22492

Description

ZITADEL is a combination of Auth0 and Keycloak. RefreshTokens is an OAuth 2.0 feature that allows applications to retrieve new access tokens and refresh the user's session without the need for interacting with a UI. RefreshTokens were not invalidated when a user was locked or deactivated. The deactivated or locked user was able to obtain a valid access token only through a refresh token grant. When the locked or deactivated user’s session was already terminated (“logged out”) then it was not possible to create a new session. Renewal of access token through a refresh token grant is limited to the configured amount of time (RefreshTokenExpiration). As a workaround, ensure the RefreshTokenExpiration in the OIDC settings of your instance is set according to your security requirements. This issue has been patched in versions 2.17.3 and 2.16.4.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/zitadel/zitadelGo
>= 2.17.0, < 2.17.32.17.3
github.com/zitadel/zitadelGo
>= 2.0.0, < 2.16.42.16.4

Affected products

1

Patches

2
fc892c52a10c

fix: deactivate refresh tokens of deactivated or locked users

https://github.com/zitadel/zitadelLivio SpringJan 10, 2023via ghsa
4 files changed · +106 6
  • internal/command/user.go+1 1 modified
    @@ -261,7 +261,7 @@ func (c *Commands) addUserToken(ctx context.Context, userWriteModel *UserWriteMo
     	if err != nil {
     		return nil, nil, err
     	}
    -	if !isUserStateExists(userWriteModel.UserState) {
    +	if userWriteModel.UserState != domain.UserStateActive {
     		return nil, nil, errors.ThrowNotFound(nil, "COMMAND-1d6Gg", "Errors.User.NotFound")
     	}
     
    
  • internal/command/user_human_refresh_token_model.go+8 1 modified
    @@ -18,6 +18,7 @@ type HumanRefreshTokenWriteModel struct {
     	UserState      domain.UserState
     	IdleExpiration time.Time
     	Expiration     time.Time
    +	UserAgentID    string
     }
     
     func NewHumanRefreshTokenWriteModel(userID, resourceOwner, tokenID string) *HumanRefreshTokenWriteModel {
    @@ -48,6 +49,8 @@ func (wm *HumanRefreshTokenWriteModel) AppendEvents(events ...eventstore.Event)
     				continue
     			}
     			wm.WriteModel.AppendEvents(e)
    +		default:
    +			wm.WriteModel.AppendEvents(e)
     		}
     	}
     }
    @@ -61,14 +64,18 @@ func (wm *HumanRefreshTokenWriteModel) Reduce() error {
     			wm.IdleExpiration = e.CreationDate().Add(e.IdleExpiration)
     			wm.Expiration = e.CreationDate().Add(e.Expiration)
     			wm.UserState = domain.UserStateActive
    +			wm.UserAgentID = e.UserAgentID
     		case *user.HumanRefreshTokenRenewedEvent:
     			if wm.UserState == domain.UserStateActive {
     				wm.RefreshToken = e.RefreshToken
     			}
     			wm.RefreshToken = e.RefreshToken
     			wm.IdleExpiration = e.CreationDate().Add(e.IdleExpiration)
    +		case *user.HumanSignedOutEvent:
    +			if wm.UserAgentID == e.UserAgentID {
    +				wm.UserState = domain.UserStateDeleted
    +			}
     		case *user.HumanRefreshTokenRemovedEvent,
    -			*user.HumanSignedOutEvent,
     			*user.UserLockedEvent,
     			*user.UserDeactivatedEvent,
     			*user.UserRemovedEvent:
    
  • internal/command/user_human_refresh_token_test.go+89 2 modified
    @@ -64,10 +64,16 @@ func TestCommands_AddAccessAndRefreshToken(t *testing.T) {
     			},
     		},
     		{
    -			name: "add refresh token, user inactive, error",
    +			name: "add refresh token, user deactivated, error",
     			fields: fields{
     				eventstore: eventstoreExpect(t,
    -					expectFilter(),
    +					expectFilter(
    +						eventFromEventPusher(
    +							user.NewUserDeactivatedEvent(context.Background(),
    +								&user.NewAggregate("userID", "orgID").Aggregate,
    +							),
    +						),
    +					),
     				),
     				idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "refreshTokenID1"),
     			},
    @@ -912,6 +918,87 @@ func TestCommands_renewRefreshToken(t *testing.T) {
     				err: caos_errs.IsErrorInvalidArgument,
     			},
     		},
    +		{
    +			name: "user deactivated, error",
    +			fields: fields{
    +				eventstore: eventstoreExpect(t,
    +					expectFilter(
    +						eventFromEventPusherWithCreationDateNow(user.NewHumanRefreshTokenAddedEvent(
    +							context.Background(),
    +							&user.NewAggregate("userID", "orgID").Aggregate,
    +							"tokenID",
    +							"applicationID",
    +							"userAgentID",
    +							"de",
    +							[]string{"clientID1"},
    +							[]string{oidc.ScopeOpenID, oidc.ScopeProfile, oidc.ScopeEmail, oidc.ScopeOfflineAccess},
    +							[]string{"password"},
    +							time.Now(),
    +							1*time.Hour,
    +							24*time.Hour,
    +						)),
    +						eventFromEventPusher(
    +							user.NewUserDeactivatedEvent(
    +								context.Background(),
    +								&user.NewAggregate("userID", "orgID").Aggregate,
    +							),
    +						),
    +					),
    +				),
    +				keyAlgorithm: refreshTokenEncryptionAlgorithm(gomock.NewController(t)),
    +			},
    +			args: args{
    +				ctx:            context.Background(),
    +				userID:         "userID",
    +				orgID:          "orgID",
    +				refreshToken:   base64.RawURLEncoding.EncodeToString([]byte("userID:tokenID:tokenID")),
    +				idleExpiration: 1 * time.Hour,
    +			},
    +			res: res{
    +				err: caos_errs.IsErrorInvalidArgument,
    +			},
    +		},
    +		{
    +			name: "user signedout, error",
    +			fields: fields{
    +				eventstore: eventstoreExpect(t,
    +					expectFilter(
    +						eventFromEventPusherWithCreationDateNow(user.NewHumanRefreshTokenAddedEvent(
    +							context.Background(),
    +							&user.NewAggregate("userID", "orgID").Aggregate,
    +							"tokenID",
    +							"applicationID",
    +							"userAgentID",
    +							"de",
    +							[]string{"clientID1"},
    +							[]string{oidc.ScopeOpenID, oidc.ScopeProfile, oidc.ScopeEmail, oidc.ScopeOfflineAccess},
    +							[]string{"password"},
    +							time.Now(),
    +							1*time.Hour,
    +							24*time.Hour,
    +						)),
    +						eventFromEventPusher(
    +							user.NewHumanSignedOutEvent(
    +								context.Background(),
    +								&user.NewAggregate("userID", "orgID").Aggregate,
    +								"userAgentID",
    +							),
    +						),
    +					),
    +				),
    +				keyAlgorithm: refreshTokenEncryptionAlgorithm(gomock.NewController(t)),
    +			},
    +			args: args{
    +				ctx:            context.Background(),
    +				userID:         "userID",
    +				orgID:          "orgID",
    +				refreshToken:   base64.RawURLEncoding.EncodeToString([]byte("userID:tokenID:tokenID")),
    +				idleExpiration: 1 * time.Hour,
    +			},
    +			res: res{
    +				err: caos_errs.IsErrorInvalidArgument,
    +			},
    +		},
     		{
     			name: "token renewed, ok",
     			fields: fields{
    
  • internal/repository/user/human.go+8 2 modified
    @@ -396,7 +396,13 @@ func NewHumanSignedOutEvent(
     }
     
     func HumanSignedOutEventMapper(event *repository.Event) (eventstore.Event, error) {
    -	return &HumanSignedOutEvent{
    +	signedOut := &HumanSignedOutEvent{
     		BaseEvent: *eventstore.BaseEventFromRepo(event),
    -	}, nil
    +	}
    +	err := json.Unmarshal(event.Data, signedOut)
    +	if err != nil {
    +		return nil, errors.ThrowInternal(err, "USER-WFS3g", "unable to unmarshal human signed out")
    +	}
    +
    +	return signedOut, nil
     }
    
301e22c4956e

fix: deactivate refresh tokens of deactivated or locked users

https://github.com/zitadel/zitadelLivio SpringJan 10, 2023via ghsa
4 files changed · +106 6
  • internal/command/user.go+1 1 modified
    @@ -261,7 +261,7 @@ func (c *Commands) addUserToken(ctx context.Context, userWriteModel *UserWriteMo
     	if err != nil {
     		return nil, nil, err
     	}
    -	if !isUserStateExists(userWriteModel.UserState) {
    +	if userWriteModel.UserState != domain.UserStateActive {
     		return nil, nil, errors.ThrowNotFound(nil, "COMMAND-1d6Gg", "Errors.User.NotFound")
     	}
     
    
  • internal/command/user_human_refresh_token_model.go+8 1 modified
    @@ -18,6 +18,7 @@ type HumanRefreshTokenWriteModel struct {
     	UserState      domain.UserState
     	IdleExpiration time.Time
     	Expiration     time.Time
    +	UserAgentID    string
     }
     
     func NewHumanRefreshTokenWriteModel(userID, resourceOwner, tokenID string) *HumanRefreshTokenWriteModel {
    @@ -48,6 +49,8 @@ func (wm *HumanRefreshTokenWriteModel) AppendEvents(events ...eventstore.Event)
     				continue
     			}
     			wm.WriteModel.AppendEvents(e)
    +		default:
    +			wm.WriteModel.AppendEvents(e)
     		}
     	}
     }
    @@ -61,14 +64,18 @@ func (wm *HumanRefreshTokenWriteModel) Reduce() error {
     			wm.IdleExpiration = e.CreationDate().Add(e.IdleExpiration)
     			wm.Expiration = e.CreationDate().Add(e.Expiration)
     			wm.UserState = domain.UserStateActive
    +			wm.UserAgentID = e.UserAgentID
     		case *user.HumanRefreshTokenRenewedEvent:
     			if wm.UserState == domain.UserStateActive {
     				wm.RefreshToken = e.RefreshToken
     			}
     			wm.RefreshToken = e.RefreshToken
     			wm.IdleExpiration = e.CreationDate().Add(e.IdleExpiration)
    +		case *user.HumanSignedOutEvent:
    +			if wm.UserAgentID == e.UserAgentID {
    +				wm.UserState = domain.UserStateDeleted
    +			}
     		case *user.HumanRefreshTokenRemovedEvent,
    -			*user.HumanSignedOutEvent,
     			*user.UserLockedEvent,
     			*user.UserDeactivatedEvent,
     			*user.UserRemovedEvent:
    
  • internal/command/user_human_refresh_token_test.go+89 2 modified
    @@ -64,10 +64,16 @@ func TestCommands_AddAccessAndRefreshToken(t *testing.T) {
     			},
     		},
     		{
    -			name: "add refresh token, user inactive, error",
    +			name: "add refresh token, user deactivated, error",
     			fields: fields{
     				eventstore: eventstoreExpect(t,
    -					expectFilter(),
    +					expectFilter(
    +						eventFromEventPusher(
    +							user.NewUserDeactivatedEvent(context.Background(),
    +								&user.NewAggregate("userID", "orgID").Aggregate,
    +							),
    +						),
    +					),
     				),
     				idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "refreshTokenID1"),
     			},
    @@ -912,6 +918,87 @@ func TestCommands_renewRefreshToken(t *testing.T) {
     				err: caos_errs.IsErrorInvalidArgument,
     			},
     		},
    +		{
    +			name: "user deactivated, error",
    +			fields: fields{
    +				eventstore: eventstoreExpect(t,
    +					expectFilter(
    +						eventFromEventPusherWithCreationDateNow(user.NewHumanRefreshTokenAddedEvent(
    +							context.Background(),
    +							&user.NewAggregate("userID", "orgID").Aggregate,
    +							"tokenID",
    +							"applicationID",
    +							"userAgentID",
    +							"de",
    +							[]string{"clientID1"},
    +							[]string{oidc.ScopeOpenID, oidc.ScopeProfile, oidc.ScopeEmail, oidc.ScopeOfflineAccess},
    +							[]string{"password"},
    +							time.Now(),
    +							1*time.Hour,
    +							24*time.Hour,
    +						)),
    +						eventFromEventPusher(
    +							user.NewUserDeactivatedEvent(
    +								context.Background(),
    +								&user.NewAggregate("userID", "orgID").Aggregate,
    +							),
    +						),
    +					),
    +				),
    +				keyAlgorithm: refreshTokenEncryptionAlgorithm(gomock.NewController(t)),
    +			},
    +			args: args{
    +				ctx:            context.Background(),
    +				userID:         "userID",
    +				orgID:          "orgID",
    +				refreshToken:   base64.RawURLEncoding.EncodeToString([]byte("userID:tokenID:tokenID")),
    +				idleExpiration: 1 * time.Hour,
    +			},
    +			res: res{
    +				err: caos_errs.IsErrorInvalidArgument,
    +			},
    +		},
    +		{
    +			name: "user signedout, error",
    +			fields: fields{
    +				eventstore: eventstoreExpect(t,
    +					expectFilter(
    +						eventFromEventPusherWithCreationDateNow(user.NewHumanRefreshTokenAddedEvent(
    +							context.Background(),
    +							&user.NewAggregate("userID", "orgID").Aggregate,
    +							"tokenID",
    +							"applicationID",
    +							"userAgentID",
    +							"de",
    +							[]string{"clientID1"},
    +							[]string{oidc.ScopeOpenID, oidc.ScopeProfile, oidc.ScopeEmail, oidc.ScopeOfflineAccess},
    +							[]string{"password"},
    +							time.Now(),
    +							1*time.Hour,
    +							24*time.Hour,
    +						)),
    +						eventFromEventPusher(
    +							user.NewHumanSignedOutEvent(
    +								context.Background(),
    +								&user.NewAggregate("userID", "orgID").Aggregate,
    +								"userAgentID",
    +							),
    +						),
    +					),
    +				),
    +				keyAlgorithm: refreshTokenEncryptionAlgorithm(gomock.NewController(t)),
    +			},
    +			args: args{
    +				ctx:            context.Background(),
    +				userID:         "userID",
    +				orgID:          "orgID",
    +				refreshToken:   base64.RawURLEncoding.EncodeToString([]byte("userID:tokenID:tokenID")),
    +				idleExpiration: 1 * time.Hour,
    +			},
    +			res: res{
    +				err: caos_errs.IsErrorInvalidArgument,
    +			},
    +		},
     		{
     			name: "token renewed, ok",
     			fields: fields{
    
  • internal/repository/user/human.go+8 2 modified
    @@ -396,7 +396,13 @@ func NewHumanSignedOutEvent(
     }
     
     func HumanSignedOutEventMapper(event *repository.Event) (eventstore.Event, error) {
    -	return &HumanSignedOutEvent{
    +	signedOut := &HumanSignedOutEvent{
     		BaseEvent: *eventstore.BaseEventFromRepo(event),
    -	}, nil
    +	}
    +	err := json.Unmarshal(event.Data, signedOut)
    +	if err != nil {
    +		return nil, errors.ThrowInternal(err, "USER-WFS3g", "unable to unmarshal human signed out")
    +	}
    +
    +	return signedOut, nil
     }
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.