Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Machine Token Docs #1725

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
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
114 changes: 114 additions & 0 deletions docs/machine-requests/machine-requests.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
---
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:

- Cron jobs that update your database
- Background workers processing queued tasks
- Microservices communicati\@ng with each other

## Creating Machine Requests

If your client is a backend service, you can create a [machine token](/docs/machine-requests/machine-tokens) and use it in the `Authorization` header of outgoing request.

### Creating requests with the JavaScript Backend SDK

Use the `clerkClient.machineTokens` object to create a [machine token](/docs/machine-requests/machine-tokens), then use the created token to make authenticated requests.

> [!WARNING]
> Creating machine tokens is subject to the [Backend API rate limits](/docs/backend-requests/resources/rate-limits)

```tsx
import { createClerkClient } from '@clerk/backend'

export default async function cronJob() {
const clerkClient = createClerkClient({ secretKey: process.env.CLERK_SECRET_KEY })

const { token } = await clerkClient.machineTokens.create({
machineId: 'mch_cron',
claims: {
permissions: ['read', 'write'],
},
expiresInSeconds: 60,
})

await fetch('https://api.example.com/cron', {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
message: 'Hello World!',
}),
})
}
```

## Verifying Machine Requests

For a machine request to be valid, it must include a valid [machine token](/docs/machine-requests/machine-tokens) in the Bearer `Authorization` header.

You can verify machine tokens in two ways:

1. Using Clerk's Backend SDK (recommended)
1. Manually verifying the JWT using your instance's public key.

### Verifying requests with the JavaScript Backend SDK

#### Using the `authenticateRequest()` method

You can use the `authenticateRequest()` method with the [JavaScript Backend SDK](/docs/references/backend/overview) to verify that the token is a valid machine token generated by Clerk.

```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, machineId } = await clerkClient.authenticateRequest(req, {
entity: 'machine',
})

if (!isMachineAuthenticated) {
return Response.json({ status: 401 })
}

return Response.json({
message: `Machine is authenticated with ID: ${machineId}`,
})
}
```

#### Using the `await auth()` Next.js helper

You can use the `await auth()` Next.js helper to verify that the request is authenticated and that the user is a machine.

NOTE FOR REVIEWER:

> [!NOTE]
> Currently the `auth()` helper does not support **any** parameters, adding the `entity` paramter would be a big change to the sdk.
> I think we can add this and default to `entity: 'user'` -- but I am not confident here, so we probably want some back and forth on this.
> Also, in the Next.js `auth()` function, will it know that the token is in the Authorization header? Not the cookie?

```tsx
import { auth } from '@clerk/nextjs/server'

export async function GET() {
const { isMachineAuthenticated, machineId } = await auth({ entity: 'machine' })

if (!isMachineAuthenticated) {
return new Response('Machine authentication failed.', { status: 401 })
}

return new Response(`Machine is authenticated with ID: ${machineId}`, { status: 200 })
}
```
65 changes: 65 additions & 0 deletions docs/machine-requests/machine-tokens.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
title: Machine tokens
description: Learn about machine tokens and how to validate them in your backend.
---

**Machine tokens** are JWTs used to authenticate machine requests.

