Skip to content
5 changes: 5 additions & 0 deletions .changeset/major-mice-admire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/backend': patch
---

Clarify issuer validation for session tokens and document that machine token verification ignores the issuer option.
5 changes: 5 additions & 0 deletions packages/backend/src/jwt/verifyJwt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,11 @@ export type VerifyJwtOptions = {
* @default 5000
*/
clockSkewInMs?: number;
/**
* A string or list of allowed issuers. If passed, the token's `iss` claim must match one exactly. If omitted, the issuer is not checked.
*
* `verifyMachineAuthToken()` accepts this option for compatibility but ignores it when verifying machine tokens.
*/
issuer?: string | string[];
/**
* @internal
Expand Down
17 changes: 17 additions & 0 deletions packages/backend/src/tokens/__tests__/request.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1555,6 +1555,23 @@ describe('tokens.authenticateRequest(options)', () => {
vi.clearAllMocks();
});

test('ignores a session issuer when authenticating an M2M JWT', async () => {
server.use(http.get('https://api.clerk.test/v1/jwks', () => HttpResponse.json(mockJwks)));

const { data: m2mJwt } = await signJwt(mockM2MJwtPayload, signingJwks, {
algorithm: 'RS256',
header: { typ: 'JWT', kid: 'ins_2GIoQhbUpy0hX7B2cVkuTMinXoD' },
});

const request = mockRequest({ authorization: `Bearer ${m2mJwt}` });
const requestState = await authenticateRequest(
request,
mockOptions({ acceptsToken: 'm2m_token', issuer: mockJwtPayload.iss }),
);

expect(requestState).toBeMachineAuthenticated();
});

// Test each token type with parameterized tests
const tokenTypes = [TokenType.ApiKey, TokenType.OAuthToken, TokenType.M2MToken];

Expand Down
Loading