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).subjectis a map with:subject(thesub) and optional:scope/:claims(carrying e.g.acr/auth_timefor 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.viewis a map%{stage, user_code, pending}wherestageis:prompt(ask the user to enter/confirm the code —pendingis theAttesto.DeviceCodeStore.pending_view()ornil),: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
@spec verify(Plug.Conn.t(), map()) :: Plug.Conn.t()