How to Monitor Phx.Gen.Auth with Vigilmon
Phx.Gen.Auth generates a complete authentication system for Phoenix applications: user registration, login, password reset, email confirmation, and session management — all wired up with secure defaults. But auth is the most critical path in any web application. When the session store degrades, login starts failing silently. When the email delivery service hiccups, password reset tokens expire before users receive them.
External monitoring surfaces what Phx.Gen.Auth can't self-report. This tutorial covers:
- A health endpoint that validates auth-layer dependencies
- HTTP uptime monitoring with Vigilmon
- Heartbeat monitoring to detect when token-cleanup jobs stall
- Slack alerts and a status page
Step 1: Generate and configure Phx.Gen.Auth
If you haven't already scaffolded authentication:
mix phx.gen.auth Accounts User users
mix ecto.migrate
Add the HTTP client for heartbeat pings:
# mix.exs
defp deps do
[
# ... generated auth deps (bcrypt_elixir, etc.)
{:req, "~> 0.4"}
]
end
Step 2: Build a health endpoint that validates auth dependencies
Auth depends on three things: the database (for user records and tokens), the session store (ETS or Redis), and optionally email delivery. Check all three:
# lib/my_app_web/plugs/health_check.ex
defmodule MyAppWeb.Plugs.HealthCheck do
import Plug.Conn
def init(opts), do: opts
def call(%Plug.Conn{request_path: "/health"} = conn, _opts) do
checks = run_checks()
status = if checks.status == "ok", do: 200, else: 503
conn
|> put_resp_content_type("application/json")
|> send_resp(status, Jason.encode!(checks))
|> halt()
end
def call(conn, _opts), do: conn
defp run_checks do
db = database_check()
session = session_store_check()
all_ok = db.status == "ok" and session.status == "ok"
%{
status: if(all_ok, do: "ok", else: "degraded"),
database: db,
session_store: session
}
end
defp database_check do
start = System.monotonic_time(:millisecond)
case Ecto.Adapters.SQL.query(MyApp.Repo, "SELECT 1", [], timeout: 3_000) do
{:ok, _} ->
%{status: "ok", latency_ms: System.monotonic_time(:millisecond) - start}
{:error, reason} ->
%{status: "error", reason: inspect(reason)}
end
end
defp session_store_check do
# Phx.Gen.Auth uses cookie sessions by default; validate the signing key is configured
secret = Application.get_env(:my_app, MyAppWeb.Endpoint)[:secret_key_base]
if is_binary(secret) and byte_size(secret) >= 64 do
%{status: "ok", type: "cookie"}
else
%{status: "error", reason: "secret_key_base missing or too short"}
end
end
end
Register the plug before your router:
# lib/my_app_web/endpoint.ex
plug MyAppWeb.Plugs.HealthCheck
plug MyAppWeb.Router
Test it:
mix phx.server
curl http://localhost:4000/health | jq .
# {
# "status": "ok",
# "database": {"status": "ok", "latency_ms": 2},
# "session_store": {"status": "ok", "type": "cookie"}
# }
Step 3: Track auth metrics with telemetry
Phoenix emits telemetry for every request. Attach a handler that counts authentication events:
# lib/my_app/telemetry.ex
defmodule MyApp.Telemetry do
require Logger
def setup do
:telemetry.attach(
"auth-request-logger",
[:phoenix, :router_dispatch, :stop],
&handle_dispatch/4,
nil
)
end
def handle_dispatch(_event, measurements, %{route: route, conn: conn}, _cfg) do
duration_ms = System.convert_time_unit(measurements.duration, :native, :millisecond)
cond do
route in ["/users/log_in", "/users/register"] and conn.status == 200 ->
Logger.info("[Auth] successful request to #{route} in #{duration_ms}ms")
route in ["/users/log_in", "/users/register"] and conn.status >= 400 ->
Logger.warning("[Auth] failed request to #{route} — status=#{conn.status} #{duration_ms}ms")
true ->
:ok
end
end
end
Call MyApp.Telemetry.setup() in application.ex:
def start(_type, _args) do
MyApp.Telemetry.setup()
# ...
end
Auth failures now surface in your logs — and you can configure a Vigilmon keyword monitor on a log-shipping health endpoint to alert when failure rate spikes.
Step 4: Monitor the health endpoint with Vigilmon
- Sign up at vigilmon.online
- Click New Monitor → HTTP
- Enter
https://yourdomain.com/health - Set check interval: 1 minute
- Enable keyword check: body must contain
"status":"ok" - Save
Also add a monitor directly on your login page to catch routing regressions:
- Click New Monitor → HTTP
- Enter
https://yourdomain.com/users/log_in - Set check interval: 5 minutes
- Enable keyword check: body must contain
Log in - Save
If a bad deploy removes the /users/log_in route, Vigilmon catches it within five minutes.
Step 5: Heartbeat monitoring for token cleanup jobs
Phx.Gen.Auth generates a UserToken schema. Stale tokens accumulate unless you run periodic cleanup. Monitor that cleanup with a heartbeat:
# lib/my_app/workers/token_cleanup_worker.ex
defmodule MyApp.Workers.TokenCleanupWorker do
use GenServer
require Logger
@interval :timer.hours(1)
def start_link(_), do: GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
def init(state) do
schedule()
{:ok, state}
end
def handle_info(:run, state) do
run_cleanup()
schedule()
{:noreply, state}
end
defp run_cleanup do
# Delete expired session and reset tokens
cutoff = DateTime.add(DateTime.utc_now(), -7, :day)
{deleted, _} =
MyApp.Repo.delete_all(
from t in MyApp.Accounts.UserToken,
where: t.inserted_at < ^cutoff and t.context != "session"
)
Logger.info("[TokenCleanup] deleted #{deleted} expired tokens")
ping_vigilmon()
rescue
e ->
Logger.error("[TokenCleanup] failed: #{Exception.message(e)}")
end
defp ping_vigilmon do
url = Application.get_env(:my_app, :vigilmon)[:heartbeat_url]
if url do
case Req.get(url, receive_timeout: 5_000) do
{:ok, %{status: 200}} -> :ok
error -> Logger.warning("Vigilmon heartbeat ping failed: #{inspect(error)}")
end
end
end
defp schedule, do: Process.send_after(self(), :run, @interval)
end
Add to your supervision tree:
children = [
MyApp.Repo,
MyAppWeb.Endpoint,
MyApp.Workers.TokenCleanupWorker
]
In Vigilmon, create a Heartbeat Monitor:
- Click New Monitor → Heartbeat
- Set expected interval: 2 hours
- Copy the ping URL
- Set it as
VIGILMON_HEARTBEAT_URLin your environment
If the cleanup job crashes or the database becomes unreachable, the heartbeat stops — Vigilmon alerts you within two missed intervals.
Step 6: Monitor the password reset flow
Password reset is the highest-stakes auth path: a broken reset flow locks users out permanently. Add a synthetic check:
# In your health check plug
defp password_reset_check do
# Verify the reset endpoint is reachable and returns the form
url = Application.get_env(:my_app, :base_url) <> "/users/reset_password"
case Req.get(url, receive_timeout: 3_000) do
{:ok, %{status: 200, body: body}} ->
if String.contains?(body, "Forgot your password") do
%{status: "ok"}
else
%{status: "error", reason: "reset page content mismatch"}
end
{:ok, %{status: status}} ->
%{status: "error", reason: "unexpected HTTP #{status}"}
{:error, reason} ->
%{status: "error", reason: inspect(reason)}
end
end
Add it to run_checks/0:
defp run_checks do
db = database_check()
session = session_store_check()
reset = password_reset_check()
all_ok = db.status == "ok" and session.status == "ok" and reset.status == "ok"
%{
status: if(all_ok, do: "ok", else: "degraded"),
database: db,
session_store: session,
password_reset: reset
}
end
Step 7: Slack alerts and status page
Slack alerts:
- In Vigilmon, go to Notifications → New Channel → Slack
- Paste your Slack incoming webhook URL
- Enable the channel on your health monitor, login monitor, and heartbeat monitor
When the database goes down:
🔴 DOWN: yourdomain.com/health
Keyword "status":"ok" not found
Detected: US-East
2 minutes ago
When the login page is unreachable:
🔴 DOWN: yourdomain.com/users/log_in
Keyword "Log in" not found in response body
Detected: EU-West
5 minutes ago
Status page:
- Go to Status Pages → New Status Page
- Add your health monitor, login monitor, and heartbeat monitor
- Share the URL with your team
What you've built
| What | How |
|------|-----|
| Health endpoint | Plug checking DB, session store, and password reset path |
| Keyword check | Vigilmon keyword match on "status":"ok" |
| Uptime monitoring | Vigilmon HTTP monitor → /health |
| Login page check | Vigilmon HTTP monitor → /users/log_in |
| Token cleanup | Heartbeat GenServer + Vigilmon heartbeat monitor |
| Auth telemetry | Phoenix telemetry handler logging auth request outcomes |
| Slack alerts | Vigilmon Slack notification channel |
| Status page | Vigilmon public status page |
Phx.Gen.Auth makes your authentication solid. Vigilmon tells you when it cracks.
Next steps
- Add a monitor on the
/users/confirmpath to catch email confirmation regressions - Alert on elevated 401/403 rates from your access logs to detect brute-force attempts early
- Use Vigilmon's response time history to track login latency trends as your user table grows
Get started free at vigilmon.online.