Documentation

Two-factor and devices

2FA setup, backup codes, login challenges, and device management.

The security module under /profile/security/* adds optional second-factor authentication, device tracking, and session management. Once 2FA is enabled, the sign-in flow gains a challenge step before issuing a full token.

2FA methods

TwoFactorService supports multiple second-factor methods, configured per-User:

  • TOTP (authenticator apps — Google Authenticator, Authy, 1Password)
  • Email code
  • SMS code (requires a phone number on the User record and a connected SMS provider)

Each method goes through the same lifecycle: setup → verify-setup → enable. Once enabled, sign-in returns a partial token instead of a full one, and the client must complete a challenge before the session unlocks.

Status

GET/profile/security/2fa/statusJWT

Returns the current 2FA configuration for the signed-in User — which methods are enabled, how many backup codes remain.

{
  "enabled": true,
  "methods": [{ "type": "totp", "enabled": true, "verifiedAt": "..." }],
  "backupCodesRemaining": 8
}

Setup flow

  1. 1

    Initiate setup

    Pick a method and start the setup. For TOTP, the response includes the QR code and the shared secret.

    POST/profile/security/2fa/setup/{method}JWT
    curl -X POST https://appengine.appmint.io/profile/security/2fa/setup/totp \
      -H "orgid: my-org" -H "Authorization: Bearer $JWT"
    
    {
      "method": "totp",
      "secret": "JBSWY3DPEHPK3PXP",
      "qrCode": "data:image/png;base64,...",
      "otpauthUrl": "otpauth://totp/AppMint:[email protected]?..."
    }
    
  2. 2

    Verify the user can produce a code

    Have the user enter a code from their app, post it back to confirm the setup is wired correctly.

    POST/profile/security/2fa/verify-setupJWT
    { "method": "totp", "code": "123456" }
    

    Returns { verified: true } on success.

  3. 3

    Enable

    Flip 2FA on for the account. Subsequent sign-ins require the second factor.

    POST/profile/security/2fa/enableJWT
    { "method": "totp" }
    

Disabling 2FA

POST/profile/security/2fa/disableJWT

Requires the current password (or a recent recent challenge — depends on org policy). Disables all methods at once; if you want to swap methods, set up the new one first then disable the old.

Backup codes

Single-use recovery codes for when the user loses their TOTP device.

POST/profile/security/2fa/backup-codesJWT

Generates a fresh batch (typically 10) and invalidates any previous batch. Show the codes to the user once; they're stored hashed.

GET/profile/security/2fa/backup-codes/countJWT

Returns the number of unused codes left. Prompt the user to regenerate when this gets low.

Sign-in challenge

When 2FA is enabled, POST /profile/signin returns a challenge token instead of a full session:

{
  "data": {
    "challenge": { "token": "...", "methods": ["totp", "email"] },
    "user": { "pk": "...", "email": "..." }
  }
}

Send the challenge token to the user's chosen method, then verify the code:

POST/profile/security/challenge/sendNo auth
POST/profile/security/challenge/verifyNo auth

/challenge/send is for methods that need a server-issued code (email, SMS). TOTP doesn't need a send step — the user already has the code in their authenticator app.

/challenge/verify exchanges the challenge token + the verification code for a real JWT pair. From here on, the session works like any other.

curl -X POST https://appengine.appmint.io/profile/security/challenge/verify \
  -H "orgid: my-org" -H "Content-Type: application/json" \
  -d '{ "challengeToken": "...", "method": "totp", "code": "123456" }'

Devices

Every successful sign-in registers a device record — user agent, IP, location (geo-IP), trust state. Users (and admins) can review and revoke them.

GET/profile/security/devicesJWT
GET/profile/security/devices/currentJWT
POST/profile/security/devices/{deviceId}/trustJWT
POST/profile/security/devices/{deviceId}/blockJWT
DELETE/profile/security/devices/{deviceId}JWT

trust marks a device as remembered — subsequent sign-ins from the same device may skip the 2FA challenge depending on policy. block hard-locks a device; DELETE revokes the active session and forgets the device record.

devices/current returns just the device handling the current request — handy for "is this device trusted" UI prompts.

Login history

GET/profile/security/login-historyJWT

Paginated list of past sign-ins for the current User: timestamp, IP, user agent, device id, success/failure. Surface this in account settings so users can spot suspicious activity.

Security settings

GET/profile/security/settingsJWT

Aggregated read of every security knob for the User — 2FA status, trusted devices count, recent login summary, password-changed-at. Use this to render a one-page security dashboard.

Org-wide policy

ConfigAdmins can enforce 2FA org-wide via Org Management settings. When the policy is on:

  • Every User must enable 2FA before their first session expires.
  • Sign-in flows respect a grace period (configurable, default 7 days).
  • Customers are unaffected — the policy is per-User-collection.

For Customer-facing 2FA (storefronts, member portals), use the same endpoints — the underlying service handles both principal types. Most Customer-facing apps choose passwordless (magic link) instead, with 2FA reserved for high-trust user accounts.