diff --git a/.changeset/kind-ravens-collect.md b/.changeset/kind-ravens-collect.md new file mode 100644 index 00000000000..b3cad0f6487 --- /dev/null +++ b/.changeset/kind-ravens-collect.md @@ -0,0 +1,14 @@ +--- +"@clerk/backend": patch +--- + +Support `min_remaining_ttl_seconds` for M2M token creation. + +Usage: + +```ts +clerkClient.m2m.createToken({ + machineSecretKey: 'ak_xxxxx', + minRemainingTtlSeconds: 240, +}) +``` diff --git a/packages/backend/src/api/__tests__/M2MTokenApi.test.ts b/packages/backend/src/api/__tests__/M2MTokenApi.test.ts index ed77bc74ef3..463366dbe37 100644 --- a/packages/backend/src/api/__tests__/M2MTokenApi.test.ts +++ b/packages/backend/src/api/__tests__/M2MTokenApi.test.ts @@ -37,8 +37,10 @@ describe('M2MToken', () => { server.use( http.post( 'https://api.clerk.test/m2m_tokens', - validateHeaders(({ request }) => { + validateHeaders(async ({ request }) => { expect(request.headers.get('Authorization')).toBe('Bearer ak_xxxxx'); + const body = (await request.json()) as Record; + expect(body.min_remaining_ttl_seconds).toBe(240); return HttpResponse.json(mockM2MToken); }), ), @@ -46,6 +48,7 @@ describe('M2MToken', () => { const response = await apiClient.m2m.createToken({ secondsUntilExpiration: 3600, + minRemainingTtlSeconds: 240, }); expect(response.id).toBe(m2mId); @@ -62,8 +65,10 @@ describe('M2MToken', () => { server.use( http.post( 'https://api.clerk.test/m2m_tokens', - validateHeaders(({ request }) => { + validateHeaders(async ({ request }) => { expect(request.headers.get('Authorization')).toBe('Bearer ak_xxxxx'); + const body = (await request.json()) as Record; + expect(body.min_remaining_ttl_seconds).toBe(240); return HttpResponse.json(mockM2MToken); }), ), @@ -72,6 +77,7 @@ describe('M2MToken', () => { const response = await apiClient.m2m.createToken({ machineSecretKey: 'ak_xxxxx', secondsUntilExpiration: 3600, + minRemainingTtlSeconds: 240, }); expect(response.id).toBe(m2mId); diff --git a/packages/backend/src/api/endpoints/M2MTokenApi.ts b/packages/backend/src/api/endpoints/M2MTokenApi.ts index a0ac56e04d2..198179b7c3f 100644 --- a/packages/backend/src/api/endpoints/M2MTokenApi.ts +++ b/packages/backend/src/api/endpoints/M2MTokenApi.ts @@ -55,6 +55,11 @@ type CreateM2MTokenParams = { */ secondsUntilExpiration?: number | null; claims?: Record | null; + /** + * Enables server-side token reuse for opaque tokens when an existing token + * with matching claims/scopes still has at least this many seconds remaining. + */ + minRemainingTtlSeconds?: number; /** * @default 'opaque' */ @@ -127,7 +132,13 @@ export class M2MTokenApi extends AbstractAPI { } async createToken(params?: CreateM2MTokenParams) { - const { claims = null, machineSecretKey, secondsUntilExpiration = null, tokenFormat = 'opaque' } = params || {}; + const { + claims = null, + machineSecretKey, + minRemainingTtlSeconds, + secondsUntilExpiration = null, + tokenFormat = 'opaque', + } = params || {}; const requestOptions = this.#createRequestOptions( { @@ -136,6 +147,7 @@ export class M2MTokenApi extends AbstractAPI { bodyParams: { secondsUntilExpiration, claims, + minRemainingTtlSeconds, tokenFormat, }, },