tutorial

How to Monitor Canada with Vigilmon

Canada brings CanCan-style authorization to Elixir — but authorization failures are silent and security-critical. Here's how to monitor Canada-powered applications with Vigilmon to catch policy regressions and access control outages.

How to Monitor Canada with Vigilmon

Canada is an authorization library for Elixir that implements the CanCan/CanCanCan policy pattern via the Canada.Can protocol. It enables resource-level permission checks — Canada.Can.can?(user, :edit, post) — with a clean separation between authorization policy and business logic. It's commonly used in Phoenix applications alongside Ecto to enforce who can do what with which resources.

The monitoring challenge: authorization is invisible when it works and catastrophic when it doesn't. A misconfigured policy that blocks all admins looks identical to a login bug from the outside. A policy that grants too much access doesn't show up in your error logs at all.

This tutorial sets up monitoring for a Canada-powered Phoenix application:

  • A health endpoint that validates core authorization policies
  • HTTP uptime monitoring with Vigilmon
  • Heartbeat monitoring for authorization-gated background workers
  • Alerts on authorization failures and policy regressions

Why Monitor Canada?

| Signal | What it catches | |---|---| | Policy module availability | Deploy failures that remove or corrupt policy modules | | Authorization denial rate | Policy regressions that over-restrict or under-restrict access | | Admin access health | Misconfigured admin policies locking out operators | | Background job authorization | Workers failing because their service account lost permissions | | Protocol implementation | Missing Canada.Can implementations for new resource types |

Authorization bugs are security bugs. Monitoring your authorization layer is not optional in production.


Step 1: Implement Canada.Can for Your Resources

Define your authorization policies:

# lib/my_app/policies/post_policy.ex
defimpl Canada.Can, for: MyApp.Accounts.User do
  def can?(%{role: :admin}, _action, _resource), do: true

  def can?(%{id: user_id}, action, %MyApp.Blog.Post{author_id: author_id})
      when action in [:edit, :delete] do
    user_id == author_id
  end

  def can?(_user, :read, %MyApp.Blog.Post{published: true}), do: true
  def can?(_user, :create, MyApp.Blog.Post), do: true
  def can?(_user, _action, _resource), do: false
end

Use it in your Phoenix controllers:

# lib/my_app_web/controllers/post_controller.ex
defmodule MyAppWeb.PostController do
  use MyAppWeb, :controller

  def edit(conn, %{"id" => id}) do
    post = MyApp.Blog.get_post!(id)
    user = conn.assigns.current_user

    if Canada.Can.can?(user, :edit, post) do
      render(conn, :edit, post: post)
    else
      conn
      |> put_status(:forbidden)
      |> put_view(MyAppWeb.ErrorView)
      |> render(:"403")
      |> halt()
    end
  end
end

Step 2: Add a Policy Health Check Endpoint

Create an endpoint that validates your core authorization policies against fixture data. This catches policy module regressions on deploy:

# lib/my_app_web/controllers/health_controller.ex
defmodule MyAppWeb.HealthController do
  use MyAppWeb, :controller

  def index(conn, _params) do
    case check_authorization_policies() do
      :ok ->
        json(conn, %{status: "ok", authorization: "policies_valid"})
      {:error, failures} ->
        conn
        |> put_status(503)
        |> json(%{status: "error", authorization: "policy_regression", failures: failures})
    end
  end

  defp check_authorization_policies do
    admin = %MyApp.Accounts.User{id: 1, role: :admin}
    user = %MyApp.Accounts.User{id: 2, role: :user}
    own_post = %MyApp.Blog.Post{id: 1, author_id: 2, published: true}
    other_post = %MyApp.Blog.Post{id: 2, author_id: 99, published: true}
    draft_post = %MyApp.Blog.Post{id: 3, author_id: 99, published: false}

    checks = [
      {"admin can edit any post", Canada.Can.can?(admin, :edit, own_post), true},
      {"user can edit own post", Canada.Can.can?(user, :edit, own_post), true},
      {"user cannot edit other post", Canada.Can.can?(user, :edit, other_post), false},
      {"user can read published post", Canada.Can.can?(user, :read, own_post), true},
      {"user cannot read draft by other", Canada.Can.can?(user, :read, draft_post), false}
    ]

    failures =
      checks
      |> Enum.reject(fn {_label, result, expected} -> result == expected end)
      |> Enum.map(fn {label, result, expected} ->
        "#{label}: got #{result}, expected #{expected}"
      end)

    if failures == [], do: :ok, else: {:error, failures}
  end
end

Add the route:

# lib/my_app_web/router.ex
scope "/", MyAppWeb do
  pipe_through :api
  get "/health", HealthController, :index
end

Test it:

curl http://localhost:4000/health
# => {"status":"ok","authorization":"policies_valid"}

