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.
How It Works
Section titled “How It Works”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
First User Setup
Section titled “First User Setup”The first time you access the admin panel, the Setup Wizard guides you through creating your admin account.
-
Navigate to
http://localhost:4321/_emdash/admin -
You’ll be redirected to the Setup Wizard. Enter:
- Site Title — Your site’s name
- Tagline — A short description
- Admin Email — Your email address
-
Click Create Site to register your passkey
-
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
-
Once your passkey is registered, you’re logged in and redirected to the admin dashboard.
Logging In
Section titled “Logging In”After setup, returning to the admin panel triggers passkey authentication:
-
Visit
/_emdash/admin -
If not logged in, you’ll see the login page
-
Click Sign in to authenticate
-
Your browser prompts for your passkey (biometrics, PIN, or security key)
-
After verification, you’re redirected to the admin dashboard
Magic Link Fallback
Section titled “Magic Link Fallback”If you can’t use your passkey (e.g., lost device), magic links provide an alternative. This requires email to be configured.
-
On the login page, click Sign in with email
-
Enter your email address
-
Check your inbox for a login link
-
Click the link to authenticate (valid for 15 minutes)
OAuth Login
Section titled “OAuth Login”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.
User Roles
Section titled “User Roles”EmDash uses role-based access control with five levels:
| Role | Level | Description |
|---|---|---|
| Subscriber | 10 | View-only access |
| Contributor | 20 | Create content (needs approval) |
| Author | 30 | Create/edit/publish own content |
| Editor | 40 | Manage all content |
| Admin | 50 | Full access including settings |
Each role inherits permissions from all lower levels. The first user is always created as Admin.
Inviting Users
Section titled “Inviting Users”Admins can invite new users via the admin panel:
-
Go to Settings > Users
-
Click Invite User
-
Enter the user’s email and select a role
-
Click Send Invite
-
The user receives an email with an invite link
-
They click the link and register their passkey
Invites are valid for 7 days. Admins can resend or revoke invites from the Users page.
Managing Passkeys
Section titled “Managing Passkeys”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.
Self-Signup
Section titled “Self-Signup”For team sites, you can enable self-signup for specific email domains:
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.
Session Configuration
Section titled “Session Configuration”Sessions use secure HttpOnly cookies with sensible defaults:
emdash({ auth: { session: { maxAge: 30 * 24 * 60 * 60, // 30 days (default) sliding: true, // Reset expiry on activity }, },});Security Notes
Section titled “Security Notes”- 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
Troubleshooting
Section titled “Troubleshooting””No passkeys registered”
Section titled “”No passkeys registered””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.
”Passkey authentication failed”
Section titled “”Passkey authentication failed””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.
”Session expired”
Section titled “”Session expired””Sessions last 30 days by default with sliding expiration. If you’re logged out unexpectedly, clear your cookies and log in again.
Lost all passkeys
Section titled “Lost all passkeys”If you’ve lost access to all your registered passkeys:
- Ask another admin to send you a magic link (requires email configuration)
- Use the magic link to log in
- 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.
Cloudflare Access
Section titled “Cloudflare Access”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.
Why Use Cloudflare Access?
Section titled “Why Use Cloudflare Access?”- 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
- Create a Cloudflare Access application for your EmDash site
- Note the Application Audience (AUD) Tag from the application settings
- Configure EmDash to use Access:
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 }, }, }), ],});Configuration Options
Section titled “Configuration Options”| Option | Type | Default | Description |
|---|---|---|---|
teamDomain | string | required | Your Access team domain (e.g., myteam.cloudflareaccess.com) |
audience | string | required | Application Audience (AUD) tag from Access settings |
autoProvision | boolean | true | Create EmDash users on first Access login |
defaultRole | number | 30 | Role for users not matching any group (30 = Author) |
syncRoles | boolean | false | Update role on each login based on IdP groups |
roleMapping | object | — | Map IdP group names to role levels |
Role Mapping
Section titled “Role Mapping”Map your IdP groups to EmDash roles:
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.
Role Sync Behavior
Section titled “Role Sync Behavior”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.
How It Works
Section titled “How It Works”- User visits
/_emdash/admin - Cloudflare Access intercepts and redirects to your IdP
- User authenticates (SSO, MFA, etc.)
- Access sets a signed JWT in the request
- EmDash validates the JWT and creates/authenticates the user
Disabled Features
Section titled “Disabled Features”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.
Troubleshooting
Section titled “Troubleshooting””No Access JWT present”
Section titled “”No Access JWT present””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/*.
”JWT audience mismatch”
Section titled “”JWT audience mismatch””The audience in your config doesn’t match the JWT. Double-check the Application Audience Tag in your Access application settings.
”User not authorized”
Section titled “”User not authorized””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