HTTP Requests with Server Workers
HTTP requests are the simplest way to communicate with Server Workers. While they don't provide all the features of JazzRPC, they are a good solution when all you need is basic authentication.
They work by generating a short-lived token with generateAuthToken
and attaching it to the request headers as Authorization: Jazz <token>
.
The server can then verify the token with authenticateRequest
and get the account that the request was made by.
While the token is cryptographically secure, using non secure connections still makes you vulnerable to MITM attacks as - unlike JazzRPC - the request is not signed.
Replay attacks are mitigated by token expiration (default to 1 minute), but it's up to you to ensure that the token is not reused.
It is recommended to use HTTPS whenever possible.
Creating a Request
You can use any method to create a request; the most common is the fetch
API.
By default, the token is expected to be in the Authorization
header in the form of Jazz <token>
.
import {
function generateAuthToken(as?: Account): string
Generates an authentication token for a given account. This token can be used to authenticate a request. See {@link authenticateRequest } for more details.generateAuthToken } from "jazz-tools"; constconst response: Response
response = awaitfunction fetch(input: global.RequestInfo, init?: RequestInit): Promise<Response> (+2 overloads)
[MDN Reference](https://developer.mozilla.org/docs/Web/API/fetch)fetch('https://example.com', {RequestInit.headers?: HeadersInit | undefined
A Headers object, an object literal, or an array of two-item arrays to set request's headers.headers: {type Authorization: string
Authorization: `Jazz ${function generateAuthToken(as?: Account): string
Generates an authentication token for a given account. This token can be used to authenticate a request. See {@link authenticateRequest } for more details.generateAuthToken()}`, }, });
Authenticating requests
You can use the authenticateRequest
function to authenticate requests.
Attempting to authenticate a request without a token doesn't fail; it returns account
as undefined
. For endpoints that require authentication, ensure account
is defined in addition to any permission checks you may need.
import {
function authenticateRequest(request: Request, options?: { expiration?: number; loadAs?: Account; getToken?: (request: Request) => string | undefined | null; }): Promise<{ account?: Account; error?: never; } | { account?: never; error: { message: string; details?: unknown; }; }>
Authenticates a Request by verifying a signed authentication token. - If a token is not provided, the returned account is `undefined` and no error is returned. - If a valid token is provided, the signer account is returned. - If an invalid token is provided, an error is returned detailing the validation error, and the returned account is `undefined`.authenticateRequest } from "jazz-tools"; import {startWorker } from "jazz-tools/worker"; export async function
function startWorker<S extends (AccountClass<Account> & CoValueFromRaw<Account>) | CoreAccountSchema>(options: WorkerOptions<...>): Promise<{ worker: Loaded<S>; experimental: { inbox: { subscribe: Inbox["subscribe"]; } | { subscribe: () => void; }; }; waitForConnection(): Promise<void>; subscribeToConnectionChange(listener: (connected: boolean) => void): () => void; done: () => Promise<void>; shutdownWorker(): Promise<void>; }>
function GET(request: Request): Promise<Response>
GET(request: Request
request: Request) { constworker = await
const worker: { worker: NonNullable<Account | ({ readonly [x: string]: any; } & Account) | null>; experimental: { inbox: { subscribe: Inbox["subscribe"]; } | { subscribe: () => void; }; }; waitForConnection(): Promise<void>; subscribeToConnectionChange(listener: (connected: boolean) => void): () => void; done: () => Promise<void>; shutdownWorker(): Promise<void>; }
startWorker({
startWorker<(CoValueClass<Account> & { fromNode: (typeof Account)["fromNode"]; } & CoValueFromRaw<Account>) | CoreAccountSchema<...>>(options: WorkerOptions<...>): Promise<...>
syncServer?: string | undefined
syncServer: "wss://cloud.jazz.tools/?key=your-api-key",accountID?: string | undefined
accountID:var process: NodeJS.Process
process.NodeJS.Process.env: NodeJS.ProcessEnv
The `process.env` property returns an object containing the user environment. See [`environ(7)`](http://man7.org/linux/man-pages/man7/environ.7.html). An example of this object looks like: ```js { TERM: 'xterm-256color', SHELL: '/usr/local/bin/bash', USER: 'maciej', PATH: '~/.bin/:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin', PWD: '/Users/maciej', EDITOR: 'vim', SHLVL: '1', HOME: '/Users/maciej', LOGNAME: 'maciej', _: '/usr/local/bin/node' } ``` It is possible to modify this object, but such modifications will not be reflected outside the Node.js process, or (unless explicitly requested) to other `Worker` threads. In other words, the following example would not work: ```bash node -e 'process.env.foo = "bar"' && echo $foo ``` While the following will: ```js import { env } from 'node:process'; env.foo = 'bar'; console.log(env.foo); ``` Assigning a property on `process.env` will implicitly convert the value to a string. **This behavior is deprecated.** Future versions of Node.js may throw an error when the value is not a string, number, or boolean. ```js import { env } from 'node:process'; env.test = null; console.log(env.test); // => 'null' env.test = undefined; console.log(env.test); // => 'undefined' ``` Use `delete` to delete a property from `process.env`. ```js import { env } from 'node:process'; env.TEST = 1; delete env.TEST; console.log(env.TEST); // => undefined ``` On Windows operating systems, environment variables are case-insensitive. ```js import { env } from 'node:process'; env.TEST = 1; console.log(env.test); // => 1 ``` Unless explicitly specified when creating a `Worker` instance, each `Worker` thread has its own copy of `process.env`, based on its parent thread's `process.env`, or whatever was specified as the `env` option to the `Worker` constructor. Changes to `process.env` will not be visible across `Worker` threads, and only the main thread can make changes that are visible to the operating system or to native add-ons. On Windows, a copy of `process.env` on a `Worker` instance operates in a case-sensitive manner unlike the main thread.env.string | undefined
JAZZ_WORKER_ACCOUNT,accountSecret?: string | undefined
accountSecret:var process: NodeJS.Process
process.NodeJS.Process.env: NodeJS.ProcessEnv
The `process.env` property returns an object containing the user environment. See [`environ(7)`](http://man7.org/linux/man-pages/man7/environ.7.html). An example of this object looks like: ```js { TERM: 'xterm-256color', SHELL: '/usr/local/bin/bash', USER: 'maciej', PATH: '~/.bin/:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin', PWD: '/Users/maciej', EDITOR: 'vim', SHLVL: '1', HOME: '/Users/maciej', LOGNAME: 'maciej', _: '/usr/local/bin/node' } ``` It is possible to modify this object, but such modifications will not be reflected outside the Node.js process, or (unless explicitly requested) to other `Worker` threads. In other words, the following example would not work: ```bash node -e 'process.env.foo = "bar"' && echo $foo ``` While the following will: ```js import { env } from 'node:process'; env.foo = 'bar'; console.log(env.foo); ``` Assigning a property on `process.env` will implicitly convert the value to a string. **This behavior is deprecated.** Future versions of Node.js may throw an error when the value is not a string, number, or boolean. ```js import { env } from 'node:process'; env.test = null; console.log(env.test); // => 'null' env.test = undefined; console.log(env.test); // => 'undefined' ``` Use `delete` to delete a property from `process.env`. ```js import { env } from 'node:process'; env.TEST = 1; delete env.TEST; console.log(env.TEST); // => undefined ``` On Windows operating systems, environment variables are case-insensitive. ```js import { env } from 'node:process'; env.TEST = 1; console.log(env.test); // => 1 ``` Unless explicitly specified when creating a `Worker` instance, each `Worker` thread has its own copy of `process.env`, based on its parent thread's `process.env`, or whatever was specified as the `env` option to the `Worker` constructor. Changes to `process.env` will not be visible across `Worker` threads, and only the main thread can make changes that are visible to the operating system or to native add-ons. On Windows, a copy of `process.env` on a `Worker` instance operates in a case-sensitive manner unlike the main thread.env.string | undefined
JAZZ_WORKER_SECRET,asActiveAccount?: boolean | undefined
If false, the worker will not set in the global account contextasActiveAccount: true, }); const {const account: Account | undefined
account,error } = await
const error: { message: string; details?: unknown; } | undefined
function authenticateRequest(request: Request, options?: { expiration?: number; loadAs?: Account; getToken?: (request: Request) => string | undefined | null; }): Promise<{ account?: Account; error?: never; } | { account?: never; error: { message: string; details?: unknown; }; }>
Authenticates a Request by verifying a signed authentication token. - If a token is not provided, the returned account is `undefined` and no error is returned. - If a valid token is provided, the signer account is returned. - If an invalid token is provided, an error is returned detailing the validation error, and the returned account is `undefined`.authenticateRequest(request: Request
request); // There was an error validating the token (e.g., invalid or expired) if (error) { return new
const error: { message: string; details?: unknown; } | undefined
var Response: new (body?: BodyInit | null, init?: ResponseInit) => Response
This Fetch API interface represents the response to a request. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response)Response(var JSON: JSON
An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.JSON.JSON.stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string (+1 overload)
Converts a JavaScript value to a JavaScript Object Notation (JSON) string.stringify(error), {
const error: { message: string; details?: unknown; }
ResponseInit.status?: number | undefined
status: 401 }); } if (!const account: Account | undefined
account) { return newvar Response: new (body?: BodyInit | null, init?: ResponseInit) => Response
This Fetch API interface represents the response to a request. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response)Response("Unauthorized", {ResponseInit.status?: number | undefined
status: 401 }); } return newvar Response: new (body?: BodyInit | null, init?: ResponseInit) => Response
This Fetch API interface represents the response to a request. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response)Response(var JSON: JSON
An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.JSON.JSON.stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string (+1 overload)
Converts a JavaScript value to a JavaScript Object Notation (JSON) string.stringify({message: string
message: `The request was made by ${const account: Account
account.Account.$jazz: AccountJazzApi<Account>
Jazz methods for Accounts are inside this property. This allows Accounts to be used as plain objects while still having access to Jazz methods.$jazz.CoValueJazzApi<Account>.id: string
id}` })); }
Multi-account environments
If you are using multiple accounts in your environment - for instance if your server starts multiple workers - or in general if you need to send and authenticate requests as a specific account, you can specify which one to use when generating the token or when authenticating the request.
Making a request as a specific account
generateAuthToken
accepts an optional account parameter, so you can generate a token for a specific account.
import {
function generateAuthToken(as?: Account): string
Generates an authentication token for a given account. This token can be used to authenticate a request. See {@link authenticateRequest } for more details.generateAuthToken } from "jazz-tools"; constconst response: Response
response = awaitfunction fetch(input: global.RequestInfo, init?: RequestInit): Promise<Response> (+2 overloads)
[MDN Reference](https://developer.mozilla.org/docs/Web/API/fetch)fetch('https://example.com', {RequestInit.headers?: HeadersInit | undefined
A Headers object, an object literal, or an array of two-item arrays to set request's headers.headers: {type Authorization: string
Authorization: `Jazz ${function generateAuthToken(as?: Account): string
Generates an authentication token for a given account. This token can be used to authenticate a request. See {@link authenticateRequest } for more details.generateAuthToken(const account: Account
account)}`, }, });
Authenticating a request as a specific account
Similarly, specify the account used to verify the token via the loadAs
option:
import {
function authenticateRequest(request: Request, options?: { expiration?: number; loadAs?: Account; getToken?: (request: Request) => string | undefined | null; }): Promise<{ account?: Account; error?: never; } | { account?: never; error: { message: string; details?: unknown; }; }>
Authenticates a Request by verifying a signed authentication token. - If a token is not provided, the returned account is `undefined` and no error is returned. - If a valid token is provided, the signer account is returned. - If an invalid token is provided, an error is returned detailing the validation error, and the returned account is `undefined`.authenticateRequest } from "jazz-tools"; import {startWorker } from "jazz-tools/worker"; export async function
function startWorker<S extends (AccountClass<Account> & CoValueFromRaw<Account>) | CoreAccountSchema>(options: WorkerOptions<...>): Promise<{ worker: Loaded<S>; experimental: { inbox: { subscribe: Inbox["subscribe"]; } | { subscribe: () => void; }; }; waitForConnection(): Promise<void>; subscribeToConnectionChange(listener: (connected: boolean) => void): () => void; done: () => Promise<void>; shutdownWorker(): Promise<void>; }>
function GET(request: Request): Promise<void>
GET(request: Request
request: Request) { const {
const worker: NonNullable<Account | ({ readonly [x: string]: any; } & Account) | null>
The worker account instance.worker } = awaitstartWorker({
startWorker<(CoValueClass<Account> & { fromNode: (typeof Account)["fromNode"]; } & CoValueFromRaw<Account>) | CoreAccountSchema<...>>(options: WorkerOptions<...>): Promise<...>
syncServer?: string | undefined
syncServer: "wss://cloud.jazz.tools/?key=your-api-key",accountID?: string | undefined
accountID:var process: NodeJS.Process
process.NodeJS.Process.env: NodeJS.ProcessEnv
The `process.env` property returns an object containing the user environment. See [`environ(7)`](http://man7.org/linux/man-pages/man7/environ.7.html). An example of this object looks like: ```js { TERM: 'xterm-256color', SHELL: '/usr/local/bin/bash', USER: 'maciej', PATH: '~/.bin/:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin', PWD: '/Users/maciej', EDITOR: 'vim', SHLVL: '1', HOME: '/Users/maciej', LOGNAME: 'maciej', _: '/usr/local/bin/node' } ``` It is possible to modify this object, but such modifications will not be reflected outside the Node.js process, or (unless explicitly requested) to other `Worker` threads. In other words, the following example would not work: ```bash node -e 'process.env.foo = "bar"' && echo $foo ``` While the following will: ```js import { env } from 'node:process'; env.foo = 'bar'; console.log(env.foo); ``` Assigning a property on `process.env` will implicitly convert the value to a string. **This behavior is deprecated.** Future versions of Node.js may throw an error when the value is not a string, number, or boolean. ```js import { env } from 'node:process'; env.test = null; console.log(env.test); // => 'null' env.test = undefined; console.log(env.test); // => 'undefined' ``` Use `delete` to delete a property from `process.env`. ```js import { env } from 'node:process'; env.TEST = 1; delete env.TEST; console.log(env.TEST); // => undefined ``` On Windows operating systems, environment variables are case-insensitive. ```js import { env } from 'node:process'; env.TEST = 1; console.log(env.test); // => 1 ``` Unless explicitly specified when creating a `Worker` instance, each `Worker` thread has its own copy of `process.env`, based on its parent thread's `process.env`, or whatever was specified as the `env` option to the `Worker` constructor. Changes to `process.env` will not be visible across `Worker` threads, and only the main thread can make changes that are visible to the operating system or to native add-ons. On Windows, a copy of `process.env` on a `Worker` instance operates in a case-sensitive manner unlike the main thread.env.string | undefined
JAZZ_WORKER_ACCOUNT,accountSecret?: string | undefined
accountSecret:var process: NodeJS.Process
process.NodeJS.Process.env: NodeJS.ProcessEnv
The `process.env` property returns an object containing the user environment. See [`environ(7)`](http://man7.org/linux/man-pages/man7/environ.7.html). An example of this object looks like: ```js { TERM: 'xterm-256color', SHELL: '/usr/local/bin/bash', USER: 'maciej', PATH: '~/.bin/:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin', PWD: '/Users/maciej', EDITOR: 'vim', SHLVL: '1', HOME: '/Users/maciej', LOGNAME: 'maciej', _: '/usr/local/bin/node' } ``` It is possible to modify this object, but such modifications will not be reflected outside the Node.js process, or (unless explicitly requested) to other `Worker` threads. In other words, the following example would not work: ```bash node -e 'process.env.foo = "bar"' && echo $foo ``` While the following will: ```js import { env } from 'node:process'; env.foo = 'bar'; console.log(env.foo); ``` Assigning a property on `process.env` will implicitly convert the value to a string. **This behavior is deprecated.** Future versions of Node.js may throw an error when the value is not a string, number, or boolean. ```js import { env } from 'node:process'; env.test = null; console.log(env.test); // => 'null' env.test = undefined; console.log(env.test); // => 'undefined' ``` Use `delete` to delete a property from `process.env`. ```js import { env } from 'node:process'; env.TEST = 1; delete env.TEST; console.log(env.TEST); // => undefined ``` On Windows operating systems, environment variables are case-insensitive. ```js import { env } from 'node:process'; env.TEST = 1; console.log(env.test); // => 1 ``` Unless explicitly specified when creating a `Worker` instance, each `Worker` thread has its own copy of `process.env`, based on its parent thread's `process.env`, or whatever was specified as the `env` option to the `Worker` constructor. Changes to `process.env` will not be visible across `Worker` threads, and only the main thread can make changes that are visible to the operating system or to native add-ons. On Windows, a copy of `process.env` on a `Worker` instance operates in a case-sensitive manner unlike the main thread.env.string | undefined
JAZZ_WORKER_SECRET, }); const {const account: Account | undefined
account,error } = await
const error: { message: string; details?: unknown; } | undefined
function authenticateRequest(request: Request, options?: { expiration?: number; loadAs?: Account; getToken?: (request: Request) => string | undefined | null; }): Promise<{ account?: Account; error?: never; } | { account?: never; error: { message: string; details?: unknown; }; }>
Authenticates a Request by verifying a signed authentication token. - If a token is not provided, the returned account is `undefined` and no error is returned. - If a valid token is provided, the signer account is returned. - If an invalid token is provided, an error is returned detailing the validation error, and the returned account is `undefined`.authenticateRequest(request: Request
request, {loadAs?: Account | undefined
loadAs:
const worker: NonNullable<Account | ({ readonly [x: string]: any; } & Account) | null>
The worker account instance.worker }); }
Custom token expiration
You can specify the expiration time of the token using the expiration
option. The default expiration time is 1 minute.
import {
function authenticateRequest(request: Request, options?: { expiration?: number; loadAs?: Account; getToken?: (request: Request) => string | undefined | null; }): Promise<{ account?: Account; error?: never; } | { account?: never; error: { message: string; details?: unknown; }; }>
Authenticates a Request by verifying a signed authentication token. - If a token is not provided, the returned account is `undefined` and no error is returned. - If a valid token is provided, the signer account is returned. - If an invalid token is provided, an error is returned detailing the validation error, and the returned account is `undefined`.authenticateRequest } from "jazz-tools"; export async functionfunction GET(request: Request): Promise<void>
GET(request: Request
request: Request) { const {const account: Account | undefined
account,error } = await
const error: { message: string; details?: unknown; } | undefined
function authenticateRequest(request: Request, options?: { expiration?: number; loadAs?: Account; getToken?: (request: Request) => string | undefined | null; }): Promise<{ account?: Account; error?: never; } | { account?: never; error: { message: string; details?: unknown; }; }>
Authenticates a Request by verifying a signed authentication token. - If a token is not provided, the returned account is `undefined` and no error is returned. - If a valid token is provided, the signer account is returned. - If an invalid token is provided, an error is returned detailing the validation error, and the returned account is `undefined`.authenticateRequest(request: Request
request, {expiration?: number | undefined
expiration: 1000 * 60 * 60 * 24 // 24 hours }); }
Custom token location
While using the Authorization
header using the Jazz <token>
format is the most common way to send the token, you can provide the token in any other way you want.
For example, you can send the token in the x-jazz-auth-token
header:
import {
function generateAuthToken(as?: Account): string
Generates an authentication token for a given account. This token can be used to authenticate a request. See {@link authenticateRequest } for more details.generateAuthToken } from "jazz-tools"; constconst response: Response
response = awaitfunction fetch(input: global.RequestInfo, init?: RequestInit): Promise<Response> (+2 overloads)
[MDN Reference](https://developer.mozilla.org/docs/Web/API/fetch)fetch('https://example.com', {RequestInit.headers?: HeadersInit | undefined
A Headers object, an object literal, or an array of two-item arrays to set request's headers.headers: { "x-jazz-auth-token":function generateAuthToken(as?: Account): string
Generates an authentication token for a given account. This token can be used to authenticate a request. See {@link authenticateRequest } for more details.generateAuthToken(), }, });
Then you can specify the location of the token using the getToken
option:
import {
function authenticateRequest(request: Request, options?: { expiration?: number; loadAs?: Account; getToken?: (request: Request) => string | undefined | null; }): Promise<{ account?: Account; error?: never; } | { account?: never; error: { message: string; details?: unknown; }; }>
Authenticates a Request by verifying a signed authentication token. - If a token is not provided, the returned account is `undefined` and no error is returned. - If a valid token is provided, the signer account is returned. - If an invalid token is provided, an error is returned detailing the validation error, and the returned account is `undefined`.authenticateRequest } from "jazz-tools"; export async functionfunction GET(request: Request): Promise<void>
GET(request: Request
request: Request) { const {const account: Account | undefined
account,error } = await
const error: { message: string; details?: unknown; } | undefined
function authenticateRequest(request: Request, options?: { expiration?: number; loadAs?: Account; getToken?: (request: Request) => string | undefined | null; }): Promise<{ account?: Account; error?: never; } | { account?: never; error: { message: string; details?: unknown; }; }>
Authenticates a Request by verifying a signed authentication token. - If a token is not provided, the returned account is `undefined` and no error is returned. - If a valid token is provided, the signer account is returned. - If an invalid token is provided, an error is returned detailing the validation error, and the returned account is `undefined`.authenticateRequest(request: Request
request, {getToken?: ((request: Request) => string | undefined | null) | undefined
getToken: (request: Request
request) =>request: Request
request.Request.headers: Headers
Returns a Headers object consisting of the headers associated with request. Note that headers added in the network layer by the user agent will not be accounted for in this object, e.g., the "Host" header. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/headers)headers.Headers.get(name: string): string | null (+1 overload)
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/get)get("x-jazz-auth-token"), }); }
Addendum: Manual token parsing
If you need to manually parse a token from a string, you can use the parseAuthToken
function.
import {
parseAuthToken,
function parseAuthToken(authToken: string, options?: { loadAs?: Account; expiration?: number; }): Promise<{ account: Account; error?: never; } | { account?: never; error: { message: string; details?: unknown; }; }>
function generateAuthToken(as?: Account): string
Generates an authentication token for a given account. This token can be used to authenticate a request. See {@link authenticateRequest } for more details.generateAuthToken } from "jazz-tools"; constconst myToken: string
myToken =function generateAuthToken(as?: Account): string
Generates an authentication token for a given account. This token can be used to authenticate a request. See {@link authenticateRequest } for more details.generateAuthToken(); const {const account: Account | undefined
account,error } = await
const error: { message: string; details?: unknown; } | undefined
parseAuthToken(
function parseAuthToken(authToken: string, options?: { loadAs?: Account; expiration?: number; }): Promise<{ account: Account; error?: never; } | { account?: never; error: { message: string; details?: unknown; }; }>
const myToken: string
myToken);