//go:build integration package oidc_test import ( "context" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/zitadel/oidc/v3/pkg/client/rp" "github.com/zitadel/oidc/v3/pkg/client/rs" "github.com/zitadel/oidc/v3/pkg/oidc" "golang.org/x/text/language" oidc_api "github.com/zitadel/zitadel/internal/api/oidc" "github.com/zitadel/zitadel/pkg/grpc/authn" "github.com/zitadel/zitadel/pkg/grpc/management" oidc_pb "github.com/zitadel/zitadel/pkg/grpc/oidc/v2beta" ) func TestOPStorage_SetUserinfoFromToken(t *testing.T) { clientID := createClient(t) authRequestID := createAuthRequest(t, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeProfile, oidc.ScopeEmail, oidc.ScopeOfflineAccess) sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId()) linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{ AuthRequestId: authRequestID, CallbackKind: &oidc_pb.CreateCallbackRequest_Session{ Session: &oidc_pb.Session{ SessionId: sessionID, SessionToken: sessionToken, }, }, }) require.NoError(t, err) // code exchange code := assertCodeResponse(t, linkResp.GetCallbackUrl()) tokens, err := exchangeTokens(t, clientID, code) require.NoError(t, err) assertTokens(t, tokens, true) assertIDTokenClaims(t, tokens.IDTokenClaims, armPasskey, startTime, changeTime) // test actual userinfo provider, err := Tester.CreateRelyingParty(CTX, clientID, redirectURI) require.NoError(t, err) userinfo, err := rp.Userinfo[*oidc.UserInfo](CTX, tokens.AccessToken, tokens.TokenType, tokens.IDTokenClaims.Subject, provider) require.NoError(t, err) assertUserinfo(t, userinfo) } func TestServer_Introspect(t *testing.T) { project, err := Tester.CreateProject(CTX) require.NoError(t, err) app, err := Tester.CreateOIDCNativeClient(CTX, redirectURI, logoutRedirectURI, project.GetId()) require.NoError(t, err) api, err := Tester.CreateAPIClient(CTX, project.GetId()) require.NoError(t, err) keyResp, err := Tester.Client.Mgmt.AddAppKey(CTX, &management.AddAppKeyRequest{ ProjectId: project.GetId(), AppId: api.GetAppId(), Type: authn.KeyType_KEY_TYPE_JSON, ExpirationDate: nil, }) require.NoError(t, err) resourceServer, err := Tester.CreateResourceServer(CTX, keyResp.GetKeyDetails()) require.NoError(t, err) scope := []string{oidc.ScopeOpenID, oidc.ScopeProfile, oidc.ScopeEmail, oidc.ScopeOfflineAccess, oidc_api.ScopeResourceOwner} authRequestID := createAuthRequest(t, app.GetClientId(), redirectURI, scope...) sessionID, sessionToken, startTime, changeTime := Tester.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId()) linkResp, err := Tester.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{ AuthRequestId: authRequestID, CallbackKind: &oidc_pb.CreateCallbackRequest_Session{ Session: &oidc_pb.Session{ SessionId: sessionID, SessionToken: sessionToken, }, }, }) require.NoError(t, err) // code exchange code := assertCodeResponse(t, linkResp.GetCallbackUrl()) tokens, err := exchangeTokens(t, app.GetClientId(), code) require.NoError(t, err) assertTokens(t, tokens, true) assertIDTokenClaims(t, tokens.IDTokenClaims, armPasskey, startTime, changeTime) // test actual introspection introspection, err := rs.Introspect[*oidc.IntrospectionResponse](context.Background(), resourceServer, tokens.AccessToken) require.NoError(t, err) assertIntrospection(t, introspection, Tester.OIDCIssuer(), app.GetClientId(), scope, []string{app.GetClientId(), api.GetClientId(), project.GetId()}, tokens.Expiry, tokens.Expiry.Add(-12*time.Hour)) } func assertUserinfo(t *testing.T, userinfo *oidc.UserInfo) { assert.Equal(t, User.GetUserId(), userinfo.Subject) assert.Equal(t, "Mickey", userinfo.GivenName) assert.Equal(t, "Mouse", userinfo.FamilyName) assert.Equal(t, "Mickey Mouse", userinfo.Name) assert.NotEmpty(t, userinfo.PreferredUsername) assert.Equal(t, userinfo.PreferredUsername, userinfo.Email) assert.False(t, bool(userinfo.EmailVerified)) assertOIDCTime(t, userinfo.UpdatedAt, User.GetDetails().GetChangeDate().AsTime()) } func assertIntrospection( t *testing.T, introspection *oidc.IntrospectionResponse, issuer, clientID string, scope, audience []string, expiration, creation time.Time, ) { assert.True(t, introspection.Active) assert.Equal(t, scope, []string(introspection.Scope)) assert.Equal(t, clientID, introspection.ClientID) assert.Equal(t, oidc.BearerToken, introspection.TokenType) assertOIDCTime(t, introspection.Expiration, expiration) assertOIDCTime(t, introspection.IssuedAt, creation) assertOIDCTime(t, introspection.NotBefore, creation) assert.Equal(t, User.GetUserId(), introspection.Subject) assert.ElementsMatch(t, audience, introspection.Audience) assert.Equal(t, issuer, introspection.Issuer) assert.NotEmpty(t, introspection.JWTID) assert.NotEmpty(t, introspection.Username) assert.Equal(t, introspection.Username, introspection.PreferredUsername) assert.Equal(t, "Mickey", introspection.GivenName) assert.Equal(t, "Mouse", introspection.FamilyName) assert.Equal(t, "Mickey Mouse", introspection.Name) assert.Equal(t, oidc.Gender("male"), introspection.Gender) assert.Equal(t, oidc.NewLocale(language.Dutch), introspection.Locale) assert.Equal(t, introspection.Username, introspection.Email) assert.False(t, bool(introspection.EmailVerified)) assertOIDCTime(t, introspection.UpdatedAt, User.GetDetails().GetChangeDate().AsTime()) require.NotNil(t, introspection.Claims) assert.Equal(t, User.Details.ResourceOwner, introspection.Claims[oidc_api.ClaimResourceOwner+"id"]) assert.NotEmpty(t, introspection.Claims[oidc_api.ClaimResourceOwner+"name"]) assert.NotEmpty(t, introspection.Claims[oidc_api.ClaimResourceOwner+"primary_domain"]) }