# 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:

```text
sitemd_auth_setup provider: supabase
```

Or with the CLI:

```bash
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`:

```yaml
---
# 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:

```bash
sitemd config setup auth
```

Or set them individually:

```bash
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](/docs/cli-config) for details.

## Providers

Each provider needs different credentials, configured via the CLI or [MCP server](/docs/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](/docs/magic-link-auth) for the full setup.

## Gating pages

Add `auth: required` to any page's frontmatter to require login:

```yaml
---
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:

```yaml
---
title: Teacher Dashboard
slug: /teacher-dashboard
gated: teacher
---
```

Allow multiple types on one page:

```yaml
---
title: Course Materials
gated: teacher, student
---
```

Use `anyLoggedIn` to require login without restricting by type — equivalent to `auth: required`:

```yaml
---
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:

```text
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`:

```markdown
## 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:

```markdown
gated: teacher, admin

Content visible to both teachers and admins.

/gated
```

Use `anyLoggedIn` to show a section to any logged-in user:

```markdown
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`:

```yaml
---
# 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:

```markdown
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:

```yaml
---
# 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:

```json
{ "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:

```js
// 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:

```js
(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](/docs/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.

## Related

- [Magic Link Auth](/docs/magic-link-auth) — passwordless login configuration
- [Dev Server — Local Auth Testing](/docs/dev-server#local-auth-testing) — test login, gating, and account pages without a backend
- [CLI Config](/docs/cli-config) — manage auth provider credentials and other service config
- [Authentication](/docs/authentication) — CLI authentication for deploying sites (separate from user auth)
- [Email Templates](/docs/email) — verification and password reset emails for auth flows
- [Deploy](/docs/deploy) — deployment requires CLI auth, not user auth
- [Project Structure](/docs/project-structure) — where auth directories live in your project