Custom auth page 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:
$TEMPLATES_DIR/my-app/login.html— per-application override$TEMPLATES_DIR/default/login.html— global custom override- 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:
| Variable | Description |
|---|---|
{{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