Documentation

Authentication overview

The auth model — JwtAuthGuard, PublicRoute, two principal types, OAuth, and API keys.

AppEngine authentication is opinionated and consistent. One global guard, two principal types, JWT for sessions, API keys for server-to-server, OAuth as a sign-in option. Once you know how the pieces fit, every endpoint behaves the same way.

The global guard

JwtAuthGuard is registered as APP_GUARD in app.module.ts. Every request hits it first. The guard:

  1. Reads Authorization: Bearer <jwt> (or x-api-key).
  2. Resolves the principal — a User or a Customer record from the database.
  3. Attaches the principal to request.user so controllers and decorators (@CurrentUser(), @CurrentCustomer()) can access it.
  4. Allows the request through, or rejects with 401.

Endpoints that should skip the guard are marked with @PublicRoute():

// src/users/auth/jwt.auth.guard.ts
export const IS_PUBLIC_KEY = 'isPublic';
export const PublicRoute = () => SetMetadata(IS_PUBLIC_KEY, true);

Any controller method with @PublicRoute() is reachable without a token. These are intentionally narrow: sign-in/sign-up flows, public storefront browse, public form submissions, magic-link redemption.

Two principal types

The dual-principal model splits "people who run an app" from "people who use it":

PrincipalStored inSign-inJWT subjectTypical scope
Useruser collectionPOST /profile/signinthe User's pkAdmin, multi-org access via roles
Customercustomer collectionPOST /profile/customer/signinthe Customer's pkSingle-org, customer-scoped data

Both flows return the same JWT shape — the difference is what the JWT unlocks. A Customer JWT can hit /storefront/cart, /profile/customer/update, /client-data/*. A User JWT additionally unlocks admin endpoints (/crm/leads, /repository/*, /org-management/*) within the orgs the user is a member of.

See Principals and tenancy for the full split.

What's in the JWT

Both flows return the same envelope:

{
  "data": {
    "token": "eyJhbGc...",
    "refresh_token": "eyJhbGc...",
    "user": { "pk": "...", "email": "...", "firstName": "...", "roles": [...], ... }
  }
}

The access token is short-lived; the refresh token has a longer expiry. When the access token expires, exchange the refresh token for a new pair.

POST/profile/user/refreshNo auth
POST/profile/customer/refreshNo auth

OAuth sign-in

AppEngine ships passport strategies for several providers — they're all sign-in methods, not authorization-grant flows. The user goes through the provider, AppEngine catches the callback, validates the profile, and issues an AppMint JWT. The provider's own access token is not what subsequent calls use.

Supported providers, all under /profile/{provider}:

  • GitHub/profile/github, /profile/github/redirect
  • Google/profile/google, /profile/google/redirect
  • Facebook/profile/facebook, /profile/facebook/redirect
  • Microsoft — strategy file present (microsoft.strategy.ts); enable per-org
  • Magic link/profile/magic-link, /profile/magic-link/redirect (Customer); /profile/user/magic-link/* (User)
  • Code-based/profile/code/{email} for one-time-code auth

See OAuth flows for the redirect/callback pattern and per-provider setup.

API keys

For server-to-server calls — backend services, build pipelines, scripts — JWT sessions don't fit. API keys give you a long-lived credential sent as x-api-key:

GET /repository/find/contact HTTP/1.1
Host: appengine.appmint.io
orgid: my-org
x-api-key: ak_live_5f3a2b...

Keys are issued from Studio Manager (or the /api-key/create endpoint), scoped to a User principal, and carry their own permission scope and rate limits. Rotation, revocation, and usage tracking are first-class.

See API keys for issuance and lifecycle.

RBAC and permissions

Once authenticated, what a principal can do depends on:

  • RoleRootAdmin, ConfigAdmin, ContentAdmin, plus any custom roles. Checked via @Roles(...).
  • Permission verbscreate, read, update, delete, review, approve. Checked via @RequirePermissions(...).
  • Per-record requiredRole — collections and individual records can override the role/permission matrix.

Endpoints stack these decorators on top of the global JWT guard. A request with the wrong role gets 403, not 401 — auth succeeded; authorization didn't.

See Roles and permissions.

2FA and device security

Optional second factor and device tracking sit under /profile/security/*. Once enabled for a User, sign-in returns a partial token that requires a 2FA challenge before becoming a full session. Device records track every login and let admins revoke individual sessions.

See Two-factor and devices.

SSO

A separate SSO surface (/sso/*) supports cross-org authentication and IDE-driven pre-authenticated flows. Useful when one Appmint account spans multiple tenants.

See SSO.

Quick reference — which header for which call

# Public — only orgid
curl https://appengine.appmint.io/storefront/products -H "orgid: my-org"

# Customer or User session
curl https://appengine.appmint.io/repository/find/contact \
  -H "orgid: my-org" \
  -H "Authorization: Bearer eyJhbGc..."

# Server-to-server with API key
curl https://appengine.appmint.io/repository/find/contact \
  -H "orgid: my-org" \
  -H "x-api-key: ak_live_..."

orgid is always required. JWT and API key are mutually exclusive in practice, but if you send both, JWT wins.