@mostajs/auth-lite
v1.2.0
Published
Password and email-code (6 digits + link) login ENGINE (framework-neutral) + node:http / Express / Next adapters + view-models. PBKDF2-SHA256 600k by default, no native addon — boots everywhere; Argon2id injectable on a server. Reads 0.x hashes and rehash
Maintainers
Readme
@mostajs/auth-lite
Password login: a framework-neutral engine, transport adapters for
node:http, Express and Next.js, and view-models rendered by@mostajs/auth-ui(HTML) or@mostajs/auth-ui-react. No native addon → it boots in Bolt.new / StackBlitz / WebContainers / the edge, on the first try. On a server, inject Argon2id.
auth-lite is the readable, dependency-free auth brick. Since 1.0 it hashes with
PBKDF2-SHA256 at 600,000 iterations (the OWASP 2025 floor, Node core crypto), verifies —
and silently re-hashes on login — the 0.x format, and ships a login / reset / set-password
engine that any host can mount. The Next.js surface is unchanged: a ready-made Session
schema, login/signup/logout Route Handlers, and a getCurrentUser() for Server Components.
1.0 at a glance
| subpath | what | for |
|---|---|---|
| . | createPasswordAuth engine, Hasher port, policy, rate limiter | everyone |
| ./node | createNodeHandlers — no framework | node:http apps |
| ./express | passwordMiddleware — does not import Express | Express apps |
| ./views | screen models (fields, states, actions) — the contract between screen and route | @mostajs/auth-ui, @mostajs/auth-ui-react |
| ./next | Route Handlers + getCurrentUser — API unchanged | Next.js apps |
The engine never opens a session: it returns userId, and the host opens its own — so a
password and a magic link open the very same session. Full reference: llms.txt. It was extracted from the
mostajs-saas-starter and hardened until it actually boots inside the StackBlitz
WebContainer — the lessons learned are baked into its API (see Why "lite").
Need OAuth, MFA, WebAuthn, magic links, Argon2id, refresh tokens? Use the full
@mostajs/authinstead.auth-liteand@mostajs/authare alternatives — pick one (see comparison).
Install
npm i @mostajs/auth-lite @mostajs/orm nextPeer requirements: @mostajs/orm >= 2.5.2, next >= 14.
To boot in a browser / WebContainer, use one of the ORM's WASM dialects (sqljs or pglite)
so there is zero native binary in the dependency tree.
Quickstart (5 steps)
1 · Schemas — your User + the bundled Session
Your User entity must have at least email (unique), passwordHash, and name.
// lib/orm/schemas.ts
import type { EntitySchema } from '@mostajs/orm';
export { SessionSchema } from '@mostajs/auth-lite';
export const UserSchema: EntitySchema = {
name: 'User',
collection: 'users',
fields: {
email: { type: 'string', required: true, unique: true, lowercase: true, trim: true },
name: { type: 'string', required: true, trim: true },
passwordHash: { type: 'string', required: true },
},
indexes: [{ fields: ['email'], unique: true }],
timestamps: true,
};2 · Repos — expose getRepos()
// lib/orm/index.ts
import { BaseRepository } from '@mostajs/orm';
import { getDialect } from '@mostajs/orm';
import { SessionSchema } from '@mostajs/auth-lite';
import { UserSchema } from './schemas';
export type User = { id: string; email: string; name: string; passwordHash: string };
export async function getRepos() {
const dialect = await getDialect(
{ dialect: 'sqljs', uri: ':memory:' }, // WASM → boots in Bolt/StackBlitz
[UserSchema, SessionSchema],
);
return {
users: new BaseRepository<User>(UserSchema, dialect),
sessions: new BaseRepository(SessionSchema, dialect),
};
}3 · Route Handlers — login / signup / logout
// app/api/auth/login/route.ts
import { createAuthHandlers } from '@mostajs/auth-lite';
import { getRepos } from '@/lib/orm';
export const POST = createAuthHandlers({ getRepos }).login;// app/api/auth/signup/route.ts
import { createAuthHandlers } from '@mostajs/auth-lite';
import { getRepos } from '@/lib/orm';
export const POST = createAuthHandlers({ getRepos }).signup;// app/api/auth/logout/route.ts
import { createAuthHandlers } from '@mostajs/auth-lite';
import { getRepos } from '@/lib/orm';
export const POST = createAuthHandlers({ getRepos }).logout;4 · Read the session in Server Components
// lib/auth.ts
import { createGetCurrentUser } from '@mostajs/auth-lite';
import { getRepos, type User } from '@/lib/orm';
export const getCurrentUser = createGetCurrentUser<User>({ getRepos });5 · Forms post to the handlers (works without client JS)
// app/login/page.tsx
export default function LoginPage() {
return (
<form action="/api/auth/login" method="post">
<input name="email" type="email" required />
<input name="password" type="password" required />
<button type="submit">Log in</button>
</form>
);
}Guard each protected page:
// app/dashboard/page.tsx
import { redirect } from 'next/navigation';
import { getCurrentUser } from '@/lib/auth';
export const dynamic = 'force-dynamic';
export default async function Dashboard() {
const user = await getCurrentUser();
if (!user) redirect('/login'); // ← per page, not only in the layout
return <p>Welcome, {user.name}</p>;
}Email sign-in: 6-digit code + link (1.2)
import { createEmailCodeAuth, ipClient } from '@mostajs/auth-lite';
const ec = createEmailCodeAuth({ findUser });
const d = await ec.request(email, { ip: ipClient(req), tenant }); // null for unknown OR throttled
// send d.code and `${base}/login/link?t=${d.token}` — respond first, send after
await ec.verifyCode(email, code, { ip, tenant }); // or: peekLink on GET, verifyLink on POSTOne challenge = code + link token, stored as digests only, 15 min, single use (one consumes the
other), 5 wrong codes kill it, bound to the tenant. The password limiter now counts failures
only, per tenant, on the real client IP (ipClient); loginThrottle is a standalone guard for
hosts without the engine. See llms.txt.
API
| Export | Signature | Purpose |
|---|---|---|
| hashPassword | (password: string) => string | Salted, 10 000× iterated SHA-256 → "salt:hashHex". No native addon. |
| verifyPassword | (password: string, stored: string) => boolean | Constant-time check (timingSafeEqual). |
| SessionSchema | EntitySchema | Session entity (token unique, expiresAt, user → User M-1 cascade). Register it alongside User. |
| createAuthHandlers | (config) => { login, signup, logout } | Each is (req: NextRequest) => Promise<NextResponse>; export as POST. |
| createGetCurrentUser | <TUser>(config) => () => Promise<TUser \| null> | Resolve the logged-in user from the cookie (read before any DB call). |
AuthLiteConfig
interface AuthLiteConfig {
getRepos: () => Promise<{ users: AuthRepo; sessions: AuthRepo }>; // required
cookieName?: string; // default "session"
ttlDays?: number; // default 7
afterAuth?: string; // default "/dashboard"
afterLogout?: string; // default "/"
loginErrorPath?: string; // default "/login?error=invalid"
signupErrorPath?: (kind: 'invalid' | 'exists') => string; // default "/signup?error=<kind>"
}AuthRepo is the minimal subset this module needs (findOne, create, delete,
findByIdWithRelations) — fully satisfied by @mostajs/orm's BaseRepository.
Why "lite" — WebContainer-safe by design
Tested for real in StackBlitz; the failures it hit are now prevented by the API itself:
- Cookies via Route Handlers, not
cookies(). Some runtimes (StackBlitz WebContainer) lose Next's request async-context (AsyncLocalStorage) across anawaiton the DB, which makes a latercookies()throw "called outside a request scope".createAuthHandlerssets the cookie on theNextResponseobject (res.cookies.set) — that works everywhere. - Read the cookie before the DB.
getCurrentUserreads the cookie while the request context is still intact, then resolves the session. - No native binary. SHA-256 from Node core
crypto— no bcrypt/argon2 to compile. For a classic production server you can swap in argon2/scrypt (thehashPassword/verifyPasswordAPI is unchanged) or move to@mostajs/auth. - Guard per page. Always
if (!user) redirect('/login')in each protected page — a layout guard alone is not enough (layout and page run in parallel →null.idcrash).
auth-lite vs @mostajs/auth
| | @mostajs/auth-lite | @mostajs/auth |
|---|---|---|
| Password hash | SHA-256 iterated (core crypto) | Argon2id |
| Methods | email/password + sessions | email/password, OAuth/OIDC, magic link, MFA TOTP, WebAuthn/Passkeys |
| Stack | @mostajs/orm + Next Route Handlers | NextAuth v5 + @mostajs/rbac |
| Native deps | none (WebContainer/edge ready) | argon2 etc. (server) |
| Footprint | one file, readable | ~30 sub-paths, full-featured |
| Best for | starters, MVPs, Bolt/StackBlitz demos | production apps needing rich auth |
They are alternatives — depend on one, not both.
License
AGPL-3.0-or-later. A commercial license is available for proprietary/closed-source use —
see LICENSE.
Author: Dr Hamid MADANI [email protected]
