fix: Revert "feat(oidc): Added new claim in userinfo response to return all requested audience roles (#9861)" (#10874)

# Which Problems Are Solved

#9861 added a `urn:zitadel:iam:org:projects:roles` claims to include all
roles from all requested roles. The intention was to return them on the
userinfo endpoint. But since the claims might also be returned in the id
and access tokens, they can grow big quite fast and break the size
limits for headers.

# How the Problems Are Solved

This PR revert the feature. The information for roles of other projects
is already available as a dedicated claim (for each project):
```json
  "urn:zitadel:iam:org:project:328813096124547391:roles": {
    "r2": {
      "306639557921669515": "zitadel.localhost"
    },
    "r3": {
      "306639557921669515": "zitadel.localhost"
    },
    "role": {
      "306639557921669515": "zitadel.localhost"
    }
  },
  "urn:zitadel:iam:org:project:341406882914631999:roles": {
    "role": {
      "306639557921669515": "zitadel.localhost",
      "328237605990695334": "aa.localhost"
    },
    "test": {
      "306639557921669515": "zitadel.localhost",
      "328237605990695334": "aa.localhost"
    }
  },
  "urn:zitadel:iam:org:project:roles": {
    "r2": {
      "306639557921669515": "zitadel.localhost"
    },
    "r3": {
      "306639557921669515": "zitadel.localhost"
    },
    "role": {
      "306639557921669515": "zitadel.localhost"
    }
  }
 ```

# Additional Changes

None

# Additional Context

- relates to #9861 
- noted issues in production
- requires backport to v4.x
This commit is contained in:
Livio Spring
2025-10-09 10:29:49 +00:00
committed by GitHub
parent 21edbc9afe
commit b8bff3cdea
6 changed files with 9 additions and 141 deletions
@@ -1,106 +0,0 @@
//go:build integration
package oidc_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zitadel/oidc/v3/pkg/client/rp"
"github.com/zitadel/oidc/v3/pkg/oidc"
oidc_api "github.com/zitadel/zitadel/internal/api/oidc"
"github.com/zitadel/zitadel/internal/domain"
)
func TestServer_UserInfo_ProjectsRolesClaim(t *testing.T) {
const (
roleFoo = "foo"
roleBar = "bar"
roleBaz = "baz"
)
clientID, projectID1 := createClient(t, Instance)
_, projectID2 := createClient(t, Instance)
addProjectRolesGrants(t, User.GetUserId(), projectID1, roleFoo, roleBar)
addProjectRolesGrants(t, User.GetUserId(), projectID2, roleBaz)
tests := []struct {
name string
prepare func(t *testing.T, clientID string, scope []string) *oidc.Tokens[*oidc.IDTokenClaims]
scope []string
assertions []func(*testing.T, *oidc.UserInfo)
wantErr bool
}{
{
name: "project role and audience scope with only current project",
prepare: getTokens,
scope: []string{oidc.ScopeProfile, oidc.ScopeOpenID, oidc.ScopeEmail, oidc.ScopeOfflineAccess,
domain.ProjectIDScope + projectID1 + domain.AudSuffix, domain.ProjectsIDScope,
},
assertions: []func(*testing.T, *oidc.UserInfo){
assertUserinfo,
func(t *testing.T, ui *oidc.UserInfo) {
assertProjectsRolesClaims(t, ui.Claims, []string{roleFoo, roleBar}, []string{Instance.DefaultOrg.Id})
},
},
},
{
name: "project role and audience scope with multiple projects as audience",
prepare: getTokens,
scope: []string{oidc.ScopeProfile, oidc.ScopeOpenID, oidc.ScopeEmail, oidc.ScopeOfflineAccess,
domain.ProjectIDScope + projectID1 + domain.AudSuffix,
domain.ProjectIDScope + projectID2 + domain.AudSuffix,
domain.ProjectsIDScope,
},
assertions: []func(*testing.T, *oidc.UserInfo){
assertUserinfo,
func(t *testing.T, ui *oidc.UserInfo) {
assertProjectsRolesClaims(t, ui.Claims, []string{roleFoo, roleBar, roleBaz}, []string{Instance.DefaultOrg.Id})
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tokens := tt.prepare(t, clientID, tt.scope)
provider, err := Instance.CreateRelyingParty(CTX, clientID, redirectURI)
require.NoError(t, err)
userinfo, err := rp.Userinfo[*oidc.UserInfo](CTX, tokens.AccessToken, tokens.TokenType, tokens.IDTokenClaims.Subject, provider)
if tt.wantErr {
assert.Error(t, err)
return
}
require.NoError(t, err)
for _, assertion := range tt.assertions {
assertion(t, userinfo)
}
})
}
}
// assertProjectsRoleClaims asserts the projectRoles in the claims.
func assertProjectsRolesClaims(t *testing.T, claims map[string]any, wantRoles, wantRoleOrgIDs []string) {
t.Helper()
projectsRoleClaims := make([]string, 0, 2)
projectsRoleClaims = append(projectsRoleClaims, oidc_api.ClaimProjectsRoles)
for _, claim := range projectsRoleClaims {
roleMap, ok := claims[claim].(map[string]any) // map of multiple roles
require.Truef(t, ok, "claim %s not found or wrong type %T", claim, claims[claim])
gotRoles := make([]string, 0, len(roleMap))
for roleKey := range roleMap {
role, ok := roleMap[roleKey].(map[string]any) // map of multiple org IDs to org domains
require.Truef(t, ok, "role %s not found or wrong type %T", roleKey, roleMap[roleKey])
gotRoles = append(gotRoles, roleKey)
gotRoleOrgIDs := make([]string, 0, len(role))
for orgID := range role {
gotRoleOrgIDs = append(gotRoleOrgIDs, orgID)
}
assert.ElementsMatch(t, wantRoleOrgIDs, gotRoleOrgIDs)
}
assert.ElementsMatch(t, wantRoles, gotRoles)
}
}
@@ -111,7 +111,7 @@ func TestServer_UserInfo(t *testing.T) {
assertions: []func(*testing.T, *oidc.UserInfo){
assertUserinfo,
func(t *testing.T, ui *oidc.UserInfo) {
assertProjectRoleClaims(t, projectID, ui.Claims, true, []string{roleFoo, roleBar}, []string{Instance.DefaultOrg.Id})
assertProjectRoleClaims(t, projectID, ui.Claims, true, []string{roleFoo}, []string{Instance.DefaultOrg.Id})
},
},
},
@@ -125,7 +125,7 @@ func TestServer_UserInfo(t *testing.T) {
assertions: []func(*testing.T, *oidc.UserInfo){
assertUserinfo,
func(t *testing.T, ui *oidc.UserInfo) {
assertProjectRoleClaims(t, projectID, ui.Claims, true, []string{roleFoo, roleBar}, []string{Instance.DefaultOrg.Id})
assertProjectRoleClaims(t, projectID, ui.Claims, true, []string{roleFoo}, []string{Instance.DefaultOrg.Id})
},
},
},
@@ -267,7 +267,7 @@ func TestServer_UserInfo_Issue6662(t *testing.T) {
userinfo, err := rp.Userinfo[*oidc.UserInfo](CTX, tokens.AccessToken, tokens.TokenType, user.GetUserId(), provider)
require.NoError(t, err)
assertProjectRoleClaims(t, projectID, userinfo.Claims, false, []string{roleFoo, roleBar}, []string{Instance.DefaultOrg.Id})
assertProjectRoleClaims(t, projectID, userinfo.Claims, false, []string{roleFoo}, []string{Instance.DefaultOrg.Id})
}
func addProjectRolesGrants(t *testing.T, userID, projectID string, roles ...string) {