Skip to main content

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

PagePathDescription
Login{apiPrefix}/ui/loginEmail/password + OAuth + magic link + SMS
Register{apiPrefix}/ui/registerNew account form (only when onRegister is set)
Forgot Password{apiPrefix}/ui/forgot-passwordPassword reset request form
Reset Password{apiPrefix}/ui/reset-passwordNew password form (via email token)
Magic Link{apiPrefix}/ui/magic-linkPasswordless email login
Verify Email{apiPrefix}/ui/verify-emailEmail verification landing page
2FA Challenge{apiPrefix}/ui/2faTOTP / SMS one-time password entry
Link Verify{apiPrefix}/ui/link-verifyOAuth account-linking confirmation
Account Conflict{apiPrefix}/ui/account-conflictOAuth 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/login
  • http://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

OptionTypeDefaultDescription
loginUrlstring{apiPrefix}/ui/loginLogin page URL (used for redirects)
registerUrlstring{apiPrefix}/ui/registerRegister page URL
siteNamestring'Awesome Node Auth'Page title and <h1> heading
customLogostringURL of a logo image (displayed above the form)
primaryColorstring#4a90d9Main brand colour (buttons, headings, focus rings)
secondaryColorstring#6c757dSecondary colour (OAuth buttons, dividers)
bgColorstring#f8fafcPage background colour
bgImagestringPage background image URL (full-cover)
cardBgstring#ffffffForm/card background colour
customCssstringRaw 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

SelectorElementNotes
.auth-containerForm cardControls max-width (400px default), padding, border-radius
.logoLogo <img>Hidden if no logoUrl is set
.site-name<h1>Site name heading (set via siteName)
h2Sub-heading"Login", "Register", etc.
.alertAlert boxBase styles
.alert-errorError alertRed background
.alert-successSuccess alertGreen background
.form-groupField container<label> + <input> wrapper
inputAll form inputsIncludes email, password, OTP fields
button[type="submit"]Primary action buttonColoured by --primary-color
.btn-socialOAuth buttonsGoogle, GitHub
.divider"or" separatorBetween form and OAuth buttons
.footer-linksBottom 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:

  1. Reads window.__AUTH_CONFIG__ (injected by the server at render time — zero extra fetch)
  2. Intercepts all fetch() calls to add credentials: 'include' and X-CSRF-Token
  3. Handles 401/403 responses with transparent token refresh
  4. Exposes window.AwesomeNodeAuth for optional custom scripting

See Browser Client (auth.js) for the full window.AwesomeNodeAuth API.


Enabling features dynamically

The UI hides/shows features based on which strategies and handlers are configured:

FeatureShown when
Register linkrouterOptions.onRegister is set
Forgot passwordauthConfig.email.sendPasswordReset or .mailer is set
Magic linkauthConfig.email.sendMagicLink or .mailer is set
Google OAuthauthConfig.oauth.google is set
GitHub OAuthauthConfig.oauth.github is set
2FA pageauthConfig.twoFactor is set
Verify emailauthConfig.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?

ClientRecommendation
Vanilla JS / jQuery / plain HTMLUse the built-in UI — zero setup
Next.js / Nuxt / SvelteKitBuilt-in UI works; or build your own pages and call the REST API
AngularUse the dedicated ng-awesome-node-auth library — it provides Guards, Interceptors, and a typed AuthService; auth.js is not needed in Angular projects

Production checklist
  • Set cookieOptions.secure: true so __Host- / __Secure- cookie prefixes are applied (cookie-tossing protection)
  • Set csrf: { enabled: true } when your frontend makes state-changing requests
  • Use ISettingsStore if you want runtime theme changes without server restarts
  • Pass uploadDir to accept logo uploads from the admin panel