Skip to content
Closed
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
05d6c8e
chore(backend): Introduce machine token secrets as authorization header
wobsoriano Jul 1, 2025
ca7a8be
chore: clean up
wobsoriano Jul 1, 2025
af6a27b
chore: use a more readable option for bapi proxy methods
wobsoriano Jul 1, 2025
fa94227
chore: add initial changeset
wobsoriano Jul 1, 2025
8dcd607
chore: add machine_secret_key type to api keys api
wobsoriano Jul 1, 2025
5d78030
Merge remote-tracking branch 'origin/main' into rob/user-2264-m2m
wobsoriano Jul 1, 2025
7bb3eb8
chore: reuse header consts
wobsoriano Jul 1, 2025
424a5a4
chore: rename to machine secret
wobsoriano Jul 1, 2025
1dbd41b
chore: clean up
wobsoriano Jul 1, 2025
7c3063c
chore: add secret property to create method
wobsoriano Jul 1, 2025
9dab708
Merge branch 'main' into rob/user-2264-m2m
wobsoriano Jul 1, 2025
db38ca5
chore: remove machine secret type from api key creation
wobsoriano Jul 1, 2025
5ce88ee
chore: make secret property optional
wobsoriano Jul 2, 2025
c33e3fd
Merge branch 'main' into rob/user-2264-m2m
wobsoriano Jul 2, 2025
f9526af
Merge branch 'main' into rob/user-2264-m2m
wobsoriano Jul 3, 2025
cb6c822
Merge branch 'main' into rob/user-2264-m2m
wobsoriano Jul 7, 2025
68bcb7e
Merge branch 'main' into rob/user-2264-m2m
wobsoriano Jul 8, 2025
e900e13
chore: add machines BAPI endpoints
wobsoriano Jul 8, 2025
6c0fc64
chore: trigger rebuild
wobsoriano Jul 8, 2025
c1d1ae2
chore: remove unnecessary params
wobsoriano Jul 8, 2025
d53115d
Merge branch 'main' into rob/user-2264-m2m
wobsoriano Jul 19, 2025
d87f937
Merge branch 'main' into rob/user-2264-m2m
wobsoriano Jul 25, 2025
7ff0538
chore: remove unused properties
wobsoriano Jul 25, 2025
e844565
chore: improve machine secret check
wobsoriano Jul 25, 2025
017bb4b
fix required secrets
wobsoriano Jul 25, 2025
e26660e
fix required secrets
wobsoriano Jul 25, 2025
0f7387d
fix required secrets
wobsoriano Jul 25, 2025
f78ddcc
chore: remove removed properties
wobsoriano Jul 25, 2025
1492a1e
Merge branch 'main' into rob/user-2264-m2m
wobsoriano Jul 28, 2025
a8c66e1
chore: remove name and claims from m2m tokens
wobsoriano Jul 28, 2025
201cb23
fix tests
wobsoriano Jul 28, 2025
64afde6
fix incorrect method in tests
wobsoriano Jul 28, 2025
b38465b
chore: update tests
wobsoriano Jul 29, 2025
18b76da
chore: update test descriptions
wobsoriano Jul 29, 2025
d91404c
chore: improve tests
wobsoriano Jul 29, 2025
37a3d65
chore: update changeset
wobsoriano Jul 29, 2025
1d69db8
chore: skip pub key init for machine tokens
wobsoriano Jul 29, 2025
b26bd76
chore: skip pub and secret key check for authenticate request with ma…
wobsoriano Jul 29, 2025
d609285
fix error handling
wobsoriano Jul 29, 2025
2e080db
chore: allow machine secrets in authenticateRequest
wobsoriano Jul 29, 2025
7055b8a
chore: remove unused export keyword
wobsoriano Jul 29, 2025
051dd85
chore: more tests
wobsoriano Jul 29, 2025
7371a32
chore: add missing secret key or machine secret error test
wobsoriano Jul 29, 2025
d96d436
Merge branch 'main' into rob/user-2264-m2m
wobsoriano Aug 4, 2025
6642321
chore: run dedupe
wobsoriano Aug 4, 2025
239b6ba
chore: add secret key
wobsoriano Aug 4, 2025
a8d310f
chore: do not destructure body params in m2m endpoints
wobsoriano Aug 4, 2025
2a74cf9
chore: do not destructure body params in machine endpoints
wobsoriano Aug 4, 2025
60b139a
chore: update tests
wobsoriano Aug 4, 2025
ad9a0ec
chore: Use machine secret key from created Clerk client
wobsoriano Aug 4, 2025
8ca8009
chore: update missing clerk instance key or machine secret key error
wobsoriano Aug 4, 2025
7569d95
formatting
wobsoriano Aug 4, 2025
ad5a0f9
fix authenticate request option types
wobsoriano Aug 4, 2025
b72e891
fix assertion
wobsoriano Aug 4, 2025
2b2ce8e
Add machine secret key to merged options
wobsoriano Aug 4, 2025
afb5923
allow custom machine secret key per method
wobsoriano Aug 5, 2025
01653ae
add jsdoc
wobsoriano Aug 5, 2025
e05d414
chore: separate backend api client machine secret key and options sec…
wobsoriano Aug 5, 2025
85e47f1
chore: clean up authorizationHeader
wobsoriano Aug 5, 2025
e2ede5f
clean up authenticate context
wobsoriano Aug 6, 2025
c98d1ed
clean up authenticate context
wobsoriano Aug 6, 2025
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
5 changes: 5 additions & 0 deletions .changeset/hot-tables-worry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@clerk/backend": minor
---

