Skip to content

@json-express/plugin-identity

Official Identity & Auth plugin for JSONExpress.

@json-express/plugin-identity implements IPlugin and is auto-discovered by the json-express runtime. It contributes the users and roles schemas, mounts the /auth/* route group, hashes passwords with argon2id, issues JWTs, and rotates refresh tokens through the configured IKvStore. Verification, password-reset, and admin-flow emails are dispatched through the configured IQueueAdapter and IEmailProvider when both are present.

For the high-level architecture and the tokenVersion revocation pattern, see Identity & Auth.

Installation

bash
npm install @json-express/plugin-identity \
            @json-express/middleware-auth \
            @json-express/kv-memory \
            @json-express/queue-memory \
            @json-express/email-console

middleware-auth and an IKvStore are required peers — the plugin throws on boot without them. The queue and email provider are optional but needed for the verification and reset flows.

For a one-line install of the full identity stack, use the @json-express/preset-identity preset, which depends on all five.

Configuration

The plugin is constructed by the runtime as new IdentityPlugin({ configProvider, logger }). You do not instantiate it manually. All configuration comes from the config provider — typically .env parsed by @json-express/config-env.

Minimum .env

bash
jex.auth.secret=a-strong-32-byte-secret
jex.auth.exclude=/auth

jex.auth.exclude=/auth tells middleware-auth not to require a JWT on the auth endpoints themselves — registration and login would otherwise be unreachable.

Full configuration reference

All keys live under jex.auth.*. Boolean and number values are parsed by the config provider; durations accept ms, s, m, h, d suffixes.

KeyTypeDefaultEffect
jex.auth.secretstring– (required)HMAC secret for signing access tokens.
jex.auth.jwksUristringJWKS endpoint URL — when set, middleware-auth validates with asymmetric keys.
jex.auth.algorithmsstring[]Restrict accepted JWT algorithms (e.g. RS256).
jex.auth.tokenTtlduration1hAccess token lifetime.
jex.auth.refreshTtlduration30dRefresh token lifetime in the KV store.
jex.auth.verifyTtlduration24hLifetime of email verification tokens.
jex.auth.resetTtlduration30mLifetime of password-reset tokens.
jex.auth.issuerstringJWT iss claim.
jex.auth.audiencestring | string[]JWT aud claim.
jex.auth.allowRegistrationbooleantrueWhen false, POST /auth/register returns 403.
jex.auth.defaultRolestringuserRole assigned to self-registered users.
jex.auth.requireVerifiedEmailbooleanfalseWhen true, login is rejected until email is verified.
jex.auth.minPasswordLengthnumber8Minimum password length enforced on register / change / reset.
jex.auth.email.appNamestringJSON ExpressUsed in email templates.
jex.auth.email.verifyUrlstringhttp://localhost:3000/auth/verifyLink target placed in verification emails.
jex.auth.email.resetUrlstringhttp://localhost:3000/auth/password/resetLink target placed in password-reset emails.
jex.auth.email.fromstringFrom: header for outbound emails.

Mounted routes

The plugin registers the following routes on kernel.boot():

MethodPathPurpose
POST/auth/registerSelf-registration (gated by allowRegistration)
POST/auth/loginIssue access + refresh tokens
POST/auth/refreshRotate refresh token, issue new access token
POST/auth/logoutRevoke the active refresh token
POST/auth/password/changeChange password while authenticated
POST/auth/verifyConfirm email via verification token (mounted only when an IEmailProvider is installed)
POST/auth/verify/resendResend verification email
POST/auth/password/forgotTrigger password reset email
POST/auth/password/resetSet a new password using a reset token

All /auth/* routes are public — gating /login behind the JWT verifier would be a chicken-and-egg problem. Add /auth to jex.auth.exclude in your env so the verifier skips them.

The injected users schema

The plugin's provideSchemas() contributes a strict users schema. If a user-defined models/users.ts or data/users.json exists, the plugin's schema overrides it — this guarantees passwordHash and tokenVersion cannot leak through generated API responses.

typescript
// internal — for reference only
defineModel({
    name: 'users',
    access: {
        read: 'public',
        create: 'admin',     // only admins can provision new users
        update: 'owner',
        delete: 'admin'
    },
    fields: {
        id: types.id(),
        email: types.string({ unique: true }),
        role: types.string({ default: 'user' }),
        emailVerified: types.boolean({ default: false }),
        // never exposed in API responses
        passwordHash: types.string({ access: { read: false } }),
        tokenVersion: types.number({ default: 0, access: { read: false } })
    }
});

Admin auto-seeding

When the plugin boots and finds an empty users collection, it automatically creates an admin@local account with role admin and a randomly generated password. The password is logged once to the configured logger so you can copy it on first run. Change it immediately via POST /auth/password/change.

Asynchronous email dispatching

When a queue and an email provider are both registered, password-reset and verification emails are enqueued on the emails topic and processed by a worker the plugin registers internally. Without a queue, the admin-flow falls back to a synchronous send. Without an email provider, the email-dependent endpoints simply do not mount.

typescript
// internal — how the plugin enqueues
await queue.enqueue('emails', 'sendPasswordReset', {
    userId: record.id,
    email: record.email,
    token: rawToken
});