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:
PATCH /api/schemas/projects/{projectId}/collections/{collectionId}
X-API-Key: {serverKey}
Content-Type: application/json
{
"settings": {
"accessMode": "restricted"
}
}| Mode | Who can access | When to use |
|---|---|---|
open (default) | Signed-in end users with the viewer role can read unless you add an explicit deny | Most collections that are already protected by your permission rules |
restricted | End users can only access collections where you have added an explicit grant for their role | Collections with private data where you want to control access entry by entry |
serverOnly | Only server API keys and project admins | Internal 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
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:
PATCH /api/projects/{orgId}/projects/{projectId}
X-API-Key: {serverKey}
Content-Type: application/json
{
"settings": {
"endUserDataAccess": "explicit"
}
}| Value | Behavior |
|---|---|
legacy (default) | The viewer role has an implicit read fallback. Existing permission rules still apply; this is unchanged from the previous behavior. |
explicit | The 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:
node scripts/migrations/report-collection-access-mode.mjsThe 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:
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:
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.
| Field | Required | Notes |
|---|---|---|
email | Yes | The new user's email address. |
password | No | Omit for passwordless or SSO-only accounts. A locked credential is stored internally so the account remains valid. |
firstName | No | Defaults to the local part of the email when omitted. |
lastName | No | Defaults to "User" when omitted. |
phone | No | |
customRole | No | An 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.
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:
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.
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.
| Event | Template key | Required placeholder |
|---|---|---|
| Email/password sign-up verification | emailVerification | {{verificationUrl}} |
| Anonymous user converts to full account | emailVerification | {{verificationUrl}} |
| OTP sign-in code | otp | {{otpCode}} |
| Magic link sign-in | magicLink | {{magicLinkUrl}} |
| Password reset | passwordReset | {{resetUrl}} |
| New sign-in notification | newSignIn | (none required) |
| Welcome message | welcome | (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
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:
htmlBodyover 50,000 characters- Auth templates missing their required placeholder (returns
400withmissingRequiredPlaceholders)
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.devserver-side; they are not affected by which host you send the initial request to.
Revoking access
To suspend a single user, send:
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: falseif users are provisioned by your backend (invite-only or admin-created accounts). - Set
allowAnonymousAuth: falseunless your app genuinely serves unauthenticated users. - Set
defaultUserRoleto 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) oraccessMode: "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-IPso 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
allowEndUserSignupisfalse, usePOST /api/projects/{projectId}/usersfrom 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.