Unlike [session tokens](/docs/backend-requests/resources/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.

## Creating machine tokens

### Machine ID

Every machine token you create needs to be associated with a `machine_id`. You can pick any value for the `machine_id` as long as it meets the following requirements:

- It must be prefixed with `mch_`
- It must only contain lowercase letters and numbers
- It must be 96 characters or less

> [!TIP]
> It is a good idea to have the `machine_id` correspond with the identity of the service generating the token. For example if you have a cron service, a `machine_id` of `mch_cron` would make sense.

- Examples:
- ✅ `mch_cron_service`
- ✅ `mch_background_worker`
- ✅ `mch_device_6580fc77_afca_47ac_8973_b7261d14e4c7`
- ❌ `user_2p94zsO6sBvVZR5Ca0KfBNLM36Z`
- ❌ `mch-invalid`
- ❌ `MCH_UPPERCASE`

### Claims

You can add custom claims to your machine token to include any additional information that your application might need. Claims are key-value pairs included in the token's payload, and they can convey important data such as permissions, roles, or any other attributes relevant to the machine's identity.

For example, it is a good practice to include any permissions that your service requires directly in the claims. This allows your backend to easily verify what actions the machine is authorized to perform.

> [!NOTE]
> You cannot add claims that are [already set by clerk](#default-machine-claims).

### Expires In Seconds

The `expiresInSeconds` parameter defines how long the machine token remains valid, specified in seconds. This parameter is optional and defaults to 60 seconds (1 minute).

If you need the machine token to be valid for a longer period of time, you can set the `expiresInSeconds` parameter to a higher value. However, keep in mind that longer-lived tokens can present a higher security risk if compromised, while shorter-lived tokens may require more frequent token generation, potentially impacting your [Backend API rate limits](/docs/backend-requests/resources/rate-limits). Therefore, it's important to balance token lifespan with security requirements and rate limit considerations.

### Clock Skew

The `allowedClockSkew` parameter provides a leeway in seconds to account for clock differences between servers. This setting affects the `nbf` (Not Before) claim in the token, calculated as `nbf = current_time - allowed_clock_skew`. The default value is 5 seconds.

Adjusting the clock skew helps prevent token validation failures due to minor time discrepancies between the issuing server and the verifying server.

## Default machine claims

Every generated token has default claims that cannot be overridden by custom claims. Clerk's default claims include:

- `exp`: expiration time - the time after which the token will expire, as a Unix timestamp. Determined using the **Token Expires In Seconds** request body parameter when creating machine tokens. See [RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.4) for more information.
- `iat`: issued at - the time at which the token was issued as a Unix timestamp. For example: `1516239022`. See [RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.6) for more information.
- `jti`: JWT ID - the ID of the token internally generated by Clerk. For example: `a1b2c3d4e5f67890abcd`. See [RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.7) for more information.
- `iss`: issuer - the Frontend API URL of your instance. For example: `https://clerk.your-site.com` for a production instance or `https://your-site.clerk.accounts.dev` for a development instance. See [RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.1) for more information.
- `nbf`: not before - the time before which the token is considered invalid, as a Unix timestamp. Determined using the **Allowed Clock Skew** request body parameter when creating machine tokens. See [RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.5) for more information.
- `sub`: subject - the ID of the machine that created the token. Determined using the **Machine ID** request body parameter when creating machine tokens. For example: `mch_123`. See [RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.2) for more information.

## Using Machine Tokens to create and verify requests

To start using machine tokens to create and verify requests, refer to [making machine requests](/docs/machine-requests/machine-requests).
17 changes: 17 additions & 0 deletions docs/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,23 @@
}
]
]
},
{
"title": "Machine Requests",
"collapse": true,
"tag": "(Beta)",
"items": [
[
{
"title": "Machine requests",
"href": "/docs/machine-requests/machine-requests"
},
{
"title": "Machine tokens",
"href": "/docs/machine-requests/machine-tokens"
}
]
]
}
]
]
Expand Down
35 changes: 35 additions & 0 deletions docs/references/backend/authenticate-request.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ It is recommended to set these options as [environment variables](/docs/deployme

---

- 'entity?'
- 'session' | '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`

---

- `domain?`
- `string`

Expand Down Expand Up @@ -225,3 +232,31 @@ export async function GET(req: Request) {
return Response.json({ message: 'This is a reply' })
}
```

### Machine token verification

The following example uses the `authenticateRequest()` to verify a machine token.

```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, machinedId } = await clerkClient.authenticateRequest(req, {
entity: 'machine',
})

if (!authReq.isMachineAuthenticated) {
return Response.json({ status: 401 })
}

return Response.json({
message: 'Machine is authenticated',
machineId,
})
}
```
Loading