awesome-node-auth

September 27, 2026 · View on GitHub

npm version license github stars

NPM

A production-ready, database-agnostic JWT authentication library for Node.js written in TypeScript. Drop-in auth for Express, NestJS, Next.js, Fastify and any other Node.js framework — connect to any database through a single interface.

The self-hosted alternative to Supertokens and Supabase Auth. Same enterprise-grade features, zero vendor lock-in.


Installation

npm install @awesome-lang-auth/node

Up to 1.10.0 the package was published as awesome-node-auth. To migrate, replace the dependency with @awesome-lang-auth/node and change your imports from 'awesome-node-auth' to '@awesome-lang-auth/node'; the API is the same.

Quick Start

import express from 'express';
import { AuthConfigurator, AuthEventBus } from '@awesome-lang-auth/node';
import { myUserStore } from './my-user-store'; // your IUserStore impl

const app = express();
app.use(express.json());

const eventBus = new AuthEventBus();

const auth = new AuthConfigurator(
  {
    accessTokenSecret: process.env.ACCESS_TOKEN_SECRET!,
    refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET!,
    accessTokenExpiresIn: '15m',
    refreshTokenExpiresIn: '7d',
  },
  myUserStore,
  { eventBus },
);

app.use(auth.buildAllRouters({
  admin: {
    accessPolicy: 'is-admin-flag', // admin panel for users with isAdmin: true
  },
})); // mounts /auth/* and /auth/admin/*

// Grant the admin panel from a seed script or CLI task (sets isAdmin; needs IUserStore.update):
//   await auth.promoteToAdmin(userId, { method: 'flag' });

app.get('/protected', auth.middleware(), (req, res) => {
  res.json({ user: req.user });
});

app.listen(3000);

Implement IUserStore once for your database and you're done.
Full DB examples (MongoDB, PostgreSQL, MySQL, in-memory) → README.detailed.md.


Features

AreaHighlights
Auth strategiesEmail/password · OAuth 2.0 (Google, GitHub, custom) · Magic links · SMS OTP · TOTP 2FA
Token managementHttpOnly-cookie or Bearer mode · automatic access/refresh rotation · __Host-/__Secure- cookie prefixes
Identity Provider (IdP) mode (v1.9)RS256-signed JWTs · public JWKS endpoint (/.well-known/jwks.json) · Resource Server middleware · zero new dependencies
Stateful sessions (v1.5)ISessionStore + real-time revocation (checkOn: allcalls|refresh|none) · works behind your own L1/L2 cache layers
Dynamic email templates (v1.6)ITemplateStore — per-language mail templates + UI i18n with safe hardcoded fallback · built-in MemoryTemplateStore
CSRF protectionDouble-submit cookie pattern · __Host- prefix hardening against cookie-tossing
Account managementRegistration · change email/password · account deletion · email verification (none/lazy/strict)
Account linkingLink multiple OAuth providers · conflict resolution via IPendingLinkStore
RBACIRolesPermissionsStore with tenant awareness
Multi-tenancyITenantStore for isolated tenant apps
Admin panelFull-featured admin UI: user management, sessions, roles, tenants, metadata, API keys, webhooks
Built-in UIZero-dependency HTML/CSS/JS login UI served at <apiPrefix>/ui/ · headless mode for SPAs
Client librariesAngular · Flutter · React · served auth.js — see Ecosystem
Event-drivenAuthEventBus · SSE push · inbound/outbound webhooks · telemetry
API keysM2M bcrypt-hashed keys with scopes, expiry, IP allowlist and audit log
OpenAPI / SwaggerAuto-generated specs for auth, admin and tools routers

Identity Provider (IdP) Mode (v1.9)

Turn any Provisioner into a central IdP that issues RS256-signed JWTs. Downstream Resource Servers validate tokens via the public JWKS endpoint — no shared secrets.

Generate the RSA private key for your .env (uses Node.js built-in crypto — no install needed):

node -e "
const { generateKeyPairSync } = require('crypto');
const { privateKey } = generateKeyPairSync('rsa', { modulusLength: 2048 });
const pem = privateKey.export({ type: 'pkcs8', format: 'pem' });
console.log('IDP_PRIVATE_KEY=' + Buffer.from(pem).toString('base64'));
"

Then in your config:

// Provisioner (IdP)
const auth = new AuthConfigurator({
  accessTokenSecret: '...',
  refreshTokenSecret: '...',
  idProvider: {
    enabled: true,
    issuer: 'https://auth.myplatform.com',
    privateKey: Buffer.from(process.env.IDP_PRIVATE_KEY!, 'base64').toString('utf8'),
  },
}, userStore);

// Resource Server (downstream)
import { createJwksAuthMiddleware } from '@awesome-lang-auth/node';

app.use('/api', createJwksAuthMiddleware({
  jwksUrl: 'https://auth.myplatform.com/.well-known/jwks.json',
  issuer:  'https://auth.myplatform.com',
}), myApiRouter);

