AttestoPhoenix.Controller.DeviceVerificationController (AttestoPhoenix v0.19.1)

Copy Markdown View Source

Device verification page (RFC 8628 §3.3).

The user-facing leg of the device grant: the user opens GET /oauth/device_verification (optionally with ?user_code=... from the verification_uri_complete), authenticates with the host, confirms the user_code, and approves or denies. This controller owns the protocol — it resolves and normalizes the user_code, drives the host login, and performs the atomic store transition (Attesto.DeviceCode.approve/3 / deny/2) — while the host owns the HTML and the login UI through two callbacks:

  • :authenticate_device_user(conn -> {:ok, subject} | {:halt, conn}). Establishes the resource owner (the host's session/login). subject is a map with :subject (the sub) and optional :scope / :claims (carrying e.g. acr/auth_time for step-up). {:halt, conn} lets the host take over the connection to render its login UI.

  • :render_device_verification(conn, view -> conn). Renders the page. view is a map %{stage, user_code, pending} where stage is :prompt (ask the user to enter/confirm the code — pending is the Attesto.DeviceCodeStore.pending_view() or nil), :approved, :denied, or :invalid (unknown/expired/already-decided code).

No auto-approval (RFC 8628 §3.3.1 / §5.4)

A user_code arriving via verification_uri_complete is only ever pre-filled — approval requires an explicit user POST carrying decision=approve. The controller never approves from a GET or from the URL alone, closing the one-click remote-phishing vector.

Host responsibility: CSRF + session (REQUIRED)

The approve/deny POST is a state-changing, session-authenticated action. The library performs the store transition but does NOT own the browser session or CSRF token, so the host MUST mount these routes behind a pipeline that enforces CSRF protection (protect_from_forgery) and a same-site session — otherwise a logged-in victim's browser can be made to POST an attacker's user_code and approve the attacker's device (a confused-deputy / device login CSRF). The controller additionally requires HTTPS (a credential-bearing authorization action must not cross a plain-HTTP hop).

Summary

Functions

verify(conn, params)

@spec verify(Plug.Conn.t(), map()) :: Plug.Conn.t()