From 155ff5720dc521d7f65d3cf74a36522fe1a4e669 Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 28 Jan 2025 12:20:49 -0800 Subject: [PATCH] adding docs pages running prettier removing redundant ids a "valid" id was in fact - not valid removing unneccessary "s" reverting adding pages back in different folder some minor changes machine_id has a 255 character limit Some Colin Notes --- docs/machine-requests/machine-requests.mdx | 114 ++++++++++++++++++ docs/machine-requests/machine-tokens.mdx | 65 ++++++++++ docs/manifest.json | 17 +++ .../backend/authenticate-request.mdx | 35 ++++++ 4 files changed, 231 insertions(+) create mode 100644 docs/machine-requests/machine-requests.mdx create mode 100644 docs/machine-requests/machine-tokens.mdx diff --git a/docs/machine-requests/machine-requests.mdx b/docs/machine-requests/machine-requests.mdx new file mode 100644 index 0000000000..3dbf300186 --- /dev/null +++ b/docs/machine-requests/machine-requests.mdx @@ -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 }) +} +``` diff --git a/docs/machine-requests/machine-tokens.mdx b/docs/machine-requests/machine-tokens.mdx new file mode 100644 index 0000000000..b960dbb303 --- /dev/null +++ b/docs/machine-requests/machine-tokens.mdx @@ -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). diff --git a/docs/manifest.json b/docs/manifest.json index 4ab96626aa..ea97e3d1c0 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -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" + } + ] + ] } ] ] diff --git a/docs/references/backend/authenticate-request.mdx b/docs/references/backend/authenticate-request.mdx index debdc96f42..54e0829fa3 100644 --- a/docs/references/backend/authenticate-request.mdx +++ b/docs/references/backend/authenticate-request.mdx @@ -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` @@ -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, + }) +} +```