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:

  1. Creates settings/auth.md with enabled: true and your chosen provider
  2. Copies login, sign-up, forgot-password, and account pages into your project
  3. 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

  1. settings/auth.md configures the provider, redirects, and user type field — enabled: true activates the system
  2. Auth page directories (auth-pages/, account-pages/, gated-pages/) are only discovered and built when auth is enabled
  3. At build time, the engine injects a flash-prevention script in <head>, stamps data-auth="required" and data-gated-types on gated pages, wraps gated sections in hidden <div> elements, converts {{currentUser.*}} to hydration spans, and adds theme/auth.js before </body>
  4. auth.js loads 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 __sitemdAuth API
  5. 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.