Configuration
RESTHeartAll restheart-accounts settings live in restheart.yml across three top-level keys: accountsConfig, oauthConfig, and emails.
Every section is independent; omitting a section disables the corresponding feature.
Full configuration reference
accountsConfig
accountsConfig:
# Since 9.7.0 the database and collection holding users, teams, invitations
# and oauth_codes are sourced from mongoRealmAuthenticator (users-db and
# users-collection), so accounts writes users where the authenticator reads
# them. There is no accountsConfig key for either.
# Application display name β used in email subjects and bodies.
app-name: "My App"
# JWT access token time-to-live in minutes.
# The key and issuer are sourced automatically from jwtConfigProvider.
jwt-ttl: 15
# Domain used for the JWT authentication cookie (Set-Cookie: Domain=...).
cookie-domain: dev.example.com
# Whether the auth cookie carries the Secure attribute (HTTPS only). Since 9.6.0.
# Default true; set false only for local HTTP development.
cookie-secure: true
# Name of the JWT authentication cookie.
# Default: "rh_auth".
cookie-name: rh_auth
# Public URL of the frontend β used to build links in transactional emails.
frontend-url: https://dev.example.com
# URL the browser is redirected to after a successful auto-login
# (email verification, invitation activation, password reset, OAuth callback).
frontend-app-url: https://dev.example.com/app
# Legal policy versions. Required only when using OAuth with consentsAccepted=true.
# The OAuth callback reads these values to build the ConsentRecord passed to
# MembershipProvider.activateViaOAuth(), so the deployment layer knows which
# version the user accepted. Omit if you do not track consent versions via OAuth.
# See: Consents Management.
terms-version: "1.0"
privacy-version: "1.0"
# Default BCP 47 locale for email templates (used when the user document
# has no "locale" field). Falls back to "en" if omitted.
default-locale: en
# Paths to custom email template HTML files (relative to the RESTHeart
# working directory). Omit any key to use the built-in template.
# See: Email Templates guide.
templates:
verification: etc/email-templates/verification.html
password-reset: etc/email-templates/password-reset.html
invite: etc/email-templates/invite.html
# ββ Membership SPI (9.4.1+) ββββββββββββββββββββββββββββββββββββββββββββββ
# The name of the ACL variable for the active team (@team), and of the claim when "@team"
# is listed in jwtConfigProvider/account-properties-claims. Default: "team".
# See: Custom Membership Providers.
team-claim-name: team
# Role name assigned to regular (non-admin) team members.
# Default: "member". Set to "user" if existing ACL rules use that label.
member-role-name: member
# System ACL role assigned to users after email verification or OAuth login.
# New unverified users get roles: ["$unauthenticated"] β they can only access
# public endpoints until verification assigns the real role.
# Default: "user". Override per-team via override-accounts-default-role.
default-role: user
# Team role assigned to the user who creates a new team.
# Stored in team.members[].role.
# Default: "owner". Override per-team via override-accounts-ownership-role.
ownership-role: owner
# Set to false to disable the built-in membership management endpoints:
# POST /auth/invite, POST /auth/resend-invite,
# GET /auth/teams, POST /auth/teams, POST /auth/switch-team,
# GET /auth/team/members, PATCH /auth/team, DELETE /auth/team
# Default: true
membership-endpoints-enabled: true
# One-off migration of existing data to the 9.12.0 membership model (memberships on the
# team only, the user keeps the active team id). Turn it on once, restart, turn it off:
# old `teams` arrays and `team.role` are removed from the user documents, and every
# membership they listed that the team does not confirm is reported in the log, never added.
# Default: false. See: Custom Membership Providers.
migrate-memberships: false
# Additional JWT claims propagated from request attached-parameters to tokens
# issued by accounts endpoints (verify, activate, reset-password, switch-team, OAuth).
# authDb is always included automatically (no need to list it here).
# Deprecated: configure account-properties-claims in jwtConfigProvider instead, once for
# every token path. The team claim is opt-in there, with "@team".
account-properties-claims:
# - srvNode # example: attached-param set by SrvNodeEnricher in multi-team deployments
oauthConfig
Omit this section entirely to disable OAuth 2.0 social login.
oauthConfig:
# Master switch. Set to false to disable all OAuth endpoints while keeping
# the configuration in place.
enabled: true
# Base URL of the RESTHeart API instance. Each provider's callback URL is
# constructed as: {api-base-url}/auth/oauth/callback/{provider}
api-base-url: https://api.example.com
# Browser is redirected here after a successful login.
frontend-success-url: https://app.example.com/app
# Browser is redirected here when the OAuth flow fails (e.g. user denied
# consent, invalid state).
frontend-error-url: https://app.example.com/login?error=oauth_error
providers:
google:
enabled: true
client-id: "123β¦.apps.googleusercontent.com"
client-secret: "GOCSPX-β¦"
scope: "openid email profile" # optional β default for google
github:
enabled: true
client-id: "Iv1.β¦"
client-secret: "β¦"
scope: "user:email" # optional β default for github
# Custom provider β requires a matching OAuthProvider plugin in plugins/
# myapp:
# enabled: true
# client-id: "β¦"
# client-secret: "β¦"
# scope: "read:profile"
emails
The emails plugin (from the restheart-emails module) provides SMTP email sending for transactional emails (registration verification, team invitations, password reset).
See the Emails Plugin documentation for full configuration reference and usage.
emails:
enabled: true
# Display name used in the email "From" header.
app-name: "My App"
# From email address.
sender-email: noreply@example.com
# SMTP connection settings.
smtp-hostname: email-smtp.eu-central-1.amazonaws.com
smtp-port: 465 # 465 = SMTPS (implicit TLS); 587 = STARTTLS
smtp-username: AKIAxxxxxxxx
smtp-password: secret
JWT token integration
The JWT access tokens issued by restheart-accounts (after login, verification, activation, password reset, and OAuth callback) are verified by RESTHeart’s own jwtAuthenticationMechanism.
Since RESTHeart v9.5, restheart-accounts reads the JWT signing key and issuer directly from jwtConfigProvider β there is no separate jwt-key or jwt-issuer in accountsConfig.
This eliminates the previous misconfiguration risk where the two keys could diverge, causing 401 Unauthorized errors on auto-login flows.
The only JWT-related setting remaining in accountsConfig is jwt-ttl, which controls how long the auto-login tokens last (independently from the main jwtTokenManager TTL):
# restheart.yml β single source of truth for the JWT key
jwtConfigProvider:
key: your-secret # used by jwtTokenManager, jwtAuthenticationMechanism
# AND restheart-accounts (automatically)
issuer: example.com
accountsConfig:
jwt-ttl: 15 # auto-login token TTL in minutes (accounts-specific)
|
Note
|
If jwt-key or jwt-issuer are still present in your accountsConfig block (from a pre-9.5 configuration), RESTHeart will log a warning and ignore them.
|
Propagating attached-parameters as JWT claims
By default the tokens issued by accounts endpoints (/auth/verify, /auth/activate, /auth/reset-password, /auth/switch-team, OAuth callbacks) include:
-
subβ the user’s email -
rolesβ the user’s roles -
authDbβ the MongoDB authentication database (always included automatically; used byJwtAuthDbVerifierin multi-team deployments) -
the claims
account-properties-claimsnames, from the user document and the request’s attached parameters. Since 9.12.0 no flow adds a claim of its own: the active team is read in ACL rules through@team, and is in the token only when the list names"@team".
In multi-team or custom deployments, interceptors such as SrvNodeEnricher attach additional parameters to the request before it reaches the service.
To propagate those parameters as JWT claims β so that the resulting token matches what POST /token would issue β list them in account-properties-claims:
accountsConfig:
# Must mirror jwtTokenManager.account-properties-claims so all issuance paths
# produce identical JWTs.
account-properties-claims:
- srvNode # set by SrvNodeEnricher; required by JwtAuthDbVerifier
jwtTokenManager:
account-properties-claims:
- srvNode # keep in sync with accountsConfig.account-properties-claims
authDb is always included automatically from the effective override-users-db value and never needs to appear in the list.
|
Tip
|
Keep accountsConfig.account-properties-claims and jwtTokenManager.account-properties-claims identical.
Any discrepancy means /token and accounts endpoints will issue JWTs with different claims, which can cause subtle authentication failures.
|
The active team in the token: "@team"
Since 9.12.0 the tokens the accounts endpoints issue (activation, email verification, password reset, POST /auth/switch-team, POST /auth/teams, the OAuth callback) carry no team claim. Before, each of them put the active team in as { "_id": {"$oid": "β¦"}, "role": "owner" }, while POST /token could only copy the team property of the user document as stored: with a custom provider storing it differently, a bare id for instance, the same user carried two shapes of the claim depending on the login path, and an ACL rule matched only one of them. The team has one source now, the variable.
restheart-accounts registers the ACL variable @team (named after team-claim-name), resolved from MembershipProvider.activeMembership for the authenticated caller. A deployment that wants the team in the token lists it as a claim, and gets it on every path, POST /token included:
jwtConfigProvider:
account-properties-claims:
- "@team" # quoted: @ is reserved in YAML
The claim is for the client, which reads from it which team it is working in; without it, the client reads the team from GET /auth/teams or from the POST /auth/switch-team response. Permissions do not need it: in ACL rules, @team._id and @team.role read the active team from the provider, never from the token, so a rule returns the same documents to a password login and to an OAuth login, and a token issued before a switch of team acts as the team active now, never as the old one:
- roles: [user]
predicate: path-prefix('/orders') and equals(@team.role, "owner")
mongo:
readFilter: {"team": "@team._id"}
mergeRequest: {"team": "@team._id"}
|
Warning
|
Migrating to 9.12.0: in ACL rules replace @user.team._id and @user.team.role with @team._id and @team.role, since @user.team resolves to nothing for a token that carries no team claim. Clients that read the team from the JWT either read it from GET /auth/teams or the switch-team response, or the deployment lists "@team" in jwtConfigProvider.account-properties-claims.
|
Since 9.7.0, account-properties-claims can also be overridden per team via override-accounts-account-properties-claims (see Multi-tenancy) β useful when different tenants want different token payloads (e.g. one wants consents for ACL predicates, another wants a plan identifier, another neither). Whichever list is in effect, a fixed denylist is enforced at token issuance and cannot be bypassed by the override β see JWT claims denylist.
Multi-tenancy with AuthDbResolver and TeamConfigInterceptor
To serve multiple teams from a single restheart-accounts deployment, configure two interceptors at REQUEST_BEFORE_EXCHANGE_INIT:
-
AuthDbResolverβ maps the incoming hostname tooverride-users-dbandoverride-cookie-domain. -
TeamConfigInterceptorβ reads aconfs/{srvId}.accountsdocument from MongoDB and attaches per-team app name, frontend URLs, email templates, and OAuth credentials. Since 9.6.0 the OAuth override surface covers all ofoauthConfig, not just provider credentials β see Multi-tenant / per-team overrides for the OAuth-specific details, in particularoverride-accounts-oauth-api-base-url, which a multi-tenant deployment should derive from the request’s own hostname rather than store, since every team is served on a different one.
Neither interceptor is bundled with restheart-accounts; implement them in your deployment layer.
restheart-accounts automatically detects and uses all override attributes when present.
| Attribute | Source | Overrides |
|---|---|---|
|
|
the users database (also honoured by |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
email template (inline HTML) |
|
|
email template (inline HTML) |
|
|
email template (inline HTML) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
whether Sign-up Management is enabled for this team (since 9.6.0) |
|
|
|
|
|
|
|
|
|
|
Note
|
override-accounts-users-unrestricted-roles and override-accounts-signup-mgmt-enabled both control the /users self-service write restriction on a per-team basis β the former exempts specific roles, the latter turns the restriction off entirely for a team that never opted into Sign-up Management. See that section for the full explanation of what each one does and why.
|
|
Note
|
{provider} is any provider name registered with OAuthService β a built-in one (google, github) or a custom OAuthProvider plugin. The override key format is the same regardless of provider; see Multi-tenant / per-team overrides for the full OAuth override reference, including scope semantics and why api-base-url should be derived per-request rather than stored.
|
confs/{srvId}.accounts document schema
The TeamConfigInterceptor reads an accounts sub-document from the existing service-configuration document (e.g. cloud.confs for free/shared nodes, restheart.confs for dedicated nodes).
All fields are optional; absent fields fall back to the node-level static configuration.
{
"_id": "ea820b",
"accounts": {
"app-name": "Customer App",
"frontend-url": "https://app.customer.com",
"frontend-app-url": "https://app.customer.com/app",
"terms-version": "2.0",
"privacy-version": "2.0",
"account-properties-claims": ["consents", "plan"],
"templates": {
"verification": "<html>β¦full HTML stringβ¦</html>",
"password-reset": "<html>β¦</html>",
"invite": "<html>β¦</html>"
},
"oauth": {
"google": {
"enabled": true,
"client-id": "123β¦.apps.googleusercontent.com",
"client-secret": "GOCSPX-β¦",
"scope": "openid email profile"
}
}
}
}
Inline HTML templates stored in MongoDB follow the same format as file-based templates (subject in <title>, i18n via <span lang>, {{variable}} placeholders).
See Email Templates for the full format reference.
|
Note
|
oauth.{provider} accepts any provider name (google, github, or a custom OAuthProvider plugin), with the same enabled / client-id / client-secret / scope shape.
|
|
Important
|
oauthConfig.api-base-url and the frontend-success-url / frontend-error-url pair are deliberately not part of this document. On a multi-tenant node every team is served on a different hostname, so TeamConfigInterceptor should derive override-accounts-oauth-api-base-url from the incoming request (scheme + host) rather than read it from a stored value β a stored value could not be correct for more than one team. The frontend URLs are typically derived from the team’s own frontend-app-url (already in this document) instead of stored separately, unless a team needs a distinct post-OAuth destination. See Multi-tenant / per-team overrides.
|
|
Tip
|
|
Secrets management (RHO)
Avoid hardcoding secrets in restheart.yml. The JWT key belongs to jwtConfigProvider (not accountsConfig) and is already documented in the JWT configuration section.
Use RESTHeart’s built-in environment variable substitution to inject sensitive values at runtime:
oauthConfig:
providers:
google:
client-secret: $(GOOGLE_CLIENT_SECRET)
github:
client-secret: $(GITHUB_CLIENT_SECRET)
emails:
smtp-password: $(SMTP_PASSWORD)
Set the corresponding environment variables before starting RESTHeart.
ACL requirements
Each public-facing service registers its own ACL allow rule automatically on startup via the ACLRegistry. No manual ACL entry is needed for these paths. If a service is disabled (or membership-endpoints-enabled: false), its path is not opened.
| Path | Method | Registered by |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
β Disabled when membership-endpoints-enabled: false.
The invite management endpoints are not public. Add the following rules to your ACL (file-based or MongoDB-based) to restrict them to owner role:
# invite and resend-invite require owner or admin
- roles: [owner]
predicate: path('/auth/invite') and method(POST)
priority: 10
- roles: [owner]
predicate: path('/auth/resend-invite') and method(POST)
priority: 10
Minimal configuration (email + password only)
The smallest working setup β no OAuth social login, no custom templates:
accountsConfig:
db: myapp
app-name: "My App"
jwt-ttl: 15
cookie-domain: example.com
frontend-url: https://example.com
frontend-app-url: https://example.com/app
emails:
enabled: true
app-name: "My App"
sender-email: noreply@example.com
smtp-hostname: email-smtp.eu-central-1.amazonaws.com
smtp-port: 465
smtp-username: $(SMTP_USERNAME)
smtp-password: $(SMTP_PASSWORD)
|
Note
|
The JWT key and issuer are inherited from jwtConfigProvider and do not need to appear in accountsConfig.
|