Skip to content

Authentication

EmDash uses passkey authentication as its primary login method. Passkeys are phishing-resistant, don’t require passwords, and work across devices through your browser or password manager.

For Cloudflare deployments, you can optionally use Cloudflare Access as an alternative authentication provider.

Passkeys use WebAuthn, a web standard that creates public-key credentials stored on your device or synced through your password manager. When you log in, your device proves possession of the credential without ever sending a password over the network.

Benefits of passkey authentication:

  • No passwords to remember or leak
  • Phishing-resistant — credentials are bound to your site’s domain
  • Cross-device sync — works with iCloud Keychain, Google Password Manager, 1Password, etc.
  • Fast login — one tap with biometrics or PIN

The first time you access the admin panel, the Setup Wizard guides you through creating your admin account.

  1. Navigate to http://localhost:4321/_emdash/admin

  2. You’ll be redirected to the Setup Wizard. Enter:

    • Site Title — Your site’s name
    • Tagline — A short description
    • Admin Email — Your email address
  3. Click Create Site to register your passkey

  4. Your browser will prompt you to create a passkey:

    • On macOS: Touch ID, device password, or security key
    • On Windows: Windows Hello or security key
    • On mobile: Face ID, fingerprint, or PIN
  5. Once your passkey is registered, you’re logged in and redirected to the admin dashboard.

After setup, returning to the admin panel triggers passkey authentication:

  1. Visit /_emdash/admin

  2. If not logged in, you’ll see the login page

  3. Click Sign in to authenticate

  4. Your browser prompts for your passkey (biometrics, PIN, or security key)

  5. After verification, you’re redirected to the admin dashboard

If you can’t use your passkey (e.g., lost device), magic links provide an alternative. This requires email to be configured.

  1. On the login page, click Sign in with email

  2. Enter your email address

  3. Check your inbox for a login link

  4. Click the link to authenticate (valid for 15 minutes)

EmDash supports OAuth login with GitHub and Google when configured. Users can link their accounts after initial passkey setup.

See the Configuration guide for setup instructions.

EmDash uses role-based access control with five levels:

RoleLevelDescription
Subscriber10View-only access
Contributor20Create content (needs approval)
Author30Create/edit/publish own content
Editor40Manage all content
Admin50Full access including settings

Each role inherits permissions from all lower levels. The first user is always created as Admin.

Admins can invite new users via the admin panel:

  1. Go to Settings > Users

  2. Click Invite User

  3. Enter the user’s email and select a role

  4. Click Send Invite

  5. The user receives an email with an invite link

  6. They click the link and register their passkey

Invites are valid for 7 days. Admins can resend or revoke invites from the Users page.

Users can manage their passkeys from the account settings:

  • Add passkey — Register additional passkeys for backup or other devices
  • Remove passkey — Delete passkeys you no longer use
  • Rename passkey — Give passkeys descriptive names

Each user can have up to 10 passkeys registered.

For team sites, you can enable self-signup for specific email domains:

astro.config.mjs
import { defineConfig } from "astro/config";
import emdash from "emdash/astro";
export default defineConfig({
integrations: [
emdash({
auth: {
selfSignup: {
domains: ["example.com"],
defaultRole: "contributor",
},
},
}),
],
});

Users with matching email domains can sign up without an invite. They’ll receive a verification email and register a passkey to complete signup.

Sessions use secure HttpOnly cookies with sensible defaults:

astro.config.mjs
emdash({
auth: {
session: {
maxAge: 30 * 24 * 60 * 60, // 30 days (default)
sliding: true, // Reset expiry on activity
},
},
});
  • Passkeys are stored as public keys — the private key never leaves your device
  • Challenge verification prevents replay attacks
  • Rate limiting protects against brute force (5 attempts/minute/IP)
  • Sessions are HttpOnly, Secure, SameSite=Lax for cookie security
  • Magic link tokens are SHA-256 hashed — raw tokens are never stored

