Skip to content

Authentication

UseBetter Console supports three authentication methods. You configure them in the sessions option of betterConsole(). At least one method must be enabled.

Auto-approve issues a stateless JWT immediately when a session is initiated — no database, no email flow. It is intended for local development only.

import { betterConsole } from "@usebetterdev/console";
const consoleInstance = betterConsole({
connectionTokenHash: process.env.BETTER_CONSOLE_TOKEN_HASH!,
sessions: { autoApprove: process.env.NODE_ENV === "development" },
});

Magic link uses a 3-step handshake with email verification. It requires a database adapter (Drizzle) for storing session and magic link records.

import { betterConsole } from "@usebetterdev/console";
import { drizzleConsoleAdapter } from "@usebetterdev/console/drizzle";
const consoleInstance = betterConsole({
adapter: drizzleConsoleAdapter(db),
connectionTokenHash: process.env.BETTER_CONSOLE_TOKEN_HASH!,
sessions: {
magicLink: {
allowedEmails: process.env.BETTER_CONSOLE_ALLOWED_EMAILS!,
},
},
});

Step through each authentication flow to see the HTTP requests and responses at each stage.

Flow:Scenario:
3-step handshake: init session, verify code from email, claim JWT.
Console UI
Server
Email
POST init
send code
sessionId
POST verify
verified
POST claim
JWT
Click "Next step" to begin stepping through the magic link flow.

Only emails matching the allowedEmails list can initiate a session. Emails that don’t match receive a ConsoleEmailNotAllowedError. You can use exact addresses or glob patterns with * wildcards.

sessions: {
magicLink: {
// Single email
allowedEmails: "[email protected]",
// Multiple emails
allowedEmails: ["[email protected]", "[email protected]"],
// Glob pattern — allow any email from a domain
allowedEmails: "*@myapp.com",
// Mix exact emails and patterns
allowedEmails: ["[email protected]", "*@eng.myapp.com"],
// From environment variable (comma-separated)
allowedEmails: process.env.BETTER_CONSOLE_ALLOWED_EMAILS!,
},
},

The universal wildcard * allows any email to initiate a session. In production, this requires explicit opt-in:

sessions: {
magicLink: {
allowedEmails: "*",
allowUnauthenticatedEmails: true,
},
},

When sendMagicLinkEmail is not provided, the verification code is delivered automatically via the Console email relay (console.usebetter.dev). The relay only sends to emails that have a registered Console account — no additional configuration is needed.

The Console UI sends appName and baseUrl in the session init request. These are used to construct the verification link in the email.

To use your own email provider instead of the Console email relay, provide the sendMagicLinkEmail callback:

sessions: {
magicLink: {
allowedEmails: process.env.BETTER_CONSOLE_ALLOWED_EMAILS!,
sendMagicLinkEmail: async ({ email, sessionId, code }) => {
await sendEmail({
to: email,
subject: "Your Console access code",
body: `Your verification code is: ${code}`,
});
},
},
},

Magic link verification has a configurable attempt limit. After maxAttempts failed code entries, the magic link is locked out and a new session must be initiated.

sessions: {
magicLink: {
allowedEmails: "[email protected]",
maxAttempts: 3, // default: 5
},
},

The magic link code expires after 10 minutes regardless of attempts.

Session tokens expire after 24 hours by default. Configure with tokenLifetime:

sessions: {
magicLink: { allowedEmails: "[email protected]" },
tokenLifetime: "8h", // valid range: 1h to 7d
},

To rotate the connection token (e.g., if compromised):

Terminal window
npx @usebetterdev/console-cli token rotate

This generates a new token pair. Update BETTER_CONSOLE_TOKEN_HASH in your environment and restart your server. All existing sessions are invalidated because session JWTs are signed with the connection token secret.

  • Configuration — full config reference, CORS, environment variables
  • CLI — all CLI commands including token management
  • Troubleshooting — common auth issues and fixes