# `AttestoPhoenix.Controller.DeviceVerificationController`
[🔗](https://github.com/XukuLLC/attesto_phoenix/blob/v0.19.1/lib/attesto_phoenix/controller/device_verification_controller.ex#L1)

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).

# `verify`

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

---

*Consult [api-reference.md](api-reference.md) for complete listing*
