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): stringGenerates 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: Responseresponse = 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 | undefinedA Headers object, an object literal, or an array of two-item arrays to set request's headers.headers: {type Authorization: stringAuthorization: `Jazz ${function generateAuthToken(as?: Account): stringGenerates 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 functionfunction 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: Requestrequest: Request) { constworker = awaitconst 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 | undefinedsyncServer: "wss://cloud.jazz.tools/?key=your-api-key",accountID?: string | undefinedaccountID:var process: NodeJS.Processprocess.NodeJS.Process.env: NodeJS.ProcessEnvThe `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 | undefinedJAZZ_WORKER_ACCOUNT,accountSecret?: string | undefinedaccountSecret:var process: NodeJS.Processprocess.NodeJS.Process.env: NodeJS.ProcessEnvThe `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 | undefinedJAZZ_WORKER_SECRET,asActiveAccount?: boolean | undefinedIf false, the worker will not set in the global account contextasActiveAccount: true, }); const {const account: Account | undefinedaccount,error } = awaitconst error: { message: string; details?: unknown; } | undefinedfunction 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: Requestrequest); // There was an error validating the token (e.g., invalid or expired) if (error) { return newconst error: { message: string; details?: unknown; } | undefinedvar Response: new (body?: BodyInit | null, init?: ResponseInit) => ResponseThis Fetch API interface represents the response to a request. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response)Response(var JSON: JSONAn 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 | undefinedstatus: 401 }); } if (!const account: Account | undefinedaccount) { return newvar Response: new (body?: BodyInit | null, init?: ResponseInit) => ResponseThis Fetch API interface represents the response to a request. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response)Response("Unauthorized", {ResponseInit.status?: number | undefinedstatus: 401 }); } return newvar Response: new (body?: BodyInit | null, init?: ResponseInit) => ResponseThis Fetch API interface represents the response to a request. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response)Response(var JSON: JSONAn 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: stringmessage: `The request was made by ${const account: Accountaccount.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: stringid}` })); }
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): stringGenerates 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: Responseresponse = 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 | undefinedA Headers object, an object literal, or an array of two-item arrays to set request's headers.headers: {type Authorization: stringAuthorization: `Jazz ${function generateAuthToken(as?: Account): stringGenerates 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: Accountaccount)}`, }, });
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 functionfunction 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: Requestrequest: 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 | undefinedsyncServer: "wss://cloud.jazz.tools/?key=your-api-key",accountID?: string | undefinedaccountID:var process: NodeJS.Processprocess.NodeJS.Process.env: NodeJS.ProcessEnvThe `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 | undefinedJAZZ_WORKER_ACCOUNT,accountSecret?: string | undefinedaccountSecret:var process: NodeJS.Processprocess.NodeJS.Process.env: NodeJS.ProcessEnvThe `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 | undefinedJAZZ_WORKER_SECRET, }); const {const account: Account | undefinedaccount,error } = awaitconst error: { message: string; details?: unknown; } | undefinedfunction 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: Requestrequest, {loadAs?: Account | undefinedloadAs: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: Requestrequest: Request) { const {const account: Account | undefinedaccount,error } = awaitconst error: { message: string; details?: unknown; } | undefinedfunction 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: Requestrequest, {expiration?: number | undefinedexpiration: 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): stringGenerates 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: Responseresponse = 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 | undefinedA 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): stringGenerates 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: Requestrequest: Request) { const {const account: Account | undefinedaccount,error } = awaitconst error: { message: string; details?: unknown; } | undefinedfunction 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: Requestrequest, {getToken?: ((request: Request) => string | undefined | null) | undefinedgetToken: (request: Requestrequest) =>request: Requestrequest.Request.headers: HeadersReturns 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): stringGenerates 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: stringmyToken =function generateAuthToken(as?: Account): stringGenerates 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 | undefinedaccount,error } = awaitconst error: { message: string; details?: unknown; } | undefinedparseAuthToken(function parseAuthToken(authToken: string, options?: { loadAs?: Account; expiration?: number; }): Promise<{ account: Account; error?: never; } | { account?: never; error: { message: string; details?: unknown; }; }>const myToken: stringmyToken);