User Auth & Gating
Add user authentication to your site so visitors can log in, sign up, and access gated content. Gate entire pages or specific sections by user type. Works with your own API or a hosted provider — Supabase, Firebase, Clerk, or Auth0.
How do I enable auth?
Auth is off by default — new projects ship without login pages or auth scripts. Enable it with the MCP tool:
sitemd_auth_setup provider: supabase
Or with the CLI:
sitemd auth setup supabase
This does three things:
- Creates
settings/auth.mdwithenabled: trueand your chosen provider - Copies login, sign-up, forgot-password, and account pages into your project
- Creates the
gated-pages/directory for user-type-gated content
To enable auth manually, create settings/auth.md:
---
# Enable user authentication
enabled: true
# Auth provider: custom, supabase, firebase, clerk, auth0
provider: supabase
# Login mode: password, magic-link
loginMode: password
# Redirect unauthenticated users here
loginPage: /login
# Redirect after successful login
afterLogin: /account
# Redirect after logout
afterLogout: /
# Webhook URL returning extended user data (called with Bearer token)
# Response fields become available as {{currentUser.fieldName}}
userDataUrl:
# Field in user data that contains the user's type/role
# e.g., if your webhook returns { role: "teacher" }, set this to: role
userTypeField:
# Redirect users who are logged in but lack the required type
accessDeniedPage: /access-denied
---
Then copy the auth page templates into your project manually — they live in engine/auth/pages/ and engine/auth/account-pages/ inside the sitemd package.
When enabled is false or missing, the build skips auth-pages/, account-pages/, and gated-pages/ entirely — no auth JavaScript is added to your site.
Set provider credentials
After enabling auth, set your provider credentials via the CLI:
sitemd config setup auth
Or set them individually:
sitemd config set auth.supabaseUrl https://xyz.supabase.co
sitemd config set auth.supabaseAnonKey eyJ...
Provider credentials are stored in .sitemd/config.json (gitignored) — never committed to source control. See CLI Config for details.
Providers
Each provider needs different credentials, configured via the CLI or MCP server:
| Provider | Config keys | Notes |
|---|---|---|
custom |
auth.apiUrl |
Your own auth API. Expects /auth/login, /auth/signup, etc. |
supabase |
auth.supabaseUrl, auth.supabaseAnonKey |
SDK loaded from CDN. Free tier: 50k MAU. |
firebase |
auth.firebaseApiKey, auth.firebaseAuthDomain, auth.firebaseProjectId |
SDK loaded from CDN. Free unlimited auth. |
clerk |
auth.clerkPublishableKey |
SDK loaded from CDN. Free tier: 10k MAU. |
auth0 |
auth.auth0Domain, auth.auth0ClientId |
SDK loaded from CDN. Uses redirect-based login. |
Set credentials with sitemd config set <key> <value> or use sitemd config setup auth for an interactive walkthrough. Provider SDKs are loaded dynamically at runtime — only the chosen provider's SDK is fetched.
If you see a red "Auth API URL not configured" banner on localhost, your provider credentials aren't set. Run sitemd config set with the keys listed above for your provider, then restart the dev server.
To use passwordless login instead of email+password, set loginMode: magic-link. See Magic Link Auth for the full setup.
Gating pages
Add auth: required to any page's frontmatter to require login:
---
title: Dashboard
slug: /dashboard
auth: required
---
When an unauthenticated visitor hits a gated page, they're redirected to your loginPage. After login, they're sent back to where they were going.
A small inline script in <head> prevents any flash of protected content before the redirect fires.
User types
Gate pages to specific user types by adding gated: to frontmatter with a comma-separated list of allowed types:
---
title: Teacher Dashboard
slug: /teacher-dashboard
gated: teacher
---
Allow multiple types on one page:
---
title: Course Materials
gated: teacher, student
---
Use anyLoggedIn to require login without restricting by type — equivalent to auth: required:
---
gated: anyLoggedIn
---
User types are read from your extended user data (the userDataUrl webhook response). Set userTypeField in settings/auth.md to the field name that holds the type. For example, if your webhook returns { "role": "teacher" }, set userTypeField: role.
If a logged-in user visits a page they don't have the right type for, they're redirected to accessDeniedPage (defaults to /access-denied).
Gated pages directory
Pages inside gated-pages/ subdirectories are automatically gated to the directory name as the user type:
gated-pages/
teacher/
grades.md → gated to "teacher"
lesson-plans.md → gated to "teacher"
student/
my-courses.md → gated to "student"
No gated: frontmatter needed — the directory name does it. Frontmatter gated: on a file inside a subdirectory overrides the directory-derived type.
Gated sections
Gate specific sections within a page so different user types see different content. Wrap content between gated: types and /gated:
## Welcome
This paragraph is visible to everyone who can access the page.
gated: teacher
## Grading Tools
This section only appears for teachers.
/gated
gated: student
## My Assignments
This section only appears for students.
/gated
Full markdown is supported inside gated fences — headings, code blocks, images, lists, everything works normally. No indentation required.
Multiple types work too:
gated: teacher, admin
Content visible to both teachers and admins.
/gated
Use anyLoggedIn to show a section to any logged-in user:
gated: anyLoggedIn
Welcome back! Here's your personalized content.
/gated
Gated sections are hidden by default (display: none) and revealed client-side after the user's type is verified. Content never flashes before the check completes.
Header account button
The account button appears in the header (between search and theme toggle) when auth is enabled. Configure it in settings/header.md:
---
# Show or hide the account button in the header: show or hide
# Requires auth provider to be configured in settings/auth.md
headerAuth: show
---
When a user is logged in, the button links to your afterLogin path and shows an accent highlight. When logged out, it links to your loginPage.
currentUser variables
Display logged-in user data anywhere on your site with mustache-style tags:
Welcome back, {{currentUser.name}}!
Your email: {{currentUser.email}}
At build time, these become placeholder <span> elements. When the page loads, auth.js fills them with the user's data. Available fields depend on your provider:
| Field | Source |
|---|---|
name |
All providers |
email |
All providers |
id |
All providers |
avatar |
All providers (if set) |
emailVerified |
All providers |
Extended user data
For custom fields beyond what your auth provider returns (subscription tier, credits, preferences), configure a webhook:
---
# Webhook URL returning extended user data (called with Bearer token)
userDataUrl: https://your-api.com/user/profile
---
The webhook is called with GET and an Authorization: Bearer {token} header. It should return a flat JSON object:
{ "plan": "pro", "credits": 42, "company": "Acme" }
These fields merge with the base user data and become available as {{currentUser.plan}}, {{currentUser.credits}}, etc. Results are cached for 5 minutes.
JavaScript API
Dashboard pages and custom scripts can use window.__sitemdAuth for auth operations:
// Auth
__sitemdAuth.login(email, password)
__sitemdAuth.signup(email, password, name)
__sitemdAuth.logout()
__sitemdAuth.isLoggedIn()
__sitemdAuth.getUser()
__sitemdAuth.getToken()
__sitemdAuth.onAuthChange(callback)
// Magic link (all providers)
__sitemdAuth.requestMagicLink(email)
// Password (custom provider)
__sitemdAuth.forgotPassword(email)
__sitemdAuth.resetPassword(token, password)
All methods return Promises. On a 401 response, the session is automatically cleared and the user is redirected to the login page.
Using auth in inline scripts
window.__sitemdAuth is set by auth.js, which loads in the <head>. The API object exists immediately, but auth initialization (session recovery, magic link callbacks) is async. Use auth.ready to wait for init to complete:
(function() {
function init() {
var auth = window.__sitemdAuth;
auth.getUser(); // safe to call here
}
if (window.__sitemdAuth) window.__sitemdAuth.ready.then(init);
else document.addEventListener('sitemd:auth-ready', init);
})();
During SPA navigation, auth.ready is already resolved and init() runs on the next microtick. The sitemd:auth-ready event path is a fallback for edge cases. See Inline HTML: Scripts for more on script timing.
Special page directories
Auth-related pages live in dedicated directories outside pages/. These directories are created when you enable auth with sitemd_auth_setup or manually:
| Directory | Purpose | URL pattern |
|---|---|---|
auth-pages/ |
Login, signup, password reset | Slug from frontmatter (e.g. /login) |
account-pages/ |
User dashboard, settings | account.md → /account, others → /account/{name} |
gated-pages/ |
User-type-gated content (subdirs = user types) | Slug from frontmatter |
These directories are only included in builds when enabled: true is set in settings/auth.md. They're watched by the dev server and exempt from filename-slug reconciliation.
SPA integration
Auth checks work with sitemd's instant navigation. When a visitor navigates to a gated page via a link (no full page reload), the auth check runs after the content swap. If the user isn't logged in, they're redirected to the login page.
How it works
settings/auth.mdconfigures the provider, redirects, and user type field —enabled: trueactivates the system- Auth page directories (
auth-pages/,account-pages/,gated-pages/) are only discovered and built when auth is enabled - At build time, the engine injects a flash-prevention script in
<head>, stampsdata-auth="required"anddata-gated-typeson gated pages, wraps gated sections in hidden<div>elements, converts{{currentUser.*}}to hydration spans, and addstheme/auth.jsbefore</body> auth.jsloads the chosen provider's SDK, manages sessions in localStorage, gates pages by auth and user type, reveals matching gated sections, hydrates user variables, and exposes the__sitemdAuthAPI- The header account button and SPA navigation hook are updated on every page transition
When auth is disabled (enabled: false or missing), none of this runs — no auth pages, no scripts, no header button, no data attributes.
Related
- Magic Link Auth — passwordless login configuration
- Dev Server — Local Auth Testing — test login, gating, and account pages without a backend
- CLI Config — manage auth provider credentials and other service config
- Authentication — CLI authentication for deploying sites (separate from user auth)
- Email Templates — verification and password reset emails for auth flows
- Deploy — deployment requires CLI auth, not user auth
- Project Structure — where auth directories live in your project