In development, if you omit privateKey an ephemeral keypair is auto-generated at startup.

→ Full guide: awesomenodeauth.com/docs/advanced/idp-mode


Key Endpoints

POST   /auth/login              POST /auth/refresh           GET  /auth/me
POST   /auth/register           POST /auth/logout            GET  /auth/sessions        ← device list
POST   /auth/forgot-password    POST /auth/change-password   DELETE /auth/sessions/:h   ← revoke device
POST   /auth/magic-link/send    POST /auth/2fa/verify        DELETE /auth/account
GET    /auth/oauth/:provider    GET  /auth/oauth/:provider/callback
POST   /auth/sessions/cleanup   POST /auth/add-phone         PATCH /auth/profile
GET    /.well-known/jwks.json                                                            ← IdP mode only

Optional Stores Snapshot

const auth = new AuthConfigurator(
  { ...config, templateStore }, // ITemplateStore — dynamic email templates + UI i18n (v1.6), part of AuthConfig
  userStore,
  { eventBus },                 // optional; the third argument only accepts { eventBus }
);

app.use('/auth', auth.router({
  sessionStore,        // ISessionStore        — stateful sessions + device management
  metadataStore,       // IUserMetadataStore   — arbitrary per-user key/value pairs
  rbacStore,           // IRolesPermissionsStore
  tenantStore,         // ITenantStore
  linkedAccountsStore, // ILinkedAccountsStore — several OAuth providers per user
  pendingLinkStore,    // IPendingLinkStore    — OAuth account-linking conflicts (with linkedAccountsStore)
}));

app.get('/protected', auth.middleware(), handler); // uses the sessionStore passed to router() above (call router() first)

With buildAllRouters(), pass the same stores as auth: { … }; the admin panel takes its own in admin: { … } (see Admin UI).

Full configuration reference → README.detailed.md § Configuration


Admin UI

Use auth.buildAllRouters({ admin: ... }) to mount both the main auth router and the admin router together. The admin router lives at /auth/admin/*, and jwtSecret is auto-filled from AuthConfig.accessTokenSecret. Set accessPolicy (or a non-empty legacy adminSecret): without either, the admin routes are mounted unprotected and a WARNING is written to stderr. Without accessPolicy, an adminSecret that is present but empty (an unset environment variable, for example) throws a configuration error at startup.

admin option (AdminOptions)Unlocks
sessionStoreSessions tab
rbacStoreRoles & Permissions tab
tenantStoreTenants tab
userMetadataStoreMetadata section in user detail
settingsStore⚙️ Control tab
linkedAccountsStoreLinked Accounts column
apiKeyStore🔑 API Keys tab
webhookStore🔗 Webhooks tab
templateStoreEmail & UI tab
uploadDir (optionally uploadBaseUrl)Logo upload in branding

Two login endpoints, two audiences

  • /auth/ui/login — end-user login for your application (built-in UI, ui: { enabled: true })
  • /auth/admin/ — admin panel for operators (its sign-in form posts to /auth/admin/login)

They are intentionally different flows. If you mount the admin UI for operators, keep linking end users to /auth/ui/login.

The admin sign-in form checks the password only, with no second factor, and its session opens the admin console and nothing else. To send operators through the application login and its 2FA flow, set admin.loginPath: '/auth/ui/login' (after signing in they land on /; reopen /auth/admin/). POST /auth/admin/login stays mounted and still accepts the password alone, so block it at your proxy if operators must always pass 2FA.


Ecosystem

awesome-node-auth is the reference server of a family of libraries that port its HTTP API to other runtimes, plus client libraries for that API.

Servers

RuntimeRepositoryStatus
Node.jsawesome-node-auth (this repo)npm @awesome-lang-auth/node
Goawesome-go-authGo module, 0.11.x · 1.0 in progress
AWS Lambdaawesome-lambda-authPreview
Pythonawesome-python-authPyPI awesome-python-auth 1.1.0
Rustawesome-rust-authGit only (not on crates.io)
Dartawesome-dart-authGit only (not on pub.dev)

Clients

ClientPackageStatus
Angularng-awesome-node-authnpm · to be renamed @awesome-lang-auth/angular
Flutterawesome_node_auth_flutterpub.dev · to be renamed awesome_flutter_auth
React@awesome-lang-auth/reactnpm 0.1.0
Browserauth.jsServed by this library at <apiPrefix>/ui/auth.js when ui.enabled is set — see Including auth.js

Documentation

ResourceLink
Full referenceREADME.detailed.md
Wiki / Guidesawesomenodeauth.com
ChangelogCHANGELOG.md
Demo appsdemo/
Framework examplesexamples/

The companion MCP server (awesome-node-auth-mcp-server) has been retired and is no longer available.


License

MIT · © 2026 nik2208 · Sponsor ❤