Auth
Decide who may read and write which documents
By default, anyone who can reach the sync server can edit, like Vite's own
dev server. Pass an authenticate hook to the server to put the websocket,
uploads and assets behind your own policy.
Add an authenticate hook
authenticate receives the request and returns a scope, or
null to reject. It runs on every connection and reconnect, so refreshed
credentials apply.
Cookies come with every request, so only the server changes:
import type { SyncAuthenticate } from "@fumadocs-editor/core/node";
declare function verifySession(
cookie: string | undefined,
): Promise<{ name: string; isEditor: boolean } | null>;
export const authenticate: SyncAuthenticate = async ({ request }) => {
const session = await verifySession(request.headers.cookie);
if (!session) return null;
return {
user: { name: session.name },
write: session.isEditor,
};
};
Scopes
Prop
Type
read and write can be functions of a document's path, relative to the
server root:
- Paths are normalized first, so
team-a/../secret.mdxdoes not match ateam-a/rule. - The functions must be synchronous. Fetch what they need inside
authenticate, as the token example does.
Needs read | Needs write |
|---|---|
| opening a document | saving it, or editing it under collab |
| seeing it in the file tree | creating, deleting and reordering pages |
| loading an asset | uploading to assets/ |
A scope user also overrides the name a collab client shows at its caret.
Media uploads
Upload requests do not share the websocket, so send the same payload in the
AUTH_HEADER header. Cookie setups can skip this.
import { AUTH_HEADER, UPLOAD_ENDPOINT } from "@fumadocs-editor/core/sync";
import type { MediaProvider } from "@fumadocs-editor/ui";
declare function getAccessToken(): Promise<string>;
export const media: MediaProvider = {
async upload(file) {
const res = await fetch(UPLOAD_ENDPOINT, {
method: "POST",
body: file,
headers: {
"x-filename": encodeURIComponent(file.name),
[AUTH_HEADER]: JSON.stringify(await getAccessToken()),
},
});
if (!res.ok) throw new Error(`upload failed: ${res.status}`);
const { src } = (await res.json()) as { src: string };
return src;
},
};
In the editor
The server enforces the scope; the editor never restricts itself. When a user
cannot write, see Read-only to show the editor as read-only.
Rejected credentials show the denied status.
Last updated on