diff --git a/.changeset/hip-ears-add.md b/.changeset/hip-ears-add.md
new file mode 100644
index 0000000000..1e7f656507
--- /dev/null
+++ b/.changeset/hip-ears-add.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-auth-backend': patch
+---
+
+The auth backend will now refuse to issue user tokens are excessively large.
diff --git a/plugins/auth-backend/src/identity/TokenFactory.test.ts b/plugins/auth-backend/src/identity/TokenFactory.test.ts
index 4f6df89210..e1dd12333d 100644
--- a/plugins/auth-backend/src/identity/TokenFactory.test.ts
+++ b/plugins/auth-backend/src/identity/TokenFactory.test.ts
@@ -154,6 +154,23 @@ describe('TokenFactory', () => {
}).rejects.toThrow();
});
+ it('should refuse to issue excessively large tokens', async () => {
+ const factory = new TokenFactory({
+ issuer: 'my-issuer',
+ keyStore: new MemoryKeyStore(),
+ keyDurationSeconds: 5,
+ logger,
+ });
+
+ await expect(() => {
+ return factory.issueToken({
+ claims: { sub: 'user:ns/n', ent: Array(10000).fill('group:ns/n') },
+ });
+ }).rejects.toThrow(
+ /^Failed to issue a new user token. The resulting token is excessively large, with either too many ownership claims or too large custom claims./,
+ );
+ });
+
it('should defaults to ES256 when no algorithm string is supplied', async () => {
const keyDurationSeconds = 5;
const factory = new TokenFactory({
diff --git a/plugins/auth-backend/src/identity/TokenFactory.ts b/plugins/auth-backend/src/identity/TokenFactory.ts
index ca16f8821f..f778077267 100644
--- a/plugins/auth-backend/src/identity/TokenFactory.ts
+++ b/plugins/auth-backend/src/identity/TokenFactory.ts
@@ -23,6 +23,7 @@ import { LoggerService } from '@backstage/backend-plugin-api';
import { AnyJWK, KeyStore, TokenIssuer, TokenParams } from './types';
const MS_IN_S = 1000;
+const MAX_TOKEN_LENGTH = 32768; // At 64 bytes per entity ref this still leaves room for about 500 entities
type Options = {
logger: LoggerService;
@@ -97,7 +98,8 @@ export class TokenFactory implements TokenIssuer {
throw new AuthenticationError('No algorithm was provided in the key');
}
- return new SignJWT({ ...additionalClaims, iss, sub, ent, aud, iat, exp })
+ const claims = { ...additionalClaims, iss, sub, ent, aud, iat, exp };
+ const token = await new SignJWT(claims)
.setProtectedHeader({ alg: key.alg, kid: key.kid })
.setIssuer(iss)
.setAudience(aud)
@@ -105,6 +107,16 @@ export class TokenFactory implements TokenIssuer {
.setIssuedAt(iat)
.setExpirationTime(exp)
.sign(await importJWK(key));
+
+ if (token.length > MAX_TOKEN_LENGTH) {
+ throw new Error(
+ `Failed to issue a new user token. The resulting token is excessively large, with either too many ownership claims or too large custom claims. You likely have a bug either in the sign-in resolver or catalog data. The following claims were requested: '${JSON.stringify(
+ claims,
+ )}'`,
+ );
+ }
+
+ return token;
}
// This will be called by other services that want to verify ID tokens.
diff --git a/plugins/explore/src/components/GroupsExplorerContent/GroupsDiagram.test.tsx b/plugins/explore/src/components/GroupsExplorerContent/GroupsDiagram.test.tsx
index 1329d3c522..3f55c1fb4f 100644
--- a/plugins/explore/src/components/GroupsExplorerContent/GroupsDiagram.test.tsx
+++ b/plugins/explore/src/components/GroupsExplorerContent/GroupsDiagram.test.tsx
@@ -68,7 +68,7 @@ describe('', () => {
);
expect(
- screen.getByRole('link', { name: 'group:my-namespace/group-a' }),
+ screen.getByRole('link', { name: 'my-namespace/group-a' }),
).toBeInTheDocument();
});
});
diff --git a/plugins/explore/src/components/GroupsExplorerContent/GroupsExplorerContent.test.tsx b/plugins/explore/src/components/GroupsExplorerContent/GroupsExplorerContent.test.tsx
index 00a6164aa9..996cad63d6 100644
--- a/plugins/explore/src/components/GroupsExplorerContent/GroupsExplorerContent.test.tsx
+++ b/plugins/explore/src/components/GroupsExplorerContent/GroupsExplorerContent.test.tsx
@@ -82,7 +82,7 @@ describe('', () => {
await waitFor(() =>
expect(
- screen.getByRole('link', { name: 'group:my-namespace/group-a' }),
+ screen.getByRole('link', { name: 'my-namespace/group-a' }),
).toBeInTheDocument(),
);
});