diff --git a/docs/machine-requests/machine-tokens.mdx b/docs/machine-requests/machine-tokens.mdx
index 3bd7ddc321..c73d021607 100644
--- a/docs/machine-requests/machine-tokens.mdx
+++ b/docs/machine-requests/machine-tokens.mdx
@@ -5,7 +5,7 @@ description: Learn about machine tokens and how to validate them in your backend
**Machine tokens** are JWTs used to authenticate machine requests your application makes.
-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.
+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.
## Creating machine tokens
diff --git a/docs/machine-requests/manual-jwt.mdx b/docs/machine-requests/manual-jwt.mdx
new file mode 100644
index 0000000000..76448cb158
--- /dev/null
+++ b/docs/machine-requests/manual-jwt.mdx
@@ -0,0 +1,134 @@
+---
+title: Manual JWT verification
+description: Learn how to manually verify Clerk-generated machine tokens (JWTs).
+---
+
+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.
+
+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.
+
+> [!TIP]
+> 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.
+
+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.
+
+## Use `authenticateRequest()` to verify a machine token
+
+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*.
+
+For more information, including usage with higher-level SDKs, see the [`authenticateRequest()` reference](/docs/references/backend/authenticate-request).
+
+For example, the following code snippet uses the `authenticateRequest()` method to verify a machine token in a simple node http server.
+
+```tsx
+import { createClerkClient } from '@clerk/backend';
+import http from 'http';
+
+const clerk = createClerkClient({ secretKey: process.env.CLERK_SECRET_KEY });
+
+const server = http.createServer(async (req, res) => {
+ try {
+ const { isMachineAuthenticated } = await clerk.authenticateRequest(req, {
+ entity: 'machine',
+ });
+
+ if (!isMachineAuthenticated) {
+ res.writeHead(401);
+ res.end(JSON.stringify({ message: 'Unauthorized' }));
+ return;
+ }
+
+ await runCronJob();
+
+ res.writeHead(200);
+ res.end(JSON.stringify({ message: 'Success' }));
+ } catch (err) {
+ res.writeHead(500);
+ res.end(JSON.stringify({ message: 'Internal Server Error' }));
+ }
+});
+
+server.listen(3000, () => {
+ console.log('Server running on port 3000');
+});
+```
+
+
+## Manually verify a machine token
+
+
+ ### Retrieve the machine token
+
+ Retrieve the machine token from the `Authorization` header.
+
+ ### Get your instance's public key
+
+ Use one of the three ways to obtain your public key:
+
+ 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).
+ 1. Use your **Frontend API URL** in JWKS format, also known as **JWKS URL**. The format is `https:///.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**.
+ 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**.
+
+ ### Verify the token signature
+
+ To verify the token signature:
+
+ 1. Use your instance's public key to verify the token's signature.
+ 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.
+ 1. Validate that the `sub` (subject) claim starts with `mch_`. This ensures the token is a machine token and not a session token.
+
+ ### Finished
+
+ 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.
+
+
+### Example
+
+The following example manually verifies a machine token.
+
+```tsx
+import jwt from 'jsonwebtoken'
+
+export default async function (req: Request, res: Response) {
+ // Your public key should be set as an environment variable
+ const publicKey = process.env.CLERK_PEM_PUBLIC_KEY
+
+ // Get the machine token from the Authorization header
+ const authHeader = req.headers.authorization
+ if (!authHeader) {
+ res.status(401).json({ error: 'No machine token provided' })
+ return
+ }
+
+ // Remove 'Bearer ' prefix if present
+ const token = authHeader.replace('Bearer ', '')
+
+ try {
+ const options = { algorithms: ['RS256'] }
+ const decoded = jwt.verify(token, publicKey, options)
+
+ // Validate the token's expiration (exp) and not before (nbf) claims
+ const currentTime = Math.floor(Date.now() / 1000)
+ if (decoded.exp < currentTime || decoded.nbf > currentTime) {
+ throw new Error('Token is expired or not yet valid')
+ }
+
+ // Validate that this is a machine token by checking the sub claim
+ if (!decoded.sub?.startsWith('mch_')) {
+ throw new Error('Not a valid machine token')
+ }
+
+ // The machine is authenticated. You can get the machine ID from the sub claim
+ const machineId = decoded.sub
+
+ res.status(200).json({
+ machineId,
+ claims: decoded
+ })
+ } catch (error) {
+ res.status(401).json({
+ error: error.message,
+ })
+ }
+}
+```
diff --git a/docs/machine-requests/machine-requests.mdx b/docs/machine-requests/overview.mdx
similarity index 59%
rename from docs/machine-requests/machine-requests.mdx
rename to docs/machine-requests/overview.mdx
index d66f137079..4de5a74280 100644
--- a/docs/machine-requests/machine-requests.mdx
+++ b/docs/machine-requests/overview.mdx
@@ -3,8 +3,6 @@ title: Machine-to-Machine Requests
description: Learn how to use machine tokens to make and verify authenticated requests.
---
-## Introduction
-
Machine-to-machine (M2M) authentication allows services, scripts, or devices to securely communicate with each other without the need for a user's session.
For example, you might need machine tokens for:
@@ -15,7 +13,7 @@ For example, you might need machine tokens for:
## Creating Machine Requests
-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.
+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.
> [!WARNING]
> 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:
1. Using Clerk's Backend SDKs (recommended)
2. Manually verifying the machine token JWT using your instance's public key.
-### Examples Using Clerk's Backend SDKs
+### Using Clerk's Backend SDKs
+
+By default, authenticating requests, will default to authenticating user [session requests](/docs/session-requests/overview).
+
+For machine requests, you can use the `entity: 'machine'` option to authenticate requests.
+
+> [!WARNING]
+> The `entity: 'machine'` option is only available in the Clerk Next.js SDK and Clerk Backend Javascript SDK during the beta.
-
+#### Examples
+
+
```tsx import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";
-
const isMachineRoute = createRouteMatcher(["/api/cron"]);
export default clerkMiddleware(async (auth, req) => {
@@ -85,54 +91,21 @@ You can verify machine tokens in two ways:
import { NextResponse } from 'next/server';
import { runCronJob } from './cronJob';
- export const POST = async () => {
- const { isMachineAuthenticated } = await auth({ entity: 'machine' });
+ export const POST = async () => {
+ const { isMachineAuthenticated } = await auth({ entity: 'machine' });
- if (!isMachineAuthenticated) {
- return NextResponse.json({ message: 'Unauthorized' }, { status: 401 });
- }
+ if (!isMachineAuthenticated) {
+ return NextResponse.json({ message: 'Unauthorized' }, { status: 401 });
+ }
- await runCronJob();
+ await runCronJob();
- return NextResponse.json({ message: 'Cron job ran' });
- };
+ return NextResponse.json({ message: 'Cron job ran' });
+ };
```
-
-
- ```tsx
- import { createClerkClient } from '@clerk/backend';
- import http from 'http';
-
- const clerk = createClerkClient({ secretKey: process.env.CLERK_SECRET_KEY });
-
- const server = http.createServer(async (req, res) => {
- try {
- const { isValid } = await clerk.authenticateRequest(req, {
- entity: 'machine',
- });
-
- if (!isValid) {
- res.writeHead(401);
- res.end(JSON.stringify({ message: 'Unauthorized' }));
- return;
- }
-
- await runCronJob();
-
- res.writeHead(200);
- res.end(JSON.stringify({ message: 'Success' }));
- } catch (err) {
- res.writeHead(500);
- res.end(JSON.stringify({ message: 'Internal Server Error' }));
- }
- });
-
- server.listen(3000, () => {
- console.log('Server running on port 3000');
- });
- ```
-
+### Manually verifying the machine token JWT
+See the [manual JWT verification](/docs/machine-requests/manual-jwt) guide for more information.
diff --git a/docs/manifest.json b/docs/manifest.json
index 95bb2cf085..1ca1ebb467 100644
--- a/docs/manifest.json
+++ b/docs/manifest.json
@@ -785,12 +785,16 @@
"items": [
[
{
- "title": "Machine requests",
- "href": "/docs/machine-requests/machine-requests"
+ "title": "Overview",
+ "href": "/docs/machine-requests/overview"
},
{
"title": "Machine tokens",
"href": "/docs/machine-requests/machine-tokens"
+ },
+ {
+ "title": "Manual JWT Verification",
+ "href": "/docs/machine-requests/manual-jwt"
}
]
]
diff --git a/docs/references/backend/authenticate-request.mdx b/docs/references/backend/authenticate-request.mdx
index 27353efcf2..f528121099 100644
--- a/docs/references/backend/authenticate-request.mdx
+++ b/docs/references/backend/authenticate-request.mdx
@@ -61,10 +61,10 @@ It is recommended to set these options as [environment variables](/docs/deployme
---
- - 'entity?'
- - 'session' | 'machine'
+ - `entity?`
+ - `user` | `machine`
- 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`
+ 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`
---
@@ -205,6 +205,34 @@ export async function GET(req: Request) {
}
```
+### Machine-to-machine authentication
+
+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`.
+Read more about [machine-to-machine authentication](/docs/machine-requests/overview).
+
+```tsx
+import { createClerkClient } from '@clerk/backend'
+
+export async function GET(req: Request) {
+ const clerkClient = createClerkClient({
+ secretKey: process.env.CLERK_SECRET_KEY,
+ publishableKey: process.env.CLERK_PUBLISHABLE_KEY,
+ })
+
+ const { isMachineAuthenticated } = await clerkClient.authenticateRequest(req, {
+ entity: 'machine',
+ })
+
+ if (!isMachineAuthenticated) {
+ return Response.json({ status: 401 })
+ }
+
+ // Add logic to perform protected actions
+
+ return Response.json({ message: 'This is a machine-to-machine reply' })
+}
+```
+
### Networkless token verification
{/* Note: this example is duped from /authenticate-request. Probably a good opportunity to use a partial here */}
diff --git a/docs/references/backend/types/auth-object.mdx b/docs/references/backend/types/auth-object.mdx
index 8f8da2b570..019897144b 100644
--- a/docs/references/backend/types/auth-object.mdx
+++ b/docs/references/backend/types/auth-object.mdx
@@ -1,9 +1,9 @@
---
title: Auth object
-description: The Auth object contains information about the current user's session.
+description: The Auth object contains information about a signed-in user or an authenticated machine request.
---
-The `Auth` object contains important information like the current user's session ID, user ID, and organization ID. It also contains methods to check for permissions and retrieve the current user's session token.
+The `Auth` object contains important information about an authenticated request. This includes a user's session ID, user ID, and organization ID. Or if the request is from a [machine](/docs/machine-requests/overview), it contains the machine's ID and machine token claims.
## How to access the `Auth` object
@@ -20,7 +20,36 @@ The `Auth` object is available on the `request` object in server contexts. Some
| Tanstack Start | [`getAuth()`](/docs/references/tanstack-start/get-auth) |
| Other | `request.auth` |
-## Properties
+Depending on the request, the `Auth` object will contain different properties.
+
+- If the request wasn't authenticated, the `Auth` object will be empty.
+- If the request is from a signed-in user, the `Auth` object will contain [properties for that user](/docs/references/backend/types/auth-object#signed-in-user-properties).
+- If the request is from a machine, the `Auth` object will contain [properties for that machine request](/docs/references/backend/types/auth-object#machine-request-properties).
+
+## Machine request properties
+
+
+ - `machineId`
+ - `string`
+
+ The ID of the machine, created by the [machine token](/docs/machine-requests/machine-tokens).
+
+ ---
+
+ - `claims`
+ - `JwtPayload`
+
+ Custom claims from the [machine token](/docs/machine-requests/machine-tokens).
+
+ ---
+
+ - `entity`
+ - `machine`
+
+ The type of the authenticated entity.
+
+
+## Signed-in user properties
- `sessionId`
@@ -79,6 +108,15 @@ The `Auth` object is available on the `request` object in server contexts. Some
---
+ ---
+
+ - `entity`
+ - `user`
+
+ The type of the authenticated entity.
+
+ ---
+
- [`has()`](#has)
- (isAuthorizedParams: [CheckAuthorizationParamsWithCustomPermissions](#check-authorization-params-with-custom-permissions)) => boolean
@@ -434,3 +472,21 @@ The following is an example of the `Auth` object with an active organization:
},
}
```
+
+## `Auth` object example of a machine request
+
+```js {{ mark: [8], prettier: false }}
+{
+ machineId: 'mch_cron_job',
+ claims: {
+ azp: 'http://localhost:3000',
+ exp: 1666622607,
+ iat: 1666622547,
+ iss: 'https://clerk.quiet.muskox-85.lcl.dev',
+ nbf: 1666622537,
+ sid: 'sess_123',
+ sub: 'user_123',
+ some_custom_claim: 'some_value',
+ },
+}
+```
diff --git a/docs/references/nextjs/auth.mdx b/docs/references/nextjs/auth.mdx
index 000e1468f8..55debb5e76 100644
--- a/docs/references/nextjs/auth.mdx
+++ b/docs/references/nextjs/auth.mdx
@@ -3,12 +3,20 @@ title: '`auth()`'
description: Access minimal authentication data for managing sessions and data fetching.
---
-The `auth()` helper returns the [`Auth`](/docs/references/backend/types/auth-object) object of the currently active user, as well as the [`redirectToSignIn()`](#redirect-to-sign-in) method.
+The `auth()` helper returns the [`Auth`](/docs/references/backend/types/auth-object) object of the currently active user, as well as the [`redirectToSignIn()`](#redirect-to-sign-in) method. For machine requests, it returns the machine's auth object.
- Only available for App Router.
- Only works on the server-side, such as in Server Components, Route Handlers, and Server Actions.
- Requires [`clerkMiddleware()`](/docs/references/nextjs/clerk-middleware) to be configured.
+## `auth({ entity })`
+
+The `auth()` helper needs to know if it should authenticate a [session request](/docs/session-requests/overview) or a [machine request](/docs/machine-requests/overview).
+By passing in the `entity` parameter, you can specify the type of request you want to authenticate. By default, it will authenticate a session request.
+
+> [!NOTE]
+> If you wnat to check **both** session and machine requests, you can pass in the `entity` parameter as `'any'`.
+
## `auth.protect()`
`auth` includes a single property, the `protect()` method, which you can use in two ways:
@@ -151,3 +159,44 @@ export async function GET() {
}
}
```
+
+### Use `auth()` for machine requests
+
+For machine requests, the `auth()` helper returns the machine's auth object.
+
+ ```tsx import { auth } from '@clerk/nextjs/server';
+import { NextResponse } from 'next/server';
+import { runCronJob } from './cronJob';
+
+export const POST = async () => {
+ const { isMachineAuthenticated } = await auth({ entity: 'machine' });
+
+ if (!isMachineAuthenticated) {
+ return NextResponse.json({ message: 'Unauthorized' }, { status: 401 });
+ }
+
+ await runCronJob();
+
+ return NextResponse.json({ message: 'Cron job ran' });
+};
+```
+
+### Use `auth()` for both session and machine requests
+
+For machine requests, the `auth()` helper returns the machine's auth object.
+
+ ```tsx import { auth } from '@clerk/nextjs/server';
+import { NextResponse } from 'next/server';
+import { runCronJob } from './cronJob';
+
+export const POST = async () => {
+ const { isMachineAuthenticated, isSignedIn } = await auth({ entity: 'any' });
+
+ if (isMachineAuthenticated || isSignedIn) {
+ return NextResponse.json({ message: 'You are either a user or a machine!' }, { status: 200 });
+ }
+
+ return NextResponse.json({ message: 'You are not signed in!' }, { status: 401 });
+};
+```
+