Guides

Securing end-user data

This guide covers the controls you can use to restrict how end users access your project data. The defaults are backward-compatible: existing projects are unaffected unless you opt in. Apply these settings in Console -> Authentication and Console -> Collections.

Collection access modes

Every collection has an accessMode setting that controls which tokens may read and write it. Set it per collection under Console -> Collections in the "Who can access" column, or via the API:

HTTP
PATCH /api/schemas/projects/{projectId}/collections/{collectionId}
X-API-Key: {serverKey}
Content-Type: application/json

{
  "settings": {
    "accessMode": "restricted"
  }
}
ModeWho can accessWhen to use
open (default)Signed-in end users with the viewer role can read unless you add an explicit denyMost collections that are already protected by your permission rules
restrictedEnd users can only access collections where you have added an explicit grant for their roleCollections with private data where you want to control access entry by entry
serverOnlyOnly server API keys and project adminsInternal collections, audit logs, billing records - nothing an end user should ever reach

When a request hits a serverOnly collection with an end-user token, the server returns 403 with code: "server_only_collection".

Making a collection server-key-only

Shell
curl -X PATCH \
  "https://cloud.mudbase.dev/api/schemas/projects/YOUR_PROJECT_ID/collections/YOUR_COLLECTION_ID" \
  -H "X-API-Key: YOUR_SERVER_KEY" \
  -H "Content-Type: application/json" \
  -d '{"settings":{"accessMode":"serverOnly"}}'

After this change, any request to that collection with an end-user Bearer token returns 403 server_only_collection. Requests from your server using X-API-Key continue to work.

Project-wide data access posture

Instead of locking individual collections, you can flip the project default for all collections at once.

endUserDataAccess lives on the project settings object. Set it via:

HTTP
PATCH /api/projects/{orgId}/projects/{projectId}
X-API-Key: {serverKey}
Content-Type: application/json

{
  "settings": {
    "endUserDataAccess": "explicit"
  }
}
ValueBehavior
legacy (default)The viewer role has an implicit read fallback. Existing permission rules still apply; this is unchanged from the previous behavior.
explicitThe viewer fallback is disabled for every collection in the project. End users can only read a collection when you have added an explicit permission grant for their role.

Run the migration report before switching to explicit to see which collections in your project currently rely on the viewer fallback:

Shell
node scripts/migrations/report-collection-access-mode.mjs

The report is read-only and does not modify any data.

Sign-up and authentication controls

Disabling end-user sign-up

When allowEndUserSignup is false, all public self-registration paths return 403 SIGNUP_DISABLED. This includes email/password registration, OTP verify (new user path), magic link verify (new user path), OAuth callback (new user path), and multi-role signup. Existing users can still sign in. Set it in Console -> Authentication -> Sign-up and access controls, or via the API:

HTTP
PATCH /api/projects/{orgId}/projects/{projectId}
X-API-Key: {serverKey}
Content-Type: application/json

{ "settings": { "allowEndUserSignup": false } }

The default is true. Legacy projects are unaffected.

Creating users from your server when sign-up is disabled

When allowEndUserSignup is false, all public registration paths are closed. Your backend can still provision accounts directly using the server user creation route:

Shell
curl -X POST \
  "https://cloud.mudbase.dev/api/projects/YOUR_PROJECT_ID/users" \
  -H "X-API-Key: YOUR_SERVER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "user@example.com",
    "password": "optional-password",
    "firstName": "Jane",
    "lastName": "Doe",
    "phone": "+1234567890",
    "customRole": "member"
  }'

Authenticate with a same-project API key in X-API-Key (prefix ak_), or a Bearer token for an org team member with the owner, admin, or developer system role. The route checks only that the key belongs to the project, not its permission list, so any project-scoped key (including one created with read-only database permissions) can create users.

FieldRequiredNotes
emailYesThe new user's email address.
passwordNoOmit for passwordless or SSO-only accounts. A locked credential is stored internally so the account remains valid.
firstNameNoDefaults to the local part of the email when omitted.
lastNameNoDefaults to "User" when omitted.
phoneNo
customRoleNoAn application role slug defined in your project. Cannot be owner, admin, or developer.

Users created through this route always receive the viewer system role and have no team privileges. No verification email is sent; your server controls whether and how to contact the user.

On success the route returns 201 with the created user object. Error codes: EMAIL_CONFLICT (400) if the address is already registered in this project; RESERVED_ROLE_NAME (400) if customRole matches a reserved team role name; API_KEY_PROJECT_MISMATCH (403) if the API key does not belong to this project.

Warning: Mudbase API keys (prefix ak_) are project-scoped server secrets. There is no publishable variant. Any key scoped to a project can call this route regardless of its permission list, so a "read-only database" key is still a full server secret and must be treated as one. Never include any project key in browser JavaScript, mobile app binaries, or any client-side code that an end user can inspect.

Disabling anonymous sign-in

When allowAnonymousAuth is false, the anonymous sign-in endpoint returns an error. Use this to prevent unauthenticated sessions from reading any project data.

HTTP
PATCH /api/projects/{orgId}/projects/{projectId}
X-API-Key: {serverKey}
Content-Type: application/json

{ "settings": { "allowAnonymousAuth": false } }

Default role for new sign-ups

defaultUserRole is the role automatically assigned when a new end user registers. Set it to "viewer" or a custom role name you have defined in your project roles:

HTTP
PATCH /api/projects/{orgId}/projects/{projectId}
X-API-Key: {serverKey}
Content-Type: application/json

{ "settings": { "defaultUserRole": "viewer" } }

The previous value was always saved per the Mongoose schema but was not exposed in the console UI until this release.

Trusted server proxy for auth rate limits

By default, auth rate limits are keyed on the caller's IP address. When your backend proxies sign-ins on behalf of many users, they all share one IP bucket and hit limits quickly.

If your request carries a valid server API key (prefix ak_) in X-API-Key, you can pass the real end-user IP in X-Mudbase-End-User-IP. Each end user then gets their own independent rate-limit bucket.

JavaScript
const endUserIp =
  req.headers["x-forwarded-for"]?.split(",")[0]?.trim() ||
  req.socket.remoteAddress;

const res = await fetch("https://cloud.mudbase.dev/api/auth/local/login", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-Key": process.env.MUDBASE_SECRET_KEY,
    "X-Mudbase-End-User-IP": endUserIp,
  },
  body: JSON.stringify({ email, password, projectId }),
});

Accepted values: a single public, globally routable IPv4 or IPv6 address. IPv6 addresses are bucketed at the /64 prefix.

Rejected (falls back to caller IP, no error returned): private ranges (RFC 1918, loopback, link-local, CGNAT, ULA), multicast and reserved ranges, and malformed addresses.

Per-server-key ceiling: 600 auth requests per minute per server key, regardless of how many distinct end-user IPs it supplies. Override with the RL_SERVER_KEY_AUTH_MAX environment variable on self-hosted deployments.

Per-account brute-force limit: the per-account limit (10 failed attempts per 15 minutes, keyed on email) is unaffected by this header. A distributed attack rotating IPs still hits the per-email ceiling.

The header is silently ignored for requests without a valid server key, so public clients cannot use it to shift rate limits.

Authentication email templates

The following emails are sent to end users during authentication flows. You can override each one with a custom HTML template via the management API. The templates are managed under Console -> Project -> Emails.

EventTemplate keyRequired placeholder
Email/password sign-up verificationemailVerification{{verificationUrl}}
Anonymous user converts to full accountemailVerification{{verificationUrl}}
OTP sign-in codeotp{{otpCode}}
Magic link sign-inmagicLink{{magicLinkUrl}}
Password resetpasswordReset{{resetUrl}}
New sign-in notificationnewSignIn(none required)
Welcome messagewelcome(none required)

Platform-level emails (billing, overage, subscription) are sent by the platform to the organization owner. They cannot be overridden here.

Overriding a template

HTTP
PUT /api/projects/{projectId}/email/templates/{templateName}
X-API-Key: {serverKey}
Content-Type: application/json

{
  "subject": "Confirm your email address",
  "htmlBody": "<!DOCTYPE html><html><body><p>Click below to verify your email.</p><a href=\"{{verificationUrl}}\">Verify email</a></body></html>"
}

Save-time validation rejects:

  • htmlBody over 50,000 characters
  • Auth templates missing their required placeholder (returns 400 with missingRequiredPlaceholders)

If a stored template somehow fails the render-time check (for example, after being corrupted), the server falls back to the next level (global override, then built-in template) and logs a warning. Auth emails always send.

Host and callback URLs

  • Recommended host: cloud.mudbase.dev (used in the OpenAPI spec and CLI default)
  • Equivalent: api.mudbase.dev (same backend)
  • Auth callback URLs in OAuth providers are pinned to api.mudbase.dev server-side; they are not affected by which host you send the initial request to.

Revoking access

To suspend a single user, send:

HTTP
PATCH /api/orgs/{orgId}/users/{userId}/status
Authorization: Bearer {teamToken}
Content-Type: application/json

{ "accountStatus": "suspended" }

The change takes effect within about 30 seconds. Tokens already issued to that user remain valid for their full lifetime: access tokens last 30 minutes and refresh tokens last 7 days. Plan accordingly if your threat model requires faster revocation.

Setting allowAnonymousAuth to false stops new anonymous sessions from being issued, but it does not revoke anonymous tokens already in circulation.

Audit logs and read history

Writes to a collection (create, update, delete) are recorded in the project audit log. Per-document read access is not logged: there is no record of which user read which document or when.

Securing end-user data: checklist

Use this checklist for any app that proxies sign-ins through your own server and stores private user data in collections.

  • Set allowEndUserSignup: false if users are provisioned by your backend (invite-only or admin-created accounts).
  • Set allowAnonymousAuth: false unless your app genuinely serves unauthenticated users.
  • Set defaultUserRole to the least-privilege role defined in your project roles. "viewer" is a reasonable starting point.
  • Review each collection that stores private data and set accessMode: "serverOnly" for internal collections (audit logs, billing, internal state) or accessMode: "restricted" for collections where only explicitly granted roles should read.
  • For a new project, set endUserDataAccess: "explicit" before you go live to enable deny-by-default across all collections. For an existing project, run the migration report first.
  • If your backend calls auth endpoints on behalf of many users, add X-Mudbase-End-User-IP so each user gets their own rate-limit bucket.
  • Override auth email templates that reference your app name or branding so users see your domain in verification and sign-in emails.
  • When allowEndUserSignup is false, use POST /api/projects/{projectId}/users from your server to create accounts rather than re-enabling public sign-up.
  • Keep API keys (ak_) server-side only. Never include them in browser or mobile client code.
Chat with us