Browse documentation
DOCS/Auth module

Auth module

The auth template is an explicit authentication starter module for ExetJS. It provides register, login, logout and current-user routes, password hashing, JWT helpers and a replaceable repository contract.

Full public documentation is available at https://exetjs.com/.

Install

exet install auth

The Auth template shipped with ExetJS CLI 0.2.0 requires @exetjs/core 0.2.0 or newer.

The installer adds:

JWT_SECRET=
JWT_EXPIRES_IN=1d

and installs:

bcryptjs
jsonwebtoken
@types/jsonwebtoken

Default route prefix

The module uses:

name: "auth";
basePath: "/auth";

With the default application API prefix /api/v1, the routes are:

MethodRoutePurpose
POST/api/v1/auth/registerRegister a user
POST/api/v1/auth/loginAuthenticate and issue a JWT
POST/api/v1/auth/logoutReturn a successful logout response
GET/api/v1/auth/meResolve the current user from a bearer token

If the application changes API_PREFIX, the route prefix changes with it.

Request validation

Register

{
  "name": "Jane Doe",
  "email": "jane@example.com",
  "password": "at-least-8-characters"
}

Validation rules:

FieldRule
namestring, minimum 2 characters
emailvalid email address
passwordstring, minimum 8 characters

A successful registration returns HTTP 201 and does not expose the password hash.

Example response:

{
  "success": true,
  "data": {
    "id": "generated-user-id",
    "name": "Jane Doe",
    "email": "jane@example.com",
    "createdAt": "2026-09-20T00:00:00.000Z"
  }
}

Login

{
  "email": "jane@example.com",
  "password": "at-least-8-characters"
}

Validation rules:

FieldRule
emailvalid email address
passwordnon-empty string

A successful login returns HTTP 200:

{
  "success": true,
  "data": {
    "user": {
      "id": "generated-user-id",
      "name": "Jane Doe",
      "email": "jane@example.com",
      "createdAt": "2026-09-20T00:00:00.000Z"
    },
    "token": "jwt-token"
  }
}

Invalid credentials return an unauthorized application error without identifying whether the email or password was wrong.

Current user

Send the JWT as a bearer token:

Authorization: Bearer <token>
GET /api/v1/auth/me

Missing, invalid or expired bearer tokens are rejected. A token is also rejected if its user can no longer be found by the configured repository.

Logout

POST /api/v1/auth/logout

The starter currently returns:

{
  "success": true,
  "data": {
    "loggedOut": true
  }
}

The starter uses stateless JWTs and does not maintain a revocation list or server-side session. Calling logout therefore does not invalidate an already issued token. Applications that require immediate token revocation must add their own session, token blacklist, rotation or revocation policy.

Environment variables

VariableRequiredDefaultNotes
JWT_SECRETproduction: yesdevelopment fallback onlyUse a strong private value in production
JWT_EXPIRES_INno1dPassed to jsonwebtoken as the token lifetime

In NODE_ENV=production, startup or first JWT use without JWT_SECRET throws an error. The built-in development fallback exists only for local development and must not be relied on in production.

Password hashing

Passwords are hashed with bcryptjs using 12 salt rounds. Password hashes are stored only through the repository contract and are never returned by the public Auth API.

Repository contract

The public storage contract is:

export type AuthRepository = {
  create: (user: AuthUser) => Promise<AuthUser>;
  findByEmail: (email: string) => Promise<AuthUser | null>;
  findById: (id: string) => Promise<AuthUser | null>;
};

The default repository is in-memory:

createInMemoryAuthRepository();

createAuthRepository is retained as a compatibility alias for the same in-memory implementation.

The in-memory repository is scoped to one Auth module factory instance and is not persistent. Restarting the process loses all users.

Module factory

import { createAuthModule } from "./modules/auth/index.js";

const authModule = createAuthModule({
  repository,
  name: "auth",
  dependsOn: ["db-postgres"],
});

Options:

OptionTypeDefaultPurpose
repositoryAuthRepository or factoryin-memory repositoryReplace storage implementation
namestringauthRequired to distinguish multiple Auth instances
dependsOnreadonly string[]noneDeclare DB/resource dependencies used by the repository

A repository factory receives the ExetContext and may be asynchronous:

const authModule = createAuthModule({
  dependsOn: ["db-postgres"],
  repository: async (ctx) => {
    ctx.logger.info("Creating persistent Auth repository");
    return createPostgresAuthRepository();
  },
});

Connecting Auth to a database

DB modules own connection lifecycle. Auth owns authentication behavior. The application repository owns schema mapping and persistence.

Example shape with PostgreSQL:

import type { AuthRepository } from "./auth/index.js";
import { createAuthModule } from "./auth/index.js";
import { dbPostgres } from "./db-postgres/index.js";

const authRepository: AuthRepository = {
  async create(user) {
    await dbPostgres.getClient().query(
      `INSERT INTO auth_users (id, name, email, password_hash, created_at)
       VALUES ($1, $2, $3, $4, $5)`,
      [user.id, user.name, user.email, user.passwordHash, user.createdAt],
    );
    return user;
  },
  async findByEmail(email) {
    const result = await dbPostgres.getClient().query(
      `SELECT id, name, email, password_hash AS "passwordHash", created_at AS "createdAt"
       FROM auth_users WHERE email = $1`,
      [email],
    );
    return result.rows[0] ?? null;
  },
  async findById(id) {
    const result = await dbPostgres.getClient().query(
      `SELECT id, name, email, password_hash AS "passwordHash", created_at AS "createdAt"
       FROM auth_users WHERE id = $1`,
      [id],
    );
    return result.rows[0] ?? null;
  },
};

export const authModule = createAuthModule({
  repository: authRepository,
  dependsOn: ["db-postgres"],
});

Create the schema, unique email constraint and migrations in application-owned migration files. The Auth template does not create tables, indexes or migrations.

Public exports

The installed module exports:

authModule;
createAuthModule;
createAuthRepository;
createInMemoryAuthRepository;

and these public types:

AuthModuleOptions;
AuthRepositoryFactory;
AuthRepository;
AuthUser;
PublicAuthUser;
AuthTokenPayload;

Production checklist

Before production use:

The starter is intentionally small and explicit. It is not a complete identity platform.