Step 3: Add Vigilmon HTTP Monitoring

  1. Log in to vigilmon.online and click Add Monitor.
  2. Set Type to HTTP / HTTPS.
  3. Enter your health endpoint: https://yourapp.com/health.
  4. Set Check interval to 1 minute.
  5. Set Expected HTTP status to 200.
  6. Under Advanced, add Response body contains: "authorization":"policies_valid".
  7. Click Save.

When a deploy breaks a policy — say, a refactor that reorganizes user struct fields — the health check catches it within a minute.


Step 4: Track Authorization Denials

Add a Plug that counts 403 responses to catch denial rate spikes:

# lib/my_app_web/plugs/authorization_metrics.ex
defmodule MyAppWeb.Plugs.AuthorizationMetrics do
  import Plug.Conn

  def init(opts), do: opts

  def call(conn, _opts) do
    register_before_send(conn, fn conn ->
      if conn.status == 403 do
        :telemetry.execute([:my_app, :authorization, :denied], %{count: 1}, %{
          path: conn.request_path,
          method: conn.method
        })
      end
      conn
    end)
  end
end

Attach a telemetry handler to track denials in your application:

# lib/my_app/application.ex
:telemetry.attach(
  "authorization-denied",
  [:my_app, :authorization, :denied],
  fn _event, _measurements, metadata, _config ->
    MyApp.AuthMetrics.record_denial(metadata.path)
  end,
  nil
)

Register the plug in your endpoint:

# lib/my_app_web/endpoint.ex
plug MyAppWeb.Plugs.AuthorizationMetrics
plug MyAppWeb.Router

Step 5: Heartbeat Monitoring for Authorization-Gated Background Jobs

Background workers that require authorization (e.g., a job that acts as a specific service user) can silently fail if their permissions are revoked:

# lib/my_app/background_job_heartbeat.ex
defmodule MyApp.BackgroundJobHeartbeat do
  use GenServer

  @interval :timer.minutes(5)
  @vigilmon_heartbeat_url System.get_env("VIGILMON_JOB_HEARTBEAT_URL") ||
                            "https://vigilmon.online/api/heartbeat/YOUR_HEARTBEAT_ID"

  def start_link(_), do: GenServer.start_link(__MODULE__, nil, name: __MODULE__)

  def init(_) do
    schedule()
    {:ok, nil}
  end

  def handle_info(:ping, state) do
    service_user = MyApp.Accounts.get_service_user!()
    authorized = Canada.Can.can?(service_user, :process, MyApp.Jobs.DataExport)

    if authorized do
      :httpc.request(:get, {String.to_charlist(@vigilmon_heartbeat_url), []}, [], [])
    end

    schedule()
    {:noreply, state}
  end

  defp schedule, do: Process.send_after(self(), :ping, @interval)
end

To create the heartbeat in Vigilmon:

  1. Click Add MonitorCron / Heartbeat.
  2. Set expected interval to 5 minutes.
  3. Copy the heartbeat URL to VIGILMON_JOB_HEARTBEAT_URL.

If the service user's role is accidentally changed, the heartbeat stops and Vigilmon alerts you.


Step 6: Set Up Alerts

Configure alert channels:

  1. Go to Alert ChannelsAdd Channel.
  2. Choose Slack, Email, or PagerDuty.
  3. Set alert threshold to 1 consecutive failure for authorization policy checks (policy bugs are high-severity).
  4. For the heartbeat, set Missing heartbeat to alert after 2 missed pings (10 minutes).
  5. Assign channels to all monitors.

For admin-accessible applications, also set up a Status Page so operators can verify access control health independently:

  1. Go to Status PagesCreate Page.
  2. Add your health monitor.
  3. Share with your ops team — they can check if the app is up before assuming it's an access issue.

Key Metrics to Watch

| Metric | Vigilmon feature | What to alert on | |---|---|---| | Policy availability | HTTP monitor on /health | Any 5xx or body mismatch | | Application availability | HTTP monitor on /health | Any non-200 or timeout | | Authorization denial rate | Tracked via Plug metrics + heartbeat | Spike above baseline | | Background job authorization | Heartbeat monitor | Any missed heartbeat | | Response time | Response time chart | P95 > 500ms | | SSL certificate | Cert expiry monitor | Expires in < 14 days |


Conclusion

Canada's protocol-based authorization is elegant and composable — but authorization regressions are security incidents, not just bugs. By running policy fixture checks on every deploy and monitoring them with Vigilmon, you get immediate notification when access control breaks. The heartbeat pattern extends this to background workers, catching the silent case where a service account quietly loses its permissions.

Get started free at vigilmon.online.

Monitor your app with Vigilmon

Free plan — 5 monitors, no credit card required. Up and running in 60 seconds.

Start free →