Skip to content

Commit ee56c1d

Browse files
committed
adding manual jwt verification
improving authenticateRequest docs page updating auth object type editing nextjs auth docs page Removing unnecessary preface
1 parent 1edb744 commit ee56c1d

File tree

7 files changed

+303
-59
lines changed

7 files changed

+303
-59
lines changed

Diff for: docs/machine-requests/machine-tokens.mdx

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ description: Learn about machine tokens and how to validate them in your backend
55

66
**Machine tokens** are JWTs used to authenticate machine requests your application makes.
77

8-
Unlike [session tokens](/docs/session-requests/session-tokens), machine tokens are not associated with a user or session. Instead **you** are responsible for determining the identity of the machine making the request.
8+
They are similiar to [session tokens](/docs/session-requests/session-tokens) however, unlike session tokens, machine tokens are not associated with a user. Instead __you__ are responsible for determining the identity of the machine making the request.
99

1010
## Creating machine tokens
1111

Diff for: docs/machine-requests/manual-jwt.mdx

+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
---
2+
title: Manual JWT verification
3+
description: Learn how to manually verify Clerk-generated machine tokens (JWTs).
4+
---
5+
6+
Your Clerk-generated [machine tokens](/docs/machine-requests/machine-tokens) are essentially JWTs which are signed using your instance's private key and can be verified using your instance's public key.
7+
8+
For every machine request, you must validate the token to ensure it hasn't expired or been tampered with (i.e., it's authentic and secure). Additionally, you likely want to differentiate between machine and user requests. If these validations succeed, then the machine is authenticated to your application.
9+
10+
> [!TIP]
11+
> To differentiate between a machine token and user token, the `sub` claim in a machine token starts with `mch_` instead of `user_` on a session token.
12+
13+
The `authenticateRequest()` method from the JavaScript Backend SDK handles these validations for you. Alternatively, you can manually verify the token without using the SDK. See the following sections for more information.
14+
15+
## Use `authenticateRequest()` to verify a machine token
16+
17+
The [`authenticateRequest()`](/docs/references/backend/authenticate-request) method from the JavaScript Backend SDK accepts the `request` object, and by using the `entity: "machine"` option, you can authenticate the request *as a machine request* instead of the default *user request*.
18+
19+
For more information, including usage with higher-level SDKs, see the [`authenticateRequest()` reference](/docs/references/backend/authenticate-request).
20+
21+
For example, the following code snippet uses the `authenticateRequest()` method to verify a machine token in a simple node http server.
22+
23+
```tsx
24+
import { createClerkClient } from '@clerk/backend';
25+
import http from 'http';
26+
27+
const clerk = createClerkClient({ secretKey: process.env.CLERK_SECRET_KEY });
28+
29+
const server = http.createServer(async (req, res) => {
30+
try {
31+
const { isMachineAuthenticated } = await clerk.authenticateRequest(req, {
32+
entity: 'machine',
33+
});
34+
35+
if (!isMachineAuthenticated) {
36+
res.writeHead(401);
37+
res.end(JSON.stringify({ message: 'Unauthorized' }));
38+
return;
39+
}
40+
41+
await runCronJob();
42+
43+
res.writeHead(200);
44+
res.end(JSON.stringify({ message: 'Success' }));
45+
} catch (err) {
46+
res.writeHead(500);
47+
res.end(JSON.stringify({ message: 'Internal Server Error' }));
48+
}
49+
});
50+
51+
server.listen(3000, () => {
52+
console.log('Server running on port 3000');
53+
});
54+
```
55+
56+
57+
## Manually verify a machine token
58+
59+
<Steps>
60+
### Retrieve the machine token
61+
62+
Retrieve the machine token from the `Authorization` header.
63+
64+
### Get your instance's public key
65+
66+
Use one of the three ways to obtain your public key:
67+
68+
1. Use the Backend API in JSON Web Key Set (JWKS) format at the following endpoint [https://api.clerk.com/v1/jwks](https://clerk.com/docs/reference/backend-api/tag/JWKS#operation/GetJWKS).
69+
1. Use your **Frontend API URL** in JWKS format, also known as **JWKS URL**. The format is `https://<YOUR_FRONTEND_API>/.well-known/jwks.json`. To retrieve your **JWKS URL**, navigate to the [**API keys**](https://dashboard.clerk.com/last-active?path=api-keys) page in the Clerk Dashboard and select **Show JWT public key**.
70+
1. Use your **PEM Public Key**. To retrieve it, navigate to the [**API keys**](https://dashboard.clerk.com/last-active?path=api-keys) page in the Clerk Dashboard and select **Show JWT Public Key**.
71+
72+
### Verify the token signature
73+
74+
To verify the token signature:
75+
76+
1. Use your instance's public key to verify the token's signature.
77+
1. Validate that the token isn't expired by checking the `exp` ([expiration time](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.4)) and `nbf` ([not before](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.5)) claims.
78+
1. Validate that the `sub` (subject) claim starts with `mch_`. This ensures the token is a machine token and not a session token.
79+
80+
### Finished
81+
82+
If the above process succeeds, the machine is considered authenticated to make requests to your application. You can also retrieve the machine ID from the token's `sub` claim.
83+
</Steps>
84+
85+
### Example
86+
87+
The following example manually verifies a machine token.
88+
89+
```tsx
90+
import jwt from 'jsonwebtoken'
91+
92+
export default async function (req: Request, res: Response) {
93+
// Your public key should be set as an environment variable
94+
const publicKey = process.env.CLERK_PEM_PUBLIC_KEY
95+
96+
// Get the machine token from the Authorization header
97+
const authHeader = req.headers.authorization
98+
if (!authHeader) {
99+
res.status(401).json({ error: 'No machine token provided' })
100+
return
101+
}
102+
103+
// Remove 'Bearer ' prefix if present
104+
const token = authHeader.replace('Bearer ', '')
105+
106+
try {
107+
const options = { algorithms: ['RS256'] }
108+
const decoded = jwt.verify(token, publicKey, options)
109+
110+
// Validate the token's expiration (exp) and not before (nbf) claims
111+
const currentTime = Math.floor(Date.now() / 1000)
112+
if (decoded.exp < currentTime || decoded.nbf > currentTime) {
113+
throw new Error('Token is expired or not yet valid')
114+
}
115+
116+
// Validate that this is a machine token by checking the sub claim
117+
if (!decoded.sub?.startsWith('mch_')) {
118+
throw new Error('Not a valid machine token')
119+
}
120+
121+
// The machine is authenticated. You can get the machine ID from the sub claim
122+
const machineId = decoded.sub
123+
124+
res.status(200).json({
125+
machineId,
126+
claims: decoded
127+
})
128+
} catch (error) {
129+
res.status(401).json({
130+
error: error.message,
131+
})
132+
}
133+
}
134+
```

Diff for: docs/machine-requests/machine-requests.mdx renamed to docs/machine-requests/overview.mdx

+22-49
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ title: Machine-to-Machine Requests
33
description: Learn how to use machine tokens to make and verify authenticated requests.
44
---
55

6-
## Introduction
7-
86
Machine-to-machine (M2M) authentication allows services, scripts, or devices to securely communicate with each other without the need for a user's session.
97

108
For example, you might need machine tokens for:
@@ -15,7 +13,7 @@ For example, you might need machine tokens for:
1513

1614
## Creating Machine Requests
1715

18-
If your client is a backend service, you can create a machine token using the [clerk backend api](https://clerk.com/docs/reference/backend-api/tag/machine-tokens). Then use the created token in the `Authorization` header of outgoing request.
16+
You can create a machine token using the [clerk backend api](https://clerk.com/docs/reference/backend-api/tag/machine-tokens). Then use the created token in the `Authorization` header of outgoing request.
1917

2018
> [!WARNING]
2119
> Because creating machine tokens using [clerk backend api](https://clerk.com/docs/reference/backend-api/tag/machine-tokens), it is subject to the [Backend API rate limits](/docs/rate-limits).
@@ -57,12 +55,20 @@ You can verify machine tokens in two ways:
5755
1. Using Clerk's Backend SDKs (recommended)
5856
2. Manually verifying the machine token JWT using your instance's public key.
5957

60-
### Examples Using Clerk's Backend SDKs
58+
### Using Clerk's Backend SDKs
59+
60+
By default, authenticating requests, will default to authenticating user [session requests](/docs/session-requests/overview).
61+
62+
For machine requests, you can use the `entity: 'machine'` option to authenticate requests.
63+
64+
> [!WARNING]
65+
> The `entity: 'machine'` option is only available in the Clerk Next.js SDK and Clerk Backend Javascript SDK during the beta.
6166
62-
<Tabs items={['Next.js Middleware', 'Next.js API Route', 'Node.js HTTP Server']}>
67+
#### Examples
68+
69+
<Tabs items={['Next.js Middleware', 'Next.js API Route']}>
6370
<Tab>
6471
```tsx import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";
65-
6672
const isMachineRoute = createRouteMatcher(["/api/cron"]);
6773

6874
export default clerkMiddleware(async (auth, req) => {
@@ -85,54 +91,21 @@ You can verify machine tokens in two ways:
8591
import { NextResponse } from 'next/server';
8692
import { runCronJob } from './cronJob';
8793

88-
export const POST = async () => {
89-
const { isMachineAuthenticated } = await auth({ entity: 'machine' });
94+
export const POST = async () => {
95+
const { isMachineAuthenticated } = await auth({ entity: 'machine' });
9096

91-
if (!isMachineAuthenticated) {
92-
return NextResponse.json({ message: 'Unauthorized' }, { status: 401 });
93-
}
97+
if (!isMachineAuthenticated) {
98+
return NextResponse.json({ message: 'Unauthorized' }, { status: 401 });
99+
}
94100

95-
await runCronJob();
101+
await runCronJob();
96102

97-
return NextResponse.json({ message: 'Cron job ran' });
98-
};
103+
return NextResponse.json({ message: 'Cron job ran' });
104+
};
99105
```
100106
</Tab>
101-
102-
<Tab>
103-
```tsx
104-
import { createClerkClient } from '@clerk/backend';
105-
import http from 'http';
106-
107-
const clerk = createClerkClient({ secretKey: process.env.CLERK_SECRET_KEY });
108-
109-
const server = http.createServer(async (req, res) => {
110-
try {
111-
const { isValid } = await clerk.authenticateRequest(req, {
112-
entity: 'machine',
113-
});
114-
115-
if (!isValid) {
116-
res.writeHead(401);
117-
res.end(JSON.stringify({ message: 'Unauthorized' }));
118-
return;
119-
}
120-
121-
await runCronJob();
122-
123-
res.writeHead(200);
124-
res.end(JSON.stringify({ message: 'Success' }));
125-
} catch (err) {
126-
res.writeHead(500);
127-
res.end(JSON.stringify({ message: 'Internal Server Error' }));
128-
}
129-
});
130-
131-
server.listen(3000, () => {
132-
console.log('Server running on port 3000');
133-
});
134-
```
135-
</Tab>
136107
</Tabs>
137108

109+
### Manually verifying the machine token JWT
138110

111+
See the [manual JWT verification](/docs/machine-requests/manual-jwt) guide for more information.

Diff for: docs/manifest.json

+6-2
Original file line numberDiff line numberDiff line change
@@ -785,12 +785,16 @@
785785
"items": [
786786
[
787787
{
788-
"title": "Machine requests",
789-
"href": "/docs/machine-requests/machine-requests"
788+
"title": "Overview",
789+
"href": "/docs/machine-requests/overview"
790790
},
791791
{
792792
"title": "Machine tokens",
793793
"href": "/docs/machine-requests/machine-tokens"
794+
},
795+
{
796+
"title": "Manual JWT Verification",
797+
"href": "/docs/machine-requests/manual-jwt"
794798
}
795799
]
796800
]

Diff for: docs/references/backend/authenticate-request.mdx

+31-3
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,10 @@ It is recommended to set these options as [environment variables](/docs/deployme
6161

6262
---
6363

64-
- 'entity?'
65-
- 'session' | 'machine'
64+
- `entity?`
65+
- `user` | `machine`
6666

67-
Determines what type of authentication to perform. If set to `'session'`, the function will authenticate a user session. If set to `'machine'`, the function will authenticate a machine-to-machine request. Defaults to `session`
67+
Determines what type of authentication to perform. If set to `user`, the function will authenticate a user session. If set to `machine`, the function will authenticate a machine-to-machine request. Defaults to `user`
6868

6969
---
7070

@@ -205,6 +205,34 @@ export async function GET(req: Request) {
205205
}
206206
```
207207
208+
### Machine-to-machine authentication
209+
210+
By default, `authenticateRequest()` will authenticate a [session request](/docs/session-requests/overview). To authenticate a machine-to-machine request, you need to set the `entity` option to `machine`.
211+
Read more about [machine-to-machine authentication](/docs/machine-requests/overview).
212+
213+
```tsx
214+
import { createClerkClient } from '@clerk/backend'
215+
216+
export async function GET(req: Request) {
217+
const clerkClient = createClerkClient({
218+
secretKey: process.env.CLERK_SECRET_KEY,
219+
publishableKey: process.env.CLERK_PUBLISHABLE_KEY,
220+
})
221+
222+
const { isMachineAuthenticated } = await clerkClient.authenticateRequest(req, {
223+
entity: 'machine',
224+
})
225+
226+
if (!isMachineAuthenticated) {
227+
return Response.json({ status: 401 })
228+
}
229+
230+
// Add logic to perform protected actions
231+
232+
return Response.json({ message: 'This is a machine-to-machine reply' })
233+
}
234+
```
235+
208236
### Networkless token verification
209237
210238
{/* Note: this example is duped from /authenticate-request. Probably a good opportunity to use a partial here */}

0 commit comments

Comments
 (0)