User Registration & Email Verification
RESTHeart CloudFlow Overview
sequenceDiagram
participant C as Client
participant R as RESTHeart
participant M as MongoDB
participant E as Email
C->>R: POST /auth/register
Note over R: validate fields, zxcvbn score ≥ 3
R->>M: check email uniqueness
R->>M: create user (roles: $unauthenticated + verify token)
M-->>R: OK
R->>E: verification email
R-->>C: 201 Created
Note over C,E: user clicks link in email
C->>R: GET /auth/verify?email=…&token=…
R->>M: constant-time token compare + TTL check
R->>M: set roles → user, unset verify token
M-->>R: OK
R-->>C: 302 + JWT cookie → frontend app URL
Registration flow
POST /auth/register is the public signup endpoint. It creates a new user and a new team in a single atomic operation.
Request
POST /auth/register
Content-Type: application/json
{
"firstName": "Alice",
"lastName": "Rossi",
"teamName": "Acme",
"email": "alice@acme.com",
"password": "correct-horse-battery"
}
Server-side steps
-
Validate all required fields.
-
Enforce password strength via zxcvbn (score ≥
minimumPasswordStrength, default 3). -
Check email uniqueness —
409 Conflictif already registered. -
Generate a cryptographically random
verificationToken(256-bit) andverificationCreatedAttimestamp. -
Create the user document with
roles: ["$unauthenticated"]and the verification token. -
Validate the user document against the collection’s JSON Schema (if configured — see below).
-
Create a
membershipdocument linking the user to the team asowner. -
Send a verification email containing a one-time link:
{baseAppUrl}/auth/verify?email=alice@acme.com&token=<verificationToken> -
Return
201 Created.
Response
{ "message": "Registration successful. Check your email to verify your address." }
Error responses
| Status | Reason |
|---|---|
|
Missing or invalid fields; password too weak; document violates collection JSON Schema |
|
Email already registered |
|
A JSON Schema is configured on the users collection but cannot be applied |
JSON Schema validation on registration
|
Note
|
Available from v9.7.0
Starting from RESTHeart v9.7.0, the registration endpoint validates the user document against the collection’s JSON Schema (if configured).
|
If the users collection has a jsonSchema metadata configured (via the _properties collection), the registration endpoint validates the user document against that schema before inserting it. This ensures that invariants declared on the users collection — for example that a consents field is always present — are enforced even for documents created through the registration endpoint, which bypasses the MongoService pipeline.
How it works
-
The user document is built from the request body (see the mapping table below).
-
The collection properties are read from
_properties._properties.{usersCollection}in the effective database. Both the database and the collection are sourced frommongoRealmAuthenticator(users-dbandusers-collection). -
If a
jsonSchemametadata is present, the document is validated against the referenced schema, resolved through thejson-schemasprovider. -
On validation failure, the endpoint returns
400with the list of violations, and no user is created. -
If no schema is configured, validation is skipped (opt-in).
Validation fails closed
Once the jsonSchema metadata is on the collection, it is honoured or the request fails. If the schema cannot be applied — it is missing from the schema store, the metadata is malformed, or restheart-mongodb is not deployed so the json-schemas provider is unavailable — the endpoint returns 500 and no user is created.
A typo in schemaId therefore surfaces as an error rather than silently disabling the invariant the schema was written to enforce.
What the schema sees
The document is validated as it is inserted, which is not its final shape. createInitialTeam runs right after the insert and adds teams and team to the user document. Consequently:
-
a schema with
required: ["team"]always fails, even though the stored document does end up with the field; -
a schema with
additionalProperties: falsepasses validation but does not describe the document a moment later.
Schemas meant for the registration path should declare teams and team as optional.
Fields of the request body that are not in the mapping table are dropped before the document is built, so the schema never sees them and cannot reject them — with or without additionalProperties: false. Body-level rejection is not part of this feature.
Violation messages are returned as-is, in the schema’s vocabulary. A body that omits firstName produces #/profile/name: required key [name] not found, not a message mentioning firstName.
Request body to user document mapping
| Request body field | User document field |
|---|---|
|
|
|
|
|
|
|
|
|
not stored on the user document — a team document is created by |
|
Note
|
The schema validates the stored document, not the request body. A schema that requires |
Example
# Create a schema that requires a 'consents' field
http POST :8080/_schemas Authorization:"Basic ..." \
_id=user-with-consents \
'$schema'="http://json-schema.org/draft-07/schema#" \
type=object \
required:='["consents"]' \
properties:='{"consents":{"type":"object"}}'
# Apply schema to users collection
http PATCH :8080/users Authorization:"Basic ..." \
jsonSchema:='{"schemaId":"user-with-consents"}'
# Registration now fails — user doc has no 'consents' field
http POST :8080/auth/register Authorization:"Basic ..." \
firstName=Alice lastName=Smith teamName=Acme \
email=alice@example.com password=Password123!
# → 400 Bad Request: User document violates schema: required key [consents] not found
|
Note
|
Consents management: User consent tracking is not handled by the Accounts plugin. See Consents Management for details. |
Email verification
GET /auth/verify?email=…&token=… is sent as a link in the registration email.
Server-side steps
-
Extract
email,tokenand the optionaldeliveryparameter from the query string. -
Find the user by email.
-
Compare
tokenagainstverificationTokenusing constant-time comparison (timing-attack mitigation). -
Check that
verificationCreatedAtis within the TTL (verificationTokenTtlDays, default 7 days). -
Assign the system ACL role from
accountsConfig.default-role(default:user) — this replaces the$unauthenticatedrole assigned at registration.$unsetverificationToken and verificationCreatedAt. -
Issue a JWT and deliver it according to
delivery(see below). -
Redirect to
{frontendAppUrl}.
Token delivery: cookie vs URL fragment
The optional delivery query parameter selects how the JWT is handed to the frontend:
delivery |
Behavior |
|---|---|
|
Default. Sets the auth cookie and redirects to |
|
Redirects to |
GET /auth/verify?email=...&token=... → cookie (default)
GET /auth/verify?email=...&token=...&delivery=cookie → cookie
GET /auth/verify?email=...&token=...&delivery=fragment → URL fragment
With delivery=fragment no cookie is set. The fragment uses the same mechanism as GET /token/redirect and the OAuth callback: since a URL fragment is never sent to any server, the token does not leak into access logs or the Referer header. The frontend reads it from window.location.hash and stores it (e.g. in localStorage) for use in the Authorization: Bearer header.
Expired token
If the token has expired, the endpoint returns 400 with a message inviting the user to request a new verification email from the login page.
|
Note
|
Re-sending a verification email is triggered by POST /auth/resend-verify (not yet implemented in v1 — users can contact support or attempt login which will prompt re-verification).
|