Edit Page

User Registration & Email Verification

RESTHeart Cloud

Flow 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

  1. Validate all required fields.

  2. Enforce password strength via zxcvbn (score ≥ minimumPasswordStrength, default 3).

  3. Check email uniqueness — 409 Conflict if already registered.

  4. Generate a cryptographically random verificationToken (256-bit) and verificationCreatedAt timestamp.

  5. Create the user document with roles: ["$unauthenticated"] and the verification token.

  6. Validate the user document against the collection’s JSON Schema (if configured — see below).

  7. Create a membership document linking the user to the team as owner.

  8. Send a verification email containing a one-time link:

    {baseAppUrl}/auth/verify?email=alice@acme.com&token=<verificationToken>
  9. Return 201 Created.

Response

{ "message": "Registration successful. Check your email to verify your address." }

Error responses

Status Reason

400

Missing or invalid fields; password too weak; document violates collection JSON Schema

409

Email already registered

500

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

  1. The user document is built from the request body (see the mapping table below).

  2. The collection properties are read from _properties._properties.{usersCollection} in the effective database. Both the database and the collection are sourced from mongoRealmAuthenticator (users-db and users-collection).

  3. If a jsonSchema metadata is present, the document is validated against the referenced schema, resolved through the json-schemas provider.

  4. On validation failure, the endpoint returns 400 with the list of violations, and no user is created.

  5. 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: false passes 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

firstName

profile.name

lastName

profile.surname

email

_id

password

password (hashed)

teamName

not stored on the user document — a team document is created by createInitialTeam

Note

The schema validates the stored document, not the request body. A schema that requires profile.name will reject a registration missing firstName, because the stored field is profile.name.

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

  1. Extract email, token and the optional delivery parameter from the query string.

  2. Find the user by email.

  3. Compare token against verificationToken using constant-time comparison (timing-attack mitigation).

  4. Check that verificationCreatedAt is within the TTL (verificationTokenTtlDays, default 7 days).

  5. Assign the system ACL role from accountsConfig.default-role (default: user) — this replaces the $unauthenticated role assigned at registration. $unset verificationToken and verificationCreatedAt.

  6. Issue a JWT and deliver it according to delivery (see below).

  7. Redirect to {frontendAppUrl}.

The optional delivery query parameter selects how the JWT is handed to the frontend:

delivery Behavior

cookie (or omitted)

Default. Sets the auth cookie and redirects to {frontendAppUrl}. For same-origin setups where cookie authentication works.

fragment

Redirects to {frontendAppUrl} with the JWT appended as a URL fragment: #access_token=…​&token_type=Bearer&expires_in=…​. For cross-origin SPAs using Bearer token authentication, where browsers block third-party cookies (Safari blocks them entirely; Chrome and Firefox block them with SameSite=strict).

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).