Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 7 additions & 31 deletions integration/tests/machine-auth/m2m.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,7 @@ test.describe('machine-to-machine auth @machine', () => {
const token = req.get('Authorization')?.split(' ')[1];

try {
const m2mToken = await clerkClient.m2mTokens.verifyToken({ token });
res.send('Protected response ' + m2mToken.id);
} catch {
res.status(401).send('Unauthorized');
}
});

app.get('/api/protected-deprecated', async (req, res) => {
const secret = req.get('Authorization')?.split(' ')[1];

try {
const m2mToken = await clerkClient.m2mTokens.verifySecret({ secret });
const m2mToken = await clerkClient.m2m.verifyToken({ token });
res.send('Protected response ' + m2mToken.id);
} catch {
res.status(401).send('Unauthorized');
Expand All @@ -81,7 +70,7 @@ test.describe('machine-to-machine auth @machine', () => {
name: `${fakeCompanyName} Email Server`,
scopedMachines: [primaryApiServer.id],
});
emailServerM2MToken = await client.m2mTokens.create({
emailServerM2MToken = await client.m2m.createToken({
machineSecretKey: emailServer.secretKey,
secondsUntilExpiration: 60 * 30,
});
Expand All @@ -91,7 +80,7 @@ test.describe('machine-to-machine auth @machine', () => {
name: `${fakeCompanyName} Analytics Server`,
// No scoped machines
});
analyticsServerM2MToken = await client.m2mTokens.create({
analyticsServerM2MToken = await client.m2m.createToken({
machineSecretKey: analyticsServer.secretKey,
secondsUntilExpiration: 60 * 30,
});
Expand All @@ -102,10 +91,10 @@ test.describe('machine-to-machine auth @machine', () => {
secretKey: instanceKeys.get('with-api-keys').sk,
});

await client.m2mTokens.revoke({
await client.m2m.revokeToken({
m2mTokenId: emailServerM2MToken.id,
});
await client.m2mTokens.revoke({
await client.m2m.revokeToken({
m2mTokenId: analyticsServerM2MToken.id,
});
await client.machines.delete(emailServer.id);
Expand Down Expand Up @@ -164,7 +153,7 @@ test.describe('machine-to-machine auth @machine', () => {

// Analytics server can access primary API server after adding scope
await u.services.clerk.machines.createScope(analyticsServer.id, primaryApiServer.id);
const m2mToken = await u.services.clerk.m2mTokens.create({
const m2mToken = await u.services.clerk.m2m.createToken({
machineSecretKey: analyticsServer.secretKey,
secondsUntilExpiration: 60 * 30,
});
Expand All @@ -176,21 +165,8 @@ test.describe('machine-to-machine auth @machine', () => {
});
expect(res2.status()).toBe(200);
expect(await res2.text()).toBe('Protected response ' + m2mToken.id);
await u.services.clerk.m2mTokens.revoke({
await u.services.clerk.m2m.revokeToken({
m2mTokenId: m2mToken.id,
});
});

test('authorizes M2M requests with deprecated verifySecret method', async ({ page, context }) => {
const u = createTestUtils({ app, page, context });

// Email server can access primary API server
const res = await u.page.request.get(app.serverUrl + '/api/protected-deprecated', {
headers: {
Authorization: `Bearer ${emailServerM2MToken.token}`,
},
});
expect(res.status()).toBe(200);
expect(await res.text()).toBe('Protected response ' + emailServerM2MToken.id);
});
});
97 changes: 10 additions & 87 deletions packages/backend/src/api/__tests__/M2MTokenApi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ describe('M2MToken', () => {
),
);

const response = await apiClient.m2mTokens.create({
const response = await apiClient.m2m.createToken({
secondsUntilExpiration: 3600,
});

Expand All @@ -65,7 +65,7 @@ describe('M2MToken', () => {
),
);

const response = await apiClient.m2mTokens.create({
const response = await apiClient.m2m.createToken({
machineSecretKey: 'ak_xxxxx',
secondsUntilExpiration: 3600,
});
Expand Down Expand Up @@ -102,7 +102,7 @@ describe('M2MToken', () => {
),
);

const errResponse = await apiClient.m2mTokens.create().catch(err => err);
const errResponse = await apiClient.m2m.createToken().catch(err => err);

expect(errResponse.status).toBe(401);
expect(errResponse.errors[0].code).toBe('machine_secret_key_invalid');
Expand Down Expand Up @@ -143,7 +143,7 @@ describe('M2MToken', () => {
),
);

const response = await apiClient.m2mTokens.revoke({
const response = await apiClient.m2m.revokeToken({
m2mTokenId: m2mId,
revocationReason: 'revoked by test',
});
Expand Down Expand Up @@ -171,7 +171,7 @@ describe('M2MToken', () => {
),
);

const response = await apiClient.m2mTokens.revoke({
const response = await apiClient.m2m.revokeToken({
m2mTokenId: m2mId,
revocationReason: 'revoked by test',
});
Expand All @@ -195,8 +195,8 @@ describe('M2MToken', () => {
),
);

const errResponse = await apiClient.m2mTokens
.revoke({
const errResponse = await apiClient.m2m
.revokeToken({
m2mTokenId: m2mId,
revocationReason: 'revoked by test',
})
Expand All @@ -223,7 +223,7 @@ describe('M2MToken', () => {
),
);

const response = await apiClient.m2mTokens.verifyToken({
const response = await apiClient.m2m.verifyToken({
token: m2mSecret,
});

Expand All @@ -249,7 +249,7 @@ describe('M2MToken', () => {
),
);

const response = await apiClient.m2mTokens.verifyToken({
const response = await apiClient.m2m.verifyToken({
token: m2mSecret,
});

Expand All @@ -273,7 +273,7 @@ describe('M2MToken', () => {
),
);

const errResponse = await apiClient.m2mTokens
const errResponse = await apiClient.m2m
.verifyToken({
token: m2mSecret,
})
Expand All @@ -282,81 +282,4 @@ describe('M2MToken', () => {
expect(errResponse.status).toBe(401);
});
});

describe('verifySecret (deprecated)', () => {
it('verifies a m2m token using machine secret', async () => {
const apiClient = createBackendApiClient({
apiUrl: 'https://api.clerk.test',
machineSecretKey: 'ak_xxxxx',
});

server.use(
http.post(
'https://api.clerk.test/m2m_tokens/verify',
validateHeaders(({ request }) => {
expect(request.headers.get('Authorization')).toBe('Bearer ak_xxxxx');
return HttpResponse.json(mockM2MToken);
}),
),
);

const response = await apiClient.m2mTokens.verifySecret({
secret: m2mSecret,
});

expect(response.id).toBe(m2mId);
expect(response.token).toBe(m2mSecret);
expect(response.scopes).toEqual(['mch_1xxxxx', 'mch_2xxxxx']);
expect(response.claims).toEqual({ foo: 'bar' });
});

it('verifies a m2m token using instance secret', async () => {
const apiClient = createBackendApiClient({
apiUrl: 'https://api.clerk.test',
secretKey: 'sk_xxxxx',
});

server.use(
http.post(
'https://api.clerk.test/m2m_tokens/verify',
validateHeaders(({ request }) => {
expect(request.headers.get('Authorization')).toBe('Bearer sk_xxxxx');
return HttpResponse.json(mockM2MToken);
}),
),
);

const response = await apiClient.m2mTokens.verifySecret({
secret: m2mSecret,
});

expect(response.id).toBe(m2mId);
expect(response.token).toBe(m2mSecret);
expect(response.scopes).toEqual(['mch_1xxxxx', 'mch_2xxxxx']);
expect(response.claims).toEqual({ foo: 'bar' });
});

it('requires a machine secret or instance secret to verify a m2m token', async () => {
const apiClient = createBackendApiClient({
apiUrl: 'https://api.clerk.test',
});

server.use(
http.post(
'https://api.clerk.test/m2m_tokens/verify',
validateHeaders(() => {
return HttpResponse.json(mockM2MToken);
}),
),
);

const errResponse = await apiClient.m2mTokens
.verifySecret({
secret: m2mSecret,
})
.catch(err => err);

expect(errResponse.status).toBe(401);
});
});
});
6 changes: 3 additions & 3 deletions packages/backend/src/api/__tests__/factory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ describe('api.client', () => {
),
);

const response = await apiClient.m2mTokens.verifyToken({
const response = await apiClient.m2m.verifyToken({
machineSecretKey: 'ak_test_in_header_params', // this will be added to headerParams.Authorization
token: 'mt_secret_test',
});
Expand Down Expand Up @@ -353,7 +353,7 @@ describe('api.client', () => {
),
);

const response = await apiClient.m2mTokens.verifyToken({
const response = await apiClient.m2m.verifyToken({
token: 'mt_secret_test',
});
expect(response.id).toBe('mt_test');
Expand Down Expand Up @@ -425,7 +425,7 @@ describe('api.client', () => {
),
);

const response = await apiClient.m2mTokens.verifyToken({
const response = await apiClient.m2m.verifyToken({
token: 'mt_secret_test',
});
expect(response.id).toBe('mt_test');
Expand Down
39 changes: 2 additions & 37 deletions packages/backend/src/api/endpoints/M2MTokenApi.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { deprecated } from '@clerk/shared/deprecated';

import { joinPaths } from '../../util/path';
import type { ClerkBackendApiRequestOptions } from '../request';
import type { M2MToken } from '../resources/M2MToken';
Expand Down Expand Up @@ -33,17 +31,6 @@ type RevokeM2MTokenParams = {
revocationReason?: string | null;
};

type VerifyM2MTokenParamsDeprecated = {
/**
* Custom machine secret key for authentication.
*/
machineSecretKey?: string;
/**
* Machine-to-machine token secret to verify.
*/
secret: string;
};

type VerifyM2MTokenParams = {
/**
* Custom machine secret key for authentication.
Expand All @@ -70,7 +57,7 @@ export class M2MTokenApi extends AbstractAPI {
return options;
}

async create(params?: CreateM2MTokenParams) {
async createToken(params?: CreateM2MTokenParams) {
const { claims = null, machineSecretKey, secondsUntilExpiration = null } = params || {};

const requestOptions = this.#createRequestOptions(
Expand All @@ -88,7 +75,7 @@ export class M2MTokenApi extends AbstractAPI {
return this.request<M2MToken>(requestOptions);
}

async revoke(params: RevokeM2MTokenParams) {
async revokeToken(params: RevokeM2MTokenParams) {
const { m2mTokenId, revocationReason = null, machineSecretKey } = params;

this.requireId(m2mTokenId);
Expand All @@ -107,28 +94,6 @@ export class M2MTokenApi extends AbstractAPI {
return this.request<M2MToken>(requestOptions);
}

/**
* Verify a machine-to-machine token.
*
* @deprecated Use {@link verifyToken} instead.
*/
async verifySecret(params: VerifyM2MTokenParamsDeprecated) {
const { secret, machineSecretKey } = params;

deprecated('verifySecret', 'Use `verifyToken({ token: mt_xxx })` instead');

const requestOptions = this.#createRequestOptions(
{
method: 'POST',
path: joinPaths(basePath, 'verify'),
bodyParams: { secret },
},
machineSecretKey,
);

return this.request<M2MToken>(requestOptions);
}

async verifyToken(params: VerifyM2MTokenParams) {
const { token, machineSecretKey } = params;

Expand Down
2 changes: 1 addition & 1 deletion packages/backend/src/api/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export function createBackendApiClient(options: CreateBackendApiOptions) {
jwks: new JwksAPI(request),
jwtTemplates: new JwtTemplatesApi(request),
machines: new MachineApi(request),
m2mTokens: new M2MTokenApi(
m2m: new M2MTokenApi(
buildRequest({
...options,
skipApiVersionInUrl: true,
Expand Down
2 changes: 1 addition & 1 deletion packages/backend/src/tokens/verify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ async function verifyM2MToken(
): Promise<MachineTokenReturnType<M2MToken, MachineTokenVerificationError>> {
try {
const client = createBackendApiClient(options);
const verifiedToken = await client.m2mTokens.verifyToken({ token });
const verifiedToken = await client.m2m.verifyToken({ token });
return { data: verifiedToken, tokenType: TokenType.M2MToken, errors: undefined };
Comment on lines +209 to 210
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Pass per-call machineSecretKey and align the exported API type

The low-level API supports a per-call machineSecretKey, but it’s not forwarded here. Also, verifyMachineAuthToken’s options type currently excludes machineSecretKey, preventing consumers from passing it even though verifyM2MToken supports it.

Forward the per-call secret:

-    const verifiedToken = await client.m2m.verifyToken({ token });
+    const verifiedToken = await client.m2m.verifyToken({ token, machineSecretKey: options.machineSecretKey });

And update the exported function type (outside this hunk; Lines 248-249) to accept the machine secret:

-export async function verifyMachineAuthToken(token: string, options: VerifyTokenOptions) {
+export async function verifyMachineAuthToken(
+  token: string,
+  options: VerifyTokenOptions & { machineSecretKey?: string },
+) {

This preserves generic usage while allowing proper auth for M2M verification without requiring an instance secret.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const verifiedToken = await client.m2m.verifyToken({ token });
return { data: verifiedToken, tokenType: TokenType.M2MToken, errors: undefined };
// … around line 209 …
- const verifiedToken = await client.m2m.verifyToken({ token });
+ const verifiedToken = await client.m2m.verifyToken({
+ token,
+ machineSecretKey: options.machineSecretKey,
+ });
return { data: verifiedToken, tokenType: TokenType.M2MToken, errors: undefined };
// …
Suggested change
const verifiedToken = await client.m2m.verifyToken({ token });
return { data: verifiedToken, tokenType: TokenType.M2MToken, errors: undefined };
// … around lines 248–249 …
-export async function verifyMachineAuthToken(token: string, options: VerifyTokenOptions) {
+export async function verifyMachineAuthToken(
+ token: string,
+ options: VerifyTokenOptions & { machineSecretKey?: string },
+) {
// function body…
}
🤖 Prompt for AI Agents
In packages/backend/src/tokens/verify.ts around lines 209 to 210, the per-call
machineSecretKey supported by the low-level client isn't being forwarded to
client.m2m.verifyToken and the exported verifyMachineAuthToken options type
(around lines 248-249) currently excludes machineSecretKey; update the call to
pass through the machineSecretKey from options into client.m2m.verifyToken and
modify the exported function/options type to include machineSecretKey (while
preserving existing generics and other fields) so callers can provide a per-call
secret for M2M verification without requiring an instance secret.

} catch (err: any) {
return handleClerkAPIError(TokenType.M2MToken, err, 'Machine token not found');
Expand Down
Loading