Guides

Custom auth page templates

Override the built-in login, register, and verify-email pages with per-application HTML templates.

Custom auth page templates

By default, Auth Service serves the built-in Vue 3 SPA for the /login, /register, and /verify-email routes. You can replace these with custom static HTML templates — globally or per-application.

Enable templates

Set TEMPLATES_DIR in the environment to a directory that Auth Service can read at runtime:

TEMPLATES_DIR=/etc/auth-service/templates

When TEMPLATES_DIR is not set, all three auth page routes fall through to the Vue SPA (or return 404 if the SPA is not built).

Directory structure

$TEMPLATES_DIR/
├── default/
│   ├── login.html           ← global override for all apps
│   ├── register.html
│   └── verify-email.html
└── my-app/                  ← per-application override (slug = "my-app")
    └── login.html

Resolution order for a given page (e.g. login) and application slug my-app:

  1. $TEMPLATES_DIR/my-app/login.html — per-application override
  2. $TEMPLATES_DIR/default/login.html — global custom override
  3. Built-in fallback shipped with auth-service (templates/default/login.html)

Template variables

Templates are plain HTML with {{VAR}} substitution. The following variables are available:

VariableDescription
{{ACTION_URL}}Form submit target — always /api/auth/sign-in/email
{{REDIRECT_TO}}Post-login redirect path (e.g. /dashboard)
{{APP_SLUG}}client_id of the requesting application
{{AUTH_URL}}Public base URL of the auth service (BETTER_AUTH_URL)
{{ERROR_MESSAGE}}Error message from a failed sign-in attempt (may be empty)
{{OAUTH_QUERY}}Raw OAuth query string (including the BetterAuth signature). Include this as a hidden field named oauth_query in your sign-in form so the OAuth flow can be resumed after authentication.
{{ALLOW_REGISTER}}"true" or "false" — whether self-registration is enabled for this application

Example login template

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Sign in</title>
</head>
<body>
  <form method="POST" action="{{ACTION_URL}}">
    <input type="hidden" name="callbackURL" value="{{REDIRECT_TO}}" />
    <input type="hidden" name="oauth_query" value="{{OAUTH_QUERY}}" />

    <label>Email <input type="email" name="email" required /></label>
    <label>Password <input type="password" name="password" required /></label>

    {{ERROR_MESSAGE}}

    <button type="submit">Sign in</button>
  </form>
</body>
</html>

The oauth_query hidden field is important — when BetterAuth's oauthProvider initiates the login flow, it signs all OAuth parameters (including client_id and a signature) into the query string. Auth Service forwards this as {{OAUTH_QUERY}} so your form can include it, allowing the authorization flow to resume correctly after the user authenticates.

Docker volume mount

In production, mount the templates directory into the container:

auth-service:
  image: ghcr.io/circle-rd/auth-service:latest
  environment:
    TEMPLATES_DIR: /templates
  volumes:
    - ./my-templates:/templates:ro
Copyright © 2026