From 689f42acc7efbfe089c80c093b1dafdeb70beafe Mon Sep 17 00:00:00 2001 From: Tom Milewski Date: Thu, 10 Apr 2025 13:35:50 -0400 Subject: [PATCH 1/3] feat(backend): Add JWKS BAPI endpoint --- .../backend/src/api/__tests__/factory.test.ts | 27 +++++++++++++++++++ packages/backend/src/api/endpoints/JwksApi.ts | 13 +++++++++ packages/backend/src/api/endpoints/index.ts | 1 + packages/backend/src/api/factory.ts | 2 ++ packages/backend/src/api/resources/JSON.ts | 13 +++++++++ packages/backend/src/fixtures/jwks.json | 12 +++++++++ 6 files changed, 68 insertions(+) create mode 100644 packages/backend/src/api/endpoints/JwksApi.ts create mode 100644 packages/backend/src/fixtures/jwks.json diff --git a/packages/backend/src/api/__tests__/factory.test.ts b/packages/backend/src/api/__tests__/factory.test.ts index f722c890f94..7ffb78b43cc 100644 --- a/packages/backend/src/api/__tests__/factory.test.ts +++ b/packages/backend/src/api/__tests__/factory.test.ts @@ -1,6 +1,7 @@ import { http, HttpResponse } from 'msw'; import { describe, expect, it } from 'vitest'; +import jwksJson from '../../fixtures/jwks.json'; import userJson from '../../fixtures/user.json'; import { server, validateHeaders } from '../../mock-server'; import { createBackendApiClient } from '../factory'; @@ -275,4 +276,30 @@ describe('api.client', () => { expect(data[0].token).toBe(''); expect(data[0].scopes).toEqual(['email', 'profile']); }); + + describe('JWKS', () => { + it('executes a successful backend API request for a single resource and returns the raw response', async () => { + server.use( + http.get( + `https://api.clerk.test/v1/jwks`, + validateHeaders(() => { + return HttpResponse.json(jwksJson); + }), + ), + ); + + const response = await apiClient.jwks.getJwks(); + const key = response.keys?.[0]; + + expect(key).toBeDefined(); + expect(key?.kid).toBe('ins_1234'); + expect(key?.alg).toBe('RS256'); + expect(key?.kty).toBe('RSA'); + expect(key?.use).toBe('sig'); + expect(key?.e).toBe('BQGF'); + expect(key?.n).toBe( + 'xV3jihnMy4sr5jJ4S66YTc6FxnFsVy3weiyJFYOAdo515AZMrpMMdraAiVmnXZfolZpv7CcnsnG290cg-XfGRNk-Jil_tJt2SLGtiT9LtWT_iev4zN8veRGzTaOb6C-Qb6T_8xsjP_sp0a92zyNgyc4UxR-acMmOqxjkHmx1q0U1fCom83WI59Yu5VmvLM4MA-1sLkmAE1bTzp4ie-_xu9anwsS3H97MONGtildB4nAG0L-lj7tReNHoYLkciEKCqqUMoK-o6JN29OKozpqiI4dVv0oityWw2ygf6eR5qrKZZjrjbAMt_emXBFGQ5Y1QSsriJoRoykGcdbXaU7S_QV', + ); + }); + }); }); diff --git a/packages/backend/src/api/endpoints/JwksApi.ts b/packages/backend/src/api/endpoints/JwksApi.ts new file mode 100644 index 00000000000..f8aef648efc --- /dev/null +++ b/packages/backend/src/api/endpoints/JwksApi.ts @@ -0,0 +1,13 @@ +import type { JwksJSON } from '../resources/JSON'; +import { AbstractAPI } from './AbstractApi'; + +const basePath = '/jwks'; + +export class JwksAPI extends AbstractAPI { + public async getJwks() { + return this.request({ + method: 'GET', + path: basePath, + }); + } +} diff --git a/packages/backend/src/api/endpoints/index.ts b/packages/backend/src/api/endpoints/index.ts index f1ea10ac9ce..c7e94d4e073 100644 --- a/packages/backend/src/api/endpoints/index.ts +++ b/packages/backend/src/api/endpoints/index.ts @@ -5,6 +5,7 @@ export * from './ClientApi'; export * from './DomainApi'; export * from './EmailAddressApi'; export * from './InvitationApi'; +export * from './JwksApi'; export * from './OrganizationApi'; export * from './PhoneNumberApi'; export * from './RedirectUrlApi'; diff --git a/packages/backend/src/api/factory.ts b/packages/backend/src/api/factory.ts index 47098188dbe..068ba90fb9b 100644 --- a/packages/backend/src/api/factory.ts +++ b/packages/backend/src/api/factory.ts @@ -5,6 +5,7 @@ import { DomainAPI, EmailAddressAPI, InvitationAPI, + JwksAPI, OrganizationAPI, PhoneNumberAPI, RedirectUrlAPI, @@ -31,6 +32,7 @@ export function createBackendApiClient(options: CreateBackendApiOptions) { clients: new ClientAPI(request), emailAddresses: new EmailAddressAPI(request), invitations: new InvitationAPI(request), + jwks: new JwksAPI(request), organizations: new OrganizationAPI(request), phoneNumbers: new PhoneNumberAPI(request), redirectUrls: new RedirectUrlAPI(request), diff --git a/packages/backend/src/api/resources/JSON.ts b/packages/backend/src/api/resources/JSON.ts index 5ad5f0cc078..9d8b20fbcb6 100644 --- a/packages/backend/src/api/resources/JSON.ts +++ b/packages/backend/src/api/resources/JSON.ts @@ -126,6 +126,19 @@ export interface ExternalAccountJSON extends ClerkResourceJSON { verification: VerificationJSON | null; } +export interface JwksJSON { + keys?: JwksKeyJSON[]; +} + +export interface JwksKeyJSON { + use: string; + kty: string; + kid: string; + alg: string; + n: string; + e: string; +} + export interface SamlAccountJSON extends ClerkResourceJSON { object: typeof ObjectType.SamlAccount; provider: string; diff --git a/packages/backend/src/fixtures/jwks.json b/packages/backend/src/fixtures/jwks.json new file mode 100644 index 00000000000..14150fd33b1 --- /dev/null +++ b/packages/backend/src/fixtures/jwks.json @@ -0,0 +1,12 @@ +{ + "keys": [ + { + "use": "sig", + "kty": "RSA", + "kid": "ins_1234", + "alg": "RS256", + "n": "xV3jihnMy4sr5jJ4S66YTc6FxnFsVy3weiyJFYOAdo515AZMrpMMdraAiVmnXZfolZpv7CcnsnG290cg-XfGRNk-Jil_tJt2SLGtiT9LtWT_iev4zN8veRGzTaOb6C-Qb6T_8xsjP_sp0a92zyNgyc4UxR-acMmOqxjkHmx1q0U1fCom83WI59Yu5VmvLM4MA-1sLkmAE1bTzp4ie-_xu9anwsS3H97MONGtildB4nAG0L-lj7tReNHoYLkciEKCqqUMoK-o6JN29OKozpqiI4dVv0oityWw2ygf6eR5qrKZZjrjbAMt_emXBFGQ5Y1QSsriJoRoykGcdbXaU7S_QV", + "e": "BQGF" + } + ] +} From 3f52c93741d31079db4c2741706ffd267a62f146 Mon Sep 17 00:00:00 2001 From: Tom Milewski Date: Thu, 10 Apr 2025 14:05:31 -0400 Subject: [PATCH 2/3] chore: Add changeset --- .changeset/bright-spoons-fix.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .changeset/bright-spoons-fix.md diff --git a/.changeset/bright-spoons-fix.md b/.changeset/bright-spoons-fix.md new file mode 100644 index 00000000000..2300a3aacb6 --- /dev/null +++ b/.changeset/bright-spoons-fix.md @@ -0,0 +1,12 @@ +--- +'@clerk/backend': patch +--- + +Adds the ability to verify proxy checks to the Backend API client. + +```ts + import { createClerkClient } from '@clerk/backend'; + + const clerkClient = createClerkClient(...); + await clerkClient.jwks.getJWKS(); +``` \ No newline at end of file From 3b819f3269fb98cebfa944aa6f596a1c1bbdb6a4 Mon Sep 17 00:00:00 2001 From: Tom Milewski Date: Thu, 10 Apr 2025 18:28:35 -0400 Subject: [PATCH 3/3] chore: Update changeset --- .changeset/bright-spoons-fix.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/bright-spoons-fix.md b/.changeset/bright-spoons-fix.md index 2300a3aacb6..3592102363f 100644 --- a/.changeset/bright-spoons-fix.md +++ b/.changeset/bright-spoons-fix.md @@ -1,8 +1,8 @@ --- -'@clerk/backend': patch +'@clerk/backend': minor --- -Adds the ability to verify proxy checks to the Backend API client. +Adds the ability to grab an instance's JWKS to the Backend API client. ```ts import { createClerkClient } from '@clerk/backend';