Files
WRNexusJS/packages/auth/README.md
Clintchiz 586a6db8ff
Quality / quality (ubuntu-latest) (push) Failing after 21s
Quality / quality (windows-latest) (push) Canceled after 0s
release: WRNexusJS 0.8.0
2026-08-02 23:18:51 +05:30

382 lines
12 KiB
Markdown

# @wrnexus/auth
Framework-native authentication, identity, account-security, and session management for WRNexusJS.
## Capabilities
- Password registration, login, recovery, reset, and authenticated password changes
- Email, phone, and username identities with verification and generic resend responses
- Magic links and passwordless email/SMS OTP login
- MFA transactions using verified email OTP, verified SMS OTP, TOTP, or recovery codes
- RFC 6238 TOTP with counter replay protection
- One-use recovery codes; regeneration invalidates previous unused codes
- Passkey/WebAuthn registration and strong passwordless sign-in through a provider contract
- OAuth account linking and provider sign-in
- Invitations, session rotation, idle and absolute expiry, revocation, and trusted devices
- Deny-by-default audited support impersonation
- Adaptive risk scoring, CAPTCHA escalation, temporary lockout, and optional login alerts
- Memory and SQL stores
- Optional encryption-keyring protection for TOTP and OAuth secrets
- Automatic API routes, middleware, browser schemas, components, runtime, migrations, and DevToolbar checks
Passkeys are a strong sign-in method. They are not currently exposed as a selectable second step in `TwoFactorChallenge`; the implemented MFA methods are email OTP, SMS OTP, TOTP, and recovery codes.
## Install
```bash
bun add @wrnexus/auth
```
WRNexusJS discovers the package automatically. Do not copy package components, client scripts, schemas, or standard `/api/auth/*` route files into the application.
## Default configuration
Create the engine:
```ts
// app/lib/auth.ts
import { createAuthEngine, MemoryAuthStore } from "@wrnexus/auth";
export const auth = createAuthEngine({
store: new MemoryAuthStore(),
secret: process.env.AUTH_SECRET!,
issuer: "My application",
onSignedIn(ctx, returnTo) {
const safe = returnTo?.startsWith("/") && !returnTo.startsWith("//") ? returnTo : "/account";
return Response.redirect(new URL(safe, ctx.url), 303);
},
onSignedOut(ctx) {
return Response.redirect(new URL("/sign-in", ctx.url), 303);
},
onSuccessfulSignUp() {
return {
autoSignIn: true,
redirectTo: "/account",
};
},
delivery: {
async send(message) {
// Queue email/SMS through your provider. Never log message.code or message.token.
},
},
});
```
Authentication behavior belongs in this engine definition: delivery, token URL
mapping, successful sign-in/sign-out responses, password policy, risk thresholds,
MFA, passkeys, and auditing can all be configured in one server-only location.
The older `config.auth.onSignedIn` and `config.auth.onSignedOut` fields remain
supported as compatibility overrides, but new applications should configure
these hooks on `createAuthEngine`.
### Successful signup behavior
Without `onSuccessfulSignUp`, a successful package registration redirects to
`/sign-in`.
To sign in immediately after registration:
```ts
onSuccessfulSignUp(ctx, user) {
return {
autoSignIn: true,
redirectTo: "/account",
};
}
```
Automatic sign-in runs the normal login policy. It does not bypass required
email or phone verification, CAPTCHA, MFA, account status, or risk checks. The
hook may also return a `Response` for a completely custom HTTP result, or return
`{ redirectTo: "/welcome" }` to redirect without creating a session.
Register it through application configuration:
```ts
// wrnexus.config.ts
import type { AuthConfig } from "@wrnexus/auth";
import type { AppConfig } from "@wrnexus/styles";
import { auth } from "./app/lib/auth.ts";
const config = {
auth: {
engine: auth,
routes: true,
middleware: true,
migrations: false,
},
} satisfies AppConfig & { auth: AuthConfig };
export default config;
```
That configuration automatically activates package routes, auth-session middleware, components, browser validation schemas, and the auth client runtime. `setDefaultAuthEngine()` remains available only for advanced manual integrations and tests.
## SQL production configuration
```ts
import { createAuthEngine, SqlAuthStore } from "@wrnexus/auth";
import { getDb } from "@wrnexus/db";
export const auth = createAuthEngine({
store: new SqlAuthStore(getDb()),
secret: process.env.AUTH_SECRET!,
});
```
```ts
export default {
db: {
// Application database configuration.
},
auth: {
engine: auth,
routes: true,
middleware: true,
migrations: true,
},
};
```
The package contributes both ordered migrations:
```text
001_auth.sql
002_auth_otp_purpose.sql
```
Migrations are enabled automatically only when `auth.engine` and a default `config.db` are present. Set `auth.migrations` explicitly when an application needs different behavior.
## Delivered action URLs
By default, the engine builds links from the supplied `baseUrl` and token purpose. Applications can map those links to their own page structure without replacing package APIs:
```ts
const auth = createAuthEngine({
store: new SqlAuthStore(getDb()),
secret: process.env.AUTH_SECRET!,
tokenUrl({ purpose, token, baseUrl }) {
if (!baseUrl) return undefined;
const paths = {
"verify-email": `/verify-email?token=${encodeURIComponent(token)}`,
"verify-phone": `/verify-phone?token=${encodeURIComponent(token)}`,
"password-reset": `/recover/reset?token=${encodeURIComponent(token)}`,
"magic-link": `/magic-link?token=${encodeURIComponent(token)}`,
invite: `/invitation?token=${encodeURIComponent(token)}`,
};
const path = paths[purpose as keyof typeof paths];
return path ? new URL(path, baseUrl).toString() : undefined;
},
});
```
Returning `undefined` intentionally omits the URL while still delivering the raw token. The callback runs only in trusted server code.
## Built-in validation
Every packaged auth form has a built-in `@wrnexus/validation` schema. The same resolved schema is used by the browser and the package API handler.
Default use requires no `app/schemas` files:
```wrn
<SignUp />
<SignIn />
<ForgotPassword />
<ResetPassword token='{token}' />
<TwoFactorChallenge />
```
To customize one schema, extend the package default and register only that override:
```ts
// app/schemas/custom-password-request.ts
import { authSchemas } from "@wrnexus/auth";
import { v } from "@wrnexus/validation";
export default authSchemas.passwordResetRequest.extend({
identifier: v
.string()
.trim()
.required("Enter your registered email address")
.email("Enter a valid registered email address"),
});
```
```ts
import customPasswordRequest from "./app/schemas/custom-password-request.ts";
export default {
auth: {
engine: auth,
schemas: {
passwordResetRequest: customPasswordRequest,
},
},
};
```
`<ForgotPassword />` can keep its default `schema="auth-password-request"`. The plugin automatically publishes the overridden browser descriptor under that same built-in schema ID. All other forms continue using package defaults.
## Route controls
Use a boolean to enable or disable all package routes:
```ts
auth: {
engine: auth,
routes: true,
}
```
Or control feature groups:
```ts
routes: {
enabled: true,
registration: true,
login: true,
verification: true,
password: true,
invitations: true,
magicLink: true,
otp: true,
mfa: true,
sessions: true,
impersonation: false,
passkeys: true,
}
```
Application routes have normal framework precedence. Disable a package group only when the application intentionally owns every endpoint in that group; no `excludeRoutes` list is required.
## Package endpoints
```text
POST /api/auth/register
POST /api/auth/login
POST /api/auth/logout
POST /api/auth/verification/request
GET|POST /api/auth/verify/email
POST /api/auth/verify/phone
POST /api/auth/password/request
POST /api/auth/password/reset
POST /api/auth/password/change
POST /api/auth/invitations/accept
POST /api/auth/magic-link/request
GET|POST /api/auth/magic-link
POST /api/auth/otp/login/request
POST /api/auth/otp/login/complete
POST /api/auth/otp
POST /api/auth/otp/verify
POST /api/auth/totp/setup
POST /api/auth/totp/confirm
POST /api/auth/totp/disable
POST /api/auth/recovery-codes
POST /api/auth/mfa/otp
POST /api/auth/mfa/complete
GET /api/auth/sessions
POST /api/auth/sessions/revoke
POST /api/auth/impersonation/start
POST /api/auth/impersonation/stop
POST /api/auth/passkeys/register/options
POST /api/auth/passkeys/register/verify
POST /api/auth/passkeys/login/options
POST /api/auth/passkeys/login/verify
```
Each URL uses a route-specific module, so rewritten framework request URLs cannot make the handler fall through to a shared-dispatcher `404`.
Unsafe package routes validate the framework CSRF token by default. Set `auth.csrf: false` only when an external API gateway provides an equivalent protection model.
## Components
```wrn
<SignIn />
<SignUp />
<ForgotPassword />
<ResetPassword token='{token}' />
<OtpSignIn method="email-otp" />
<MagicLinkSignIn />
<PasskeyButton mode="authenticate" />
<TwoFactorChallenge mfaToken='{mfaToken}' challengeId='{challengeId}' />
<AuthenticatorSetup credentialId='{credentialId}' secret='{secret}' uri='{uri}' />
<RecoveryCodes codes='{codes}' />
<DeviceSessions sessions='{sessions}' currentSessionId='{currentSessionId}' />
<VerifyEmail token='{token}' identifier='{identifier}' />
<VerifyPhone token='{token}' identifier='{identifier}' />
<InvitationAccept token='{token}' />
<ImpersonationBanner targetName='{targetName}' />
<AccountStatus status='{account.status}' />
```
`identifier` is optional on verification components. Supply it when an unauthenticated verification page should support resending a token. The response remains generic whether the account exists or not.
## CAPTCHA and risk
The HTTP handlers never trust a browser `captchaVerified` field. CAPTCHA completion is accepted only from server-populated `ctx.locals.captcha.success` or `ctx.locals.captchaVerified === true`.
Rate limiting remains an application or gateway responsibility. Apply it to registration, login, reset, magic-link, OTP, verification, passkey, invitation, and impersonation endpoints.
## MFA
1. Password, OAuth, magic-link, or OTP login may return `code: "mfa-required"` with a short-lived `mfaToken`.
2. The response lists only methods actually available to that user.
3. Email/SMS MFA is offered only for verified linked identities.
4. `beginMfaOtp()` issues an MFA-bound OTP when needed.
5. `completeMfa()` consumes the one-time transaction and creates the session.
## Passkeys
The browser runtime coordinates `navigator.credentials.create()` and `navigator.credentials.get()`. A configured server-side `PasskeyProvider` must verify the challenge, RP ID, origin, signature, user presence or verification, counter, and credential ownership.
Multi-process deployments must provide a shared `PasskeyChallengeStore`; the default memory implementation is process-local. Missing passkey providers return a controlled `503` response rather than crashing the route.
## Protect long-lived secrets
```ts
import { createAuthSecretProtector } from "@wrnexus/auth";
import { createKeyring } from "@wrnexus/encryption";
const keyring = createKeyring([
{
id: "auth-2026-01",
secret: process.env.AUTH_ENCRYPTION_KEY!,
active: true,
},
]);
const auth = createAuthEngine({
store: new SqlAuthStore(getDb()),
secret: process.env.AUTH_SECRET!,
secretProtector: createAuthSecretProtector(keyring),
});
```
TOTP seeds and OAuth access/refresh tokens are protected before persistence. Keep old keys available during rotation.
## Custom HTTP integration
`createAuthHttpHandlers()` remains available for custom route paths or response behavior. Prefer package routes for standard flows; copied application API files are unnecessary.
## Development
```bash
bun run auth:dev
bun run validate:auth
```
Read [SECURITY.md](./SECURITY.md) before production deployment.
## Package-owned UI blocks and route helpers
Authentication forms continue to compose `@wrnexus/ui` inputs, buttons, cards, alerts, badges, avatars, and PIN controls. The package also provides:
- `<AuthShell />`
- `<AuthProviderButtons />`
- `<AuthSecurityNotice />`
- complete sign-in, sign-up, MFA, passkey, recovery, account-status, session, and impersonation blocks
Server helpers include `authRoute`, `authSuccess`, `authFailure`, `requireAuthUser`, `optionalAuthUser`, `currentAuthSession`, and `authComponentProps`.