Built-in UI — Activation & CSS Customization
awesome-node-auth ships a zero-dependency, server-rendered HTML UI for all authentication flows. No React, no bundler, no build step required — the server injects your theme and configuration at request time.
Pages included
| Page | Path | Description |
|---|---|---|
| Login | {apiPrefix}/ui/login | Email/password + OAuth + magic link + SMS |
| Register | {apiPrefix}/ui/register | New account form (only when onRegister is set) |
| Forgot Password | {apiPrefix}/ui/forgot-password | Password reset request form |
| Reset Password | {apiPrefix}/ui/reset-password | New password form (via email token) |
| Magic Link | {apiPrefix}/ui/magic-link | Passwordless email login |
| Verify Email | {apiPrefix}/ui/verify-email | Email verification landing page |
| 2FA Challenge | {apiPrefix}/ui/2fa | TOTP / SMS one-time password entry |
| Link Verify | {apiPrefix}/ui/link-verify | OAuth account-linking confirmation |
| Account Conflict | {apiPrefix}/ui/account-conflict | OAuth provider/email conflict resolution |
Step 1 — Install the UI router
buildUiRouter is separate from the auth API router. Mount both under the same prefix:
import express from 'express';
import { AuthConfigurator, buildUiRouter } from 'awesome-node-auth';
import { MyUserStore } from './my-user-store';
const app = express();
app.use(express.json());
const config: AuthConfig = {
// ... JWT secrets & expiry
ui: { loginUrl: '/auth/ui/login' }
};
const userStore = new MyUserStore();
const auth = new AuthConfigurator(config, userStore);
const routerOptions = {
// onRegister, oauthStrategies, etc.
};
// 1. Mount the REST API router
app.use('/auth', auth.router(routerOptions));
// 2. Mount the UI router at the same prefix + /ui
app.use(
'/auth/ui',
buildUiRouter({
authConfig: config,
routerOptions: routerOptions, // enables register/OAuth flags
apiPrefix: '/auth',
})
);
app.listen(3000, () => console.log('Running on http://localhost:3000'));
The built-in UI is now available at:
http://localhost:3000/auth/ui/loginhttp://localhost:3000/auth/ui/register- etc.
Step 2 — Configure AuthConfig.ui
All UI settings live under authConfig.ui. Every option is optional.
const auth = new AuthConfigurator(
{
// ... JWT secrets & expiry ...
ui: {
loginUrl: '/auth/ui/login',
registerUrl: '/auth/ui/register', // default
siteName: 'My App',
customLogo: 'https://cdn.example.com/logo.png',
primaryColor: '#6366f1', // indigo
secondaryColor: '#64748b',
bgColor: '#f0f4ff',
cardBg: '#ffffff',
bgImage: 'https://cdn.example.com/bg.jpg',
// Raw CSS injected into every page — override any rule or variable
customCss: `
:root { --primary-color: #6366f1; }
.auth-container { border-radius: 20px; }
`,
},
},
userStore
);
AuthConfig.ui options
| Option | Type | Default | Description |
|---|---|---|---|
loginUrl | string | {apiPrefix}/ui/login | Login page URL (used for redirects) |
registerUrl | string | {apiPrefix}/ui/register | Register page URL |
siteName | string | 'Awesome Node Auth' | Page title and <h1> heading |
customLogo | string | — | URL of a logo image (displayed above the form) |
primaryColor | string | #4a90d9 | Main brand colour (buttons, headings, focus rings) |
secondaryColor | string | #6c757d | Secondary colour (OAuth buttons, dividers) |
bgColor | string | #f8fafc | Page background colour |
bgImage | string | — | Page background image URL (full-cover) |
cardBg | string | #ffffff | Form/card background colour |
customCss | string | — | Raw CSS injected into every page <style> tag |
Step 3 — Dynamic settings via ISettingsStore
For real-time theme changes without restarting the server (e.g. from an admin panel), implement ISettingsStore and pass it to buildUiRouter:
import { ISettingsStore } from 'awesome-node-auth';
class MongoSettingsStore implements ISettingsStore {
async getSettings() {
const doc = await db.collection('settings').findOne({ key: 'ui' });
return doc ?? {};
}
async updateSettings(settings: Partial<AuthSettings>) {
await db.collection('settings').updateOne(
{ key: 'ui' },
{ $set: { ...settings, key: 'ui' } },
{ upsert: true }
);
}
}
app.use('/auth/ui', buildUiRouter({
authConfig: config,
apiPrefix: '/auth',
settingsStore: new MongoSettingsStore(),
}));
Settings in the store override authConfig.ui. The built-in admin router's UI settings tab writes to the same ISettingsStore, so changes made in the admin panel appear instantly.
Step 4 — Custom logo uploads
To let admins upload a custom logo, pass an uploadDir to buildUiRouter. Uploaded files are served at {apiPrefix}/ui/assets/uploads/:
import path from 'path';
app.use('/auth/ui', buildUiRouter({
authConfig: config,
apiPrefix: '/auth',
uploadDir: path.resolve(__dirname, 'uploads/logo'),
}));
// Then set logoUrl to the uploaded path:
// logoUrl: '/auth/ui/assets/uploads/logo.png'
CSS customization reference
How theming works
The UI is styled with CSS custom properties (variables) defined in :root. When you set primaryColor, bgColor, etc. in AuthConfig.ui or ISettingsStore, the server renders an inline <style> block that overrides these variables before the stylesheet loads — eliminating any flash of unstyled content.
The customCss string is appended after the variable block, letting you override individual rules.
Full CSS variable reference
:root {
/* ── Colours ─────────────────────────────────────────── */
--primary-color: #4a90d9; /* buttons, headings, focus rings */
--primary-color-hover: #357abd; /* button hover state */
--secondary-color: #6c757d; /* OAuth buttons, dividers */
--secondary-color-hover:#5a6268; /* OAuth button hover */
--bg-color: #f8fafc; /* page background */
--bg-image: none; /* background image (url("…")) */
--card-bg: #ffffff; /* form card background */
--text-color: #1e293b; /* primary text */
--text-muted: #64748b; /* secondary/label text */
--error-color: #ef4444; /* error alerts */
--success-color: #22c55e; /* success alerts */
--border-color: #e2e8f0; /* input borders, dividers */
--input-focus: #4a90d9; /* input focus ring (same as primary by default) */
}
Overriding variables only — the minimal approach
ui: {
primaryColor: '#6366f1', // only changes --primary-color + --input-focus
}
Full CSS override via customCss
ui: {
customCss: `
/* Override variables */
:root {
--primary-color: #8b5cf6;
--primary-color-hover: #7c3aed;
--bg-color: #0f0f13;
--card-bg: #1a1a2e;
--text-color: #e2e8f0;
--text-muted: #94a3b8;
--border-color: #334155;
}
/* Override component rules */
.auth-container {
border-radius: 20px;
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5);
}
h1 { font-size: 28px; }
h2 { font-size: 14px; letter-spacing: 0.1em; text-transform: uppercase; }
button[type="submit"] {
border-radius: 8px;
letter-spacing: 0.05em;
}
input {
background: var(--card-bg);
color: var(--text-color);
}
`,
}
Dark mode example
ui: {
bgColor: '#0f172a',
cardBg: '#1e293b',
customCss: `
:root {
--bg-color: #0f172a;
--card-bg: #1e293b;
--text-color: #f1f5f9;
--text-muted: #94a3b8;
--border-color: #334155;
}
input { background: #0f172a; color: #f1f5f9; }
label { color: #cbd5e1; }
`,
}
CSS class reference
| Selector | Element | Notes |
|---|---|---|
.auth-container | Form card | Controls max-width (400px default), padding, border-radius |
.logo | Logo <img> | Hidden if no logoUrl is set |
.site-name | <h1> | Site name heading (set via siteName) |
h2 | Sub-heading | "Login", "Register", etc. |
.alert | Alert box | Base styles |
.alert-error | Error alert | Red background |
.alert-success | Success alert | Green background |
.form-group | Field container | <label> + <input> wrapper |
input | All form inputs | Includes email, password, OTP fields |
button[type="submit"] | Primary action button | Coloured by --primary-color |
.btn-social | OAuth buttons | Google, GitHub |
.divider | "or" separator | Between form and OAuth buttons |
.footer-links | Bottom link area | "Forgot password?", "Sign up" |
Step 5 — Custom assets directory
For complete control, replace all built-in HTML pages with your own by pointing uiAssetsDir at a local directory. Your directory must contain the same file names as the built-in pages.
app.use('/auth/ui', buildUiRouter({
authConfig: config,
apiPrefix: '/auth',
uiAssetsDir: path.resolve(__dirname, 'my-custom-ui'),
}));
Your HTML files still benefit from server-side config injection (window.__AUTH_CONFIG__, CSS variables) as long as they contain a </head> tag.
How auth.js activates automatically
Each built-in HTML page includes <script src="auth.js">, which:
- Reads
window.__AUTH_CONFIG__(injected by the server at render time — zero extra fetch) - Intercepts all
fetch()calls to addcredentials: 'include'andX-CSRF-Token - Handles 401/403 responses with transparent token refresh
- Exposes
window.AwesomeNodeAuthfor optional custom scripting
See Browser Client (
auth.js) for the fullwindow.AwesomeNodeAuthAPI.
Enabling features dynamically
The UI hides/shows features based on which strategies and handlers are configured:
| Feature | Shown when |
|---|---|
| Register link | routerOptions.onRegister is set |
| Forgot password | authConfig.email.sendPasswordReset or .mailer is set |
| Magic link | authConfig.email.sendMagicLink or .mailer is set |
| Google OAuth | authConfig.oauth.google is set |
| GitHub OAuth | authConfig.oauth.github is set |
| 2FA page | authConfig.twoFactor is set |
| Verify email | authConfig.email.mailer is set and emailVerificationMode !== 'none' |
Pass routerOptions to buildUiRouter so these flags are computed correctly:
const config: AuthConfig = { /* … */ };
const auth = new AuthConfigurator(config, userStore);
const routerOpts = {
onRegister: async (data) => { /* … */ },
// …
};
app.use('/auth', auth.router(routerOpts));
app.use('/auth/ui', buildUiRouter({ authConfig: config, routerOptions: routerOpts, apiPrefix: '/auth' }));
Redirecting after login
awesome-node-auth redirects to AuthConfig.ui.loginUrl when an unauthenticated user hits a protected route. After a successful login the default redirect target is / (the home page).
Customize both with AwesomeNodeAuth.init() in your own JS:
<script src="/auth/ui/assets/auth.js"></script>
<script>
AwesomeNodeAuth.init({ homeUrl: '/dashboard' });
</script>
Or server-side, via the window.__AUTH_CONFIG__ injection — the homeUrl can be baked in by buildUiRouter at render time if you extend getUiConfig.
Angular / Next.js — do I need the built-in UI?
| Client | Recommendation |
|---|---|
| Vanilla JS / jQuery / plain HTML | Use the built-in UI — zero setup |
| Next.js / Nuxt / SvelteKit | Built-in UI works; or build your own pages and call the REST API |
| Angular | Use the dedicated ng-awesome-node-auth library — it provides Guards, Interceptors, and a typed AuthService; auth.js is not needed in Angular projects |
- Set
cookieOptions.secure: trueso__Host-/__Secure-cookie prefixes are applied (cookie-tossing protection) - Set
csrf: { enabled: true }when your frontend makes state-changing requests - Use
ISettingsStoreif you want runtime theme changes without server restarts - Pass
uploadDirto accept logo uploads from the admin panel