WIP M2M Tokens
91 changes: 89 additions & 2 deletions packages/backend/src/api/endpoints/MachineTokensApi.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,102 @@
import { joinPaths } from '../../util/path';
import type { ClerkBackendApiRequestOptions } from '../request';
import type { MachineToken } from '../resources/MachineToken';
import { AbstractAPI } from './AbstractApi';

const basePath = '/m2m_tokens';

type WithMachineSecret<T> = T & { machineSecret?: string | null };

type CreateMachineTokenParams = WithMachineSecret<{
claims?: Record<string, any> | null;
secondsUntilExpiration?: number | null;
}>;

type UpdateMachineTokenParams = WithMachineSecret<
{
m2mTokenId: string;
revocationReason?: string | null;
revoked?: boolean;
} & Pick<CreateMachineTokenParams, 'secondsUntilExpiration' | 'claims'>
>;

type RevokeMachineTokenParams = WithMachineSecret<{
m2mTokenId: string;
revocationReason?: string | null;
}>;

type VerifyMachineTokenParams = WithMachineSecret<{
secret: string;
}>;

export class MachineTokensApi extends AbstractAPI {
async verifySecret(secret: string) {
#requireMachineSecret(machineSecret?: string | null): asserts machineSecret is string {
if (!machineSecret) {
throw new Error('A machine secret is required.');
}
}

async create(params: CreateMachineTokenParams) {
const { machineSecret, ...bodyParams } = params;
this.#requireMachineSecret(machineSecret);
return this.request<MachineToken>({
method: 'POST',
path: basePath,
bodyParams,
headerParams: {
Authorization: `Bearer ${machineSecret}`,
},
});
}

async update(params: UpdateMachineTokenParams) {
const { m2mTokenId, machineSecret, ...bodyParams } = params;
this.#requireMachineSecret(machineSecret);
this.requireId(m2mTokenId);
return this.request<MachineToken>({
method: 'PATCH',
path: joinPaths(basePath, m2mTokenId),
bodyParams,
headerParams: {
Authorization: `Bearer ${machineSecret}`,
},
});
}

async revoke(params: RevokeMachineTokenParams) {
const { m2mTokenId, machineSecret, ...bodyParams } = params;
this.requireId(m2mTokenId);

const requestOptions: ClerkBackendApiRequestOptions = {
method: 'POST',
path: joinPaths(basePath, m2mTokenId, 'revoke'),
bodyParams,
};

if (machineSecret) {
requestOptions.headerParams = {
Authorization: `Bearer ${machineSecret}`,
};
}

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

async verifySecret(params: VerifyMachineTokenParams) {
const { secret, machineSecret } = params;

const requestOptions: ClerkBackendApiRequestOptions = {
method: 'POST',
path: joinPaths(basePath, 'verify'),
bodyParams: { secret },
});
};

if (machineSecret) {
requestOptions.headerParams = {
Authorization: `Bearer ${machineSecret}`,
};
}

return this.request<MachineToken>(requestOptions);
}
}
1 change: 1 addition & 0 deletions packages/backend/src/api/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export function createBackendApiClient(options: CreateBackendApiOptions) {
buildRequest({
...options,
skipApiVersionInUrl: true,
requireSecretKey: false,
}),
),
oauthApplications: new OAuthApplicationsApi(request),
Expand Down
6 changes: 3 additions & 3 deletions packages/backend/src/api/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,12 @@ export function buildRequest(options: BuildRequestOptions) {
// Build headers
const headers = new Headers({
'Clerk-API-Version': SUPPORTED_BAPI_VERSION,
'User-Agent': userAgent,
[constants.Headers.UserAgent]: userAgent,
...headerParams,
});

if (secretKey) {
headers.set('Authorization', `Bearer ${secretKey}`);
if (secretKey && !headers.has(constants.Headers.Authorization)) {
headers.set(constants.Headers.Authorization, `Bearer ${secretKey}`);
}

let res: Response | undefined;
Expand Down
1 change: 1 addition & 0 deletions packages/backend/src/api/resources/JSON.ts
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,7 @@ export interface MachineJSON extends ClerkResourceJSON {
export interface MachineTokenJSON extends ClerkResourceJSON {
object: typeof ObjectType.MachineToken;
name: string;
secret?: string;
subject: string;
scopes: string[];
claims: Record<string, any> | null;
Expand Down
4 changes: 3 additions & 1 deletion packages/backend/src/api/resources/MachineToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ export class MachineToken {
readonly creationReason: string | null,
readonly createdAt: number,
readonly updatedAt: number,
readonly secret?: string,
) {}

static fromJSON(data: MachineTokenJSON) {
static fromJSON(data: MachineTokenJSON): MachineToken {
return new MachineToken(
data.id,
data.name,
Expand All @@ -32,6 +33,7 @@ export class MachineToken {
data.creation_reason,
data.created_at,
data.updated_at,
data.secret,
);
}
}
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 verifyMachineToken(
): Promise<MachineTokenReturnType<MachineToken, MachineTokenVerificationError>> {
try {
const client = createBackendApiClient(options);
const verifiedToken = await client.machineTokens.verifySecret(secret);
const verifiedToken = await client.machineTokens.verifySecret({ secret });
return { data: verifiedToken, tokenType: TokenType.MachineToken, errors: undefined };
} catch (err: any) {
return handleClerkAPIError(TokenType.MachineToken, err, 'Machine token not found');
Expand Down
Loading