If you see this error on login, your passkey may have been deleted from your password manager. Ask an admin to send you a magic link or new invite.

This usually means the passkey was created for a different domain. Passkeys are domain-bound — a passkey for localhost:4321 won’t work on example.com. Register a new passkey for each domain.

Sessions last 30 days by default with sliding expiration. If you’re logged out unexpectedly, clear your cookies and log in again.

If you’ve lost access to all your registered passkeys:

  1. Ask another admin to send you a magic link (requires email configuration)
  2. Use the magic link to log in
  3. Register a new passkey in account settings

If you’re the only admin and email isn’t configured, you’ll need to reset your site’s authentication through the database.

When deploying to Cloudflare, you can use Cloudflare Access as your authentication provider instead of passkeys. Access handles authentication at the edge using your existing identity provider.

  • Single Sign-On — Users authenticate with your company’s IdP
  • Centralized access control — Manage who can access the admin in the Cloudflare dashboard
  • No passkey management — No need to register or manage passkeys
  • Group-based roles — Map IdP groups to EmDash roles automatically
  1. Create a Cloudflare Access application for your EmDash site
  2. Note the Application Audience (AUD) Tag from the application settings
  3. Configure EmDash to use Access:
astro.config.mjs
import { defineConfig } from "astro/config";
import cloudflare from "@astrojs/cloudflare";
import emdash from "emdash/astro";
import { d1 } from "emdash/db";
export default defineConfig({
output: "server",
adapter: cloudflare(),
integrations: [
emdash({
database: d1({ binding: "DB" }),
auth: {
cloudflareAccess: {
teamDomain: "myteam.cloudflareaccess.com",
audience: "abc123def456...", // From Access app settings
},
},
}),
],
});
OptionTypeDefaultDescription
teamDomainstringrequiredYour Access team domain (e.g., myteam.cloudflareaccess.com)
audiencestringrequiredApplication Audience (AUD) tag from Access settings
autoProvisionbooleantrueCreate EmDash users on first Access login
defaultRolenumber30Role for users not matching any group (30 = Author)
syncRolesbooleanfalseUpdate role on each login based on IdP groups
roleMappingobjectMap IdP group names to role levels

Map your IdP groups to EmDash roles:

astro.config.mjs
emdash({
auth: {
cloudflareAccess: {
teamDomain: "myteam.cloudflareaccess.com",
audience: "abc123...",
roleMapping: {
Admins: 50, // Admin
"Content Editors": 40, // Editor
Writers: 30, // Author
},
defaultRole: 20, // Contributor for users not in any group
},
},
});

The first matching group wins if a user belongs to multiple groups. The first user to access the site always becomes Admin, regardless of groups.

By default (syncRoles: false), a user’s role is set when they first log in and doesn’t change afterward. This allows admins to manually adjust roles in EmDash.

Set syncRoles: true if you want IdP groups to be authoritative — the user’s role will update on every login based on their current groups.

  1. User visits /_emdash/admin
  2. Cloudflare Access intercepts and redirects to your IdP
  3. User authenticates (SSO, MFA, etc.)
  4. Access sets a signed JWT in the request
  5. EmDash validates the JWT and creates/authenticates the user

When Access is enabled, these features are unavailable:

  • Login page (/_emdash/admin/login)
  • Passkey registration and management
  • OAuth login
  • Magic link login
  • Self-signup
  • User invites

User management is done entirely through your Cloudflare Access policies.

The request reached EmDash without an Access JWT. This means:

  • Access isn’t configured to protect your application
  • The Access policy isn’t matching the admin routes

Verify your Access application covers /_emdash/admin/*.

The audience in your config doesn’t match the JWT. Double-check the Application Audience Tag in your Access application settings.

The user authenticated via Access but autoProvision is false and they don’t exist in EmDash. Either:

  • Set autoProvision: true, or
  • Create the user manually before they log in