diff --git a/internal/api/oidc/auth_request.go b/internal/api/oidc/auth_request.go index 173585ff13..138035af58 100644 --- a/internal/api/oidc/auth_request.go +++ b/internal/api/oidc/auth_request.go @@ -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) diff --git a/internal/api/oidc/token_client_credentials.go b/internal/api/oidc/token_client_credentials.go index 0b836a03cc..5871e2f130 100644 --- a/internal/api/oidc/token_client_credentials.go +++ b/internal/api/oidc/token_client_credentials.go @@ -47,6 +47,7 @@ func (s *Server) ClientCredentialsExchange(ctx context.Context, r *op.ClientRequ nil, false, "", + domain.OIDCResponseTypeUnspecified, ) if err != nil { return nil, err diff --git a/internal/api/oidc/token_code.go b/internal/api/oidc/token_code.go index 3aa53e629e..ee3585be69 100644 --- a/internal/api/oidc/token_code.go +++ b/internal/api/oidc/token_code.go @@ -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 diff --git a/internal/api/oidc/token_exchange.go b/internal/api/oidc/token_exchange.go index 63a594b940..3887ff7c51 100644 --- a/internal/api/oidc/token_exchange.go +++ b/internal/api/oidc/token_exchange.go @@ -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 { diff --git a/internal/api/oidc/token_jwt_profile.go b/internal/api/oidc/token_jwt_profile.go index 4717d29f9c..d60e6a283e 100644 --- a/internal/api/oidc/token_jwt_profile.go +++ b/internal/api/oidc/token_jwt_profile.go @@ -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 diff --git a/internal/api/oidc/token_refresh.go b/internal/api/oidc/token_refresh.go index f0d92fa521..76e85a4888 100644 --- a/internal/api/oidc/token_refresh.go +++ b/internal/api/oidc/token_refresh.go @@ -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 diff --git a/internal/command/oidc_session.go b/internal/command/oidc_session.go index c2922f5194..bea17986ea 100644 --- a/internal/command/oidc_session.go +++ b/internal/command/oidc_session.go @@ -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 { diff --git a/internal/command/oidc_session_test.go b/internal/command/oidc_session_test.go index 43ca622a29..18a115eb00 100644 --- a/internal/command/oidc_session_test.go +++ b/internal/command/oidc_session_test.go @@ -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 { diff --git a/internal/domain/application_oidc.go b/internal/domain/application_oidc.go index 1ffb61f538..617b889561 100644 --- a/internal/domain/application_oidc.go +++ b/internal/domain/application_oidc.go @@ -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 )