Skip to content

Commit f4f22e4

Browse files
committed
remove m2m changes
1 parent 8c62389 commit f4f22e4

7 files changed

Lines changed: 22 additions & 66 deletions

File tree

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
---
2-
'@clerk/shared': minor
32
'@clerk/backend': minor
43
---
54

6-
Type the JWT `aud` claim as an optional `string | string[]` per RFC 7519 and expose it on verified OAuth JWT access tokens. Clerk-issued OAuth access tokens may include a single RFC 8707 resource URI as a string.
5+
Expose the optional `aud` audience as `string | string[]` on verified OAuth access tokens, for both JWT and opaque tokens.

‎packages/backend/src/api/resources/IdPOAuthAccessToken.ts‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { JwtPayload } from '@clerk/shared/types';
33
import type { IdPOAuthAccessTokenJSON } from './JSON';
44

55
type OAuthJwtPayload = JwtPayload & {
6+
aud?: string | string[];
67
jti?: string;
78
client_id?: string;
89
scope?: string;
@@ -62,10 +63,10 @@ export class IdPOAuthAccessToken {
6263
oauthPayload.scp ?? oauthPayload.scope?.split(' ') ?? [],
6364
false,
6465
null,
65-
oauthPayload.exp * 1000 <= Date.now() - clockSkewInMs,
66-
oauthPayload.exp * 1000, // milliseconds: expiration, converted from JWT exp claim
67-
oauthPayload.iat * 1000, // milliseconds: createdAt, converted from JWT iat claim
68-
oauthPayload.iat * 1000, // milliseconds: updatedAt, no JWT equivalent, defaults to iat
66+
payload.exp * 1000 <= Date.now() - clockSkewInMs,
67+
payload.exp * 1000, // milliseconds: expiration, converted from JWT exp claim
68+
payload.iat * 1000, // milliseconds: createdAt, converted from JWT iat claim
69+
payload.iat * 1000, // milliseconds: updatedAt, no JWT equivalent, defaults to iat
6970
oauthPayload.aud,
7071
);
7172
}

‎packages/backend/src/api/resources/M2MToken.ts‎

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ type M2MJwtPayload = {
77
exp: number;
88
iat: number;
99
jti?: string;
10-
aud?: string | string[];
10+
aud?: string[];
1111
scopes?: string;
1212
[key: string]: unknown;
1313
};
@@ -82,19 +82,10 @@ export class M2MToken {
8282
}
8383

8484
static fromJwtPayload(payload: M2MJwtPayload, clockSkewInMs = 5000): M2MToken {
85-
let audience: string[] = [];
86-
// If audience is an array, use it directly;
87-
// If it's a string, wrap it in an array;
88-
// If it's undefined, leave it as an empty array.
89-
if (Array.isArray(payload.aud)) {
90-
audience = payload.aud;
91-
} else if (payload.aud) {
92-
audience = [payload.aud];
93-
}
9485
return new M2MToken(
9586
payload.jti ?? '', // jti should always be present in Clerk-issued M2M JWTs
9687
payload.sub,
97-
payload.scopes?.split(' ') ?? audience,
88+
payload.scopes?.split(' ') ?? payload.aud ?? [],
9889
extractCustomClaims(payload),
9990
false,
10091
null,

‎packages/backend/src/api/resources/__tests__/M2MToken.test.ts‎

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -105,21 +105,6 @@ describe('M2MToken', () => {
105105
expect(token.scopes).toEqual(['scope1', 'scope2', 'scope3']);
106106
});
107107

108-
it('seeds scopes from a string aud claim', () => {
109-
const payload = {
110-
sub: 'mch_test',
111-
exp: 1666648550,
112-
iat: 1666648250,
113-
jti: 'mt_test',
114-
aud: 'https://my-resource.example.com',
115-
};
116-
117-
const token = M2MToken.fromJwtPayload(payload);
118-
119-
expect(token.scopes).toEqual(['https://my-resource.example.com']);
120-
expect(token.claims).toEqual({ aud: 'https://my-resource.example.com' });
121-
});
122-
123108
it('returns empty scopes when neither aud nor scopes present', () => {
124109
const payload = {
125110
sub: 'mch_test',

‎packages/backend/src/tokens/__tests__/verify.test.ts‎

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -230,14 +230,18 @@ describe('tokens.verifyMachineAuthToken(token, options)', () => {
230230
expect(data.scopes).toEqual(['mch_1xxxxx', 'mch_2xxxxx']);
231231
});
232232

233-
it('verifies provided OAuth token', async () => {
233+
it.each([
234+
{ aud: undefined },
235+
{ aud: 'https://my-resource.example.com' },
236+
{ aud: ['https://my-resource.example.com', 'https://other-resource.example.com'] },
237+
])('verifies opaque OAuth token with aud=$aud', async ({ aud }) => {
234238
const token = 'oat_8XOIucKvqHVr5tYP123456789abcdefghij';
235239

236240
server.use(
237241
http.post(
238242
'https://api.clerk.test/oauth_applications/access_tokens/verify',
239243
validateHeaders(() => {
240-
return HttpResponse.json(mockVerificationResults.oauth_token);
244+
return HttpResponse.json({ ...mockVerificationResults.oauth_token, ...(aud === undefined ? {} : { aud }) });
241245
}),
242246
),
243247
);
@@ -255,6 +259,7 @@ describe('tokens.verifyMachineAuthToken(token, options)', () => {
255259
expect(data.id).toBe('oat_2VTWUzvGC5UhdJCNx6xG1D98edc');
256260
expect(data.subject).toBe('user_2vYVtestTESTtestTESTtestTESTtest');
257261
expect(data.scopes).toEqual(['read:foo', 'write:bar']);
262+
expect(data.aud).toEqual(aud);
258263
});
259264

260265
describe('handles API errors for API keys', () => {
@@ -424,6 +429,7 @@ describe('tokens.verifyMachineAuthToken(token, options)', () => {
424429
expect(data.type).toBe('oauth_token');
425430
expect(data.subject).toBe('user_2vYVtestTESTtestTESTtestTESTtest');
426431
expect(data.scopes).toEqual(['read:foo', 'write:bar']);
432+
expect(data.aud).toBeUndefined();
427433
// Timestamps are exposed in milliseconds, matching M2MToken and the API JSON shape
428434
expect(data.expiration).toBe(mockOAuthAccessTokenJwtPayload.exp * 1000);
429435
expect(data.createdAt).toBe(mockOAuthAccessTokenJwtPayload.iat * 1000);
@@ -570,7 +576,10 @@ describe('tokens.verifyMachineAuthToken(token, options)', () => {
570576
},
571577
);
572578

573-
it('verifies OAuth JWT with a matching RFC 8707 resource audience', async () => {
579+
it.each([
580+
{ aud: 'https://my-resource.example.com' },
581+
{ aud: ['https://my-resource.example.com', 'https://other-resource.example.com'] },
582+
])('verifies OAuth JWT with a matching resource audience aud=$aud', async ({ aud }) => {
574583
server.use(
575584
http.get(
576585
'https://api.clerk.test/v1/jwks',
@@ -583,7 +592,7 @@ describe('tokens.verifyMachineAuthToken(token, options)', () => {
583592
const audience = 'https://my-resource.example.com';
584593
const oauthJwt = await createSignedOAuthJwt({
585594
...mockOAuthAccessTokenJwtPayload,
586-
aud: audience,
595+
aud,
587596
});
588597

589598
const result = await verifyMachineAuthToken(oauthJwt, {
@@ -593,26 +602,7 @@ describe('tokens.verifyMachineAuthToken(token, options)', () => {
593602
});
594603

595604
expect(result.tokenType).toBe('oauth_token');
596-
expect(result.data).toMatchInlineSnapshot(`
597-
IdPOAuthAccessToken {
598-
"aud": "https://my-resource.example.com",
599-
"clientId": "client_2VTWUzvGC5UhdJCNx6xG1D98edc",
600-
"createdAt": 1666648250000,
601-
"expiration": 1666648550000,
602-
"expired": false,
603-
"id": "oat_2xKa9Bgv7NxMRDFyQw8LpZ3cTmU1vHjE",
604-
"revocationReason": null,
605-
"revoked": false,
606-
"scopes": [
607-
"read:foo",
608-
"write:bar",
609-
],
610-
"subject": "user_2vYVtestTESTtestTESTtestTESTtest",
611-
"type": "oauth_token",
612-
"updatedAt": 1666648250000,
613-
}
614-
`);
615-
expect((result.data as IdPOAuthAccessToken).aud).toBe(audience);
605+
expect(result.data).toMatchObject({ aud, scopes: ['read:foo', 'write:bar'] });
616606
expect(result.errors).toBeUndefined();
617607
});
618608

‎packages/shared/src/types/jwt.ts‎

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,6 @@ export interface ClerkJWTClaims {
7777
*/
7878
azp?: string;
7979

80-
/**
81-
* JWT Audience - [RFC7519#section-4.1.3](https://tools.ietf.org/html/rfc7519#section-4.1.3).
82-
*/
83-
aud?: string | string[];
84-
8580
/**
8681
* JWT Actor - [RFC8693](https://www.rfc-editor.org/rfc/rfc8693.html#name-act-actor-claim).
8782
*/

‎packages/shared/src/types/jwtv2.ts‎

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,6 @@ type JWTPayloadBase = {
8282
*/
8383
azp?: string;
8484

85-
/**
86-
* JWT Audience - [RFC7519#section-4.1.3](https://tools.ietf.org/html/rfc7519#section-4.1.3).
87-
*/
88-
aud?: string | string[];
89-
9085
/**
9186
* JWT Actor - [RFC8693](https://www.rfc-editor.org/rfc/rfc8693.html#name-act-actor-claim).
9287
*/

0 commit comments

Comments
 (0)