switch omitIdentityTokenOwnershipClaim to true

Signed-off-by: Fredrik Adelöw <freben@spotify.com>
This commit is contained in:
Fredrik Adelöw
2026-03-18 22:10:50 +01:00
parent 7d86248253
commit d7c67cddf5
5 changed files with 28 additions and 56 deletions
+11
View File
@@ -0,0 +1,11 @@
---
'@backstage/plugin-auth-backend': minor
---
**BREAKING**: The setting `auth.omitIdentityTokenOwnershipClaim` has had its default value switched to `true`.
With this setting Backstage user tokens issued by the `auth` backend will no longer contain an `ent` claim - the one with the user's ownership entity refs. This means that tokens issued in large orgs no longer risk hitting HTTP header size limits.
To get ownership info for the current user, code should use the `userInfo` core service. In practice code will typically already conform to this since the `ent` claim has not been readily exposed in any other way for quite some time. But code which explicitly decodes Backstage tokens - which is strongly discouraged - may be affected by this change.
The setting will remain for some time to allow it to be set back to `false` if need be, but it will be removed entirely in a future release.
-44
View File
@@ -399,50 +399,6 @@ async signInResolver({ profile }, ctx) {
}
```
## Reducing the size of issued tokens
By default the auth backend will issue user identity tokens that include the
ownership references of the user in the `ent` claim of the JWT payload. This is
done to make it easier and more efficient for consumers of the token to resolve
ownership of the user. However, depending on the shape of your organization and
how you resolve ownership claims, these tokens can grow quite large.
To address this, the auth backend now supports the configuration flag
`auth.omitIdentityTokenOwnershipClaim` that causes the `ent` claim to be omitted
from the token. This can be set to `true` in the `app-config.yaml` file.
```yaml title="in app-config.yaml"
auth:
omitIdentityTokenOwnershipClaim: true
```
When this flag is set, the `ent` claim will no longer be present in the token,
and consumers of the token will need to call the `/v1/userinfo` endpoint on the
auth backend to fetch the ownership references of the user. However, there's usually no
action required for consumers. Clients will still receive the full set
of claims during authentication, and any plugin backends will already need to
use the
[`UserInfoService`](../backend-system/core-services/user-info.md) to
access the ownership references from user credentials, which already calls the
user info endpoint if necessary.
When enabling this flag, it is important that any custom sign-in resolvers directly return the result of the sign-in method. For example, the following would not work:
```ts
const { token } = await ctx.issueToken({
claims: { sub: entityRef, ent: [entityRef] },
});
return { token }; // WARNING: This will not work
```
Instead, the sign-in resolver should directly return the result:
```ts
return ctx.issueToken({
claims: { sub: entityRef, ent: [entityRef] },
});
```
##### Using the `dangerouslyAllowSignInWithoutUserInCatalog` Option
Another way to bypass this requirement is to enable the `dangerouslyAllowSignInWithoutUserInCatalog` option for resolvers.
+9 -3
View File
@@ -45,10 +45,16 @@ export interface Config {
/**
* Whether to omit the entity ownership references (`ent`) claim from the
* identity token. If this is enabled the `ent` claim will only be available
* via the user info endpoint and the `UserInfoService`.
* identity token.
*
* Defaults to `false`.
* If this is disabled an `ent` claim will be included in the token
* containing all of the user's ownership refs as returned by the sign in
* resolver. This can in extreme cases lead to tokens that risk hitting HTTP
* header size limits. Setting it to `false` is therefore discouraged, and
* is only provided for backward compatibility reasons.
*
* Defaults to `true`, which means that the `ent` claim instead is available
* via the user info endpoint and the `UserInfoService`.
*/
omitIdentityTokenOwnershipClaim?: boolean;
+4 -4
View File
@@ -100,7 +100,7 @@ describe('authPlugin', () => {
const token = refreshRes.body.backstageIdentity.token;
const decoded = JSON.parse(atob(token.split('.')[1]));
expect(decoded.sub).toEqual(expectedIdentity.userEntityRef);
expect(decoded.ent).toEqual(expectedIdentity.ownershipEntityRefs);
expect('ent' in decoded).toBeFalsy();
const userInfoRes = await request(server)
.get('/api/auth/v1/userinfo')
@@ -115,7 +115,7 @@ describe('authPlugin', () => {
});
});
it('should omit ownership claims from the token when the config is set', async () => {
it('should include ownership claims in the token when the config is set to false', async () => {
const { server } = await startTestBackend({
features: [
authPlugin,
@@ -127,7 +127,7 @@ describe('authPlugin', () => {
baseUrl: 'http://localhost',
},
auth: {
omitIdentityTokenOwnershipClaim: true,
omitIdentityTokenOwnershipClaim: false,
...mockProvidersConfig,
},
},
@@ -149,7 +149,7 @@ describe('authPlugin', () => {
const token = refreshRes.body.backstageIdentity.token;
const decoded = JSON.parse(atob(token.split('.')[1]));
expect(decoded.sub).toEqual(expectedIdentity.userEntityRef);
expect(decoded.ent).toBeUndefined();
expect(decoded.ent).toEqual(expectedIdentity.ownershipEntityRefs);
const userInfoRes = await request(server)
.get('/api/auth/v1/userinfo')
+4 -5
View File
@@ -87,11 +87,10 @@ export async function createRouter(
database,
});
const omitClaimsFromToken = config.getOptionalBoolean(
'auth.omitIdentityTokenOwnershipClaim',
)
? ['ent']
: [];
const omitClaimsFromToken =
config.getOptionalBoolean('auth.omitIdentityTokenOwnershipClaim') ?? true
? ['ent']
: [];
let tokenIssuer: TokenIssuer;
if (keyStore instanceof StaticKeyStore) {