Skip to Content
ConnectModule Access

Module Access

A module access key is a permission for a feature, not for data.

Collection permissions answer “which rows and columns may this user touch?” A key answers a different question: “may this user use this feature at all?”

Keys have names like system:logs (open the Logs page) or reports:export (export a report). You grant a key on a policy. If a user has several policies, they get every key that any one of those policies grants.

This page is about using keys in your own code. To create keys and grant them to people, see Roles & Policies.

Reading a user’s keys

Call GET /api/permissions/me. The response has a moduleAccess object listing the keys that user holds:

{ "data": { "...": "collection permissions" }, "moduleAccess": { "system:logs": true, "reports:export": true }, "meta": { "resource_uri": "/tenant:acme" } }

If you use scopes, the keys you get back are the ones that apply to the scope you asked for. Set the scope with the X-Resource-Uri header (or the daas_resource_uri cookie). The scope that was used comes back as meta.resource_uri.

In a React component, don’t fetch this yourself — read it from the permissions context:

const { hasModuleAccess } = usePermissions(); if (hasModuleAccess('reports:export')) { // show the export button }

Checking a key on the server

The check above only decides what the user sees. It runs in the browser, so it cannot stop a request. Hiding a button does not protect the thing behind it — someone can still call your API directly. Always check the key again on the server.

This matters more for keys than for collection permissions. Collection permissions have a safety net: if the app layer forgets to check, the database checks again (Row Level Security). Keys have no safety net. No table sits behind a key, so the database has nothing to re-check. Your server-side check is the only thing standing there.

Use lib/module-access/enforce.ts in API route handlers and Server Components:

import { enforceModuleAccess, hasModuleAccess, getModuleAccess } from '@/lib/module-access/enforce'; // Stops the request: throws ModuleAccessError (status 403) if the key is missing await enforceModuleAccess('reports:export'); // Just asks — returns true or false, throws nothing if (await hasModuleAccess('reports:export')) { /* ... */ } // Everything at once const { isAdmin, moduleAccess } = await getModuleAccess();

A typical route:

// app/api/reports/export/route.ts export async function GET() { await enforceModuleAccess('reports:export'); // ... }

How the check behaves

A list of keys means “any one of these.” enforceModuleAccess(['reports:export', 'reports:admin']) passes if the user has either one. This matches how workflow commands treat keys.

Admins pass every check. Be careful here: getModuleAccess() returns { isAdmin: true, moduleAccess: {} } for an admin — the key list comes back empty, because an admin holds all keys rather than a specific set. So check isAdmin first. If you only look at the list, you will lock admins out of everything.

If anything goes wrong, access is denied. No one logged in, a failed lookup, an unexpected error — all of them mean “no access”. The check never lets someone through because something broke.

Features and data are two separate checks. One asks may they use this feature, the other asks may they touch these rows. If your route does both, call both:

await enforceModuleAccess('reports:export'); await enforcePermission({ collection: 'reports', action: 'read' });

Two things this check does not do

It needs a direct database connection. If your app talks to a DaaS instance somewhere else instead of its own database, use the moduleAccess values from /api/permissions/me instead of this helper.

It ignores scopes. Unlike /api/permissions/me, this check looks at all of the user’s policies at once, whatever scope they belong to. So a key granted in one tenant will pass the check in every tenant. If a feature must be limited to one tenant, also check a collection permission, which is scope-aware.

Using a key in a workflow

A workflow command can list keys in module_access_keys, next to its policies list. The user needs either one — a matching key or a matching policy:

{ "module_access_keys": ["workflow:approve"] }

See Workflows for the full command format.

Last updated on