docs(device-authorization): gate approval on owner-only request details

Approve stays disabled unless the lookup returned the client ID, which
GET /device includes only for the session that claimed the code. The
demo also discards stale lookups, reports invalid or expired codes, and
both examples show a registered client name instead of the raw ID.
This commit is contained in:
Gustavo Valverde
2026-09-28 13:24:32 +01:00
parent 84193e0514
commit bb0c8c6a9b
2 changed files with 57 additions and 7 deletions
@@ -462,6 +462,11 @@ Users must be authenticated to approve or deny device authorization requests:
Avoid approval pages that show only the user code, or that enable Approve before the client and scopes appear. On such a page the user cannot tell a request from their own device apart from one started by someone else, for example a code sent to them in a message. The risk is higher when `validateClient` is not configured, because anyone can then start a request.
```tsx title="app/device/approve/page.tsx"
// Client IDs you registered, mapped to names your users recognize.
const knownClients: Record<string, string> = {
"acme-cli": "Acme CLI",
};
export default function DeviceApprovalPage() {
const { user } = useAuth(); // Must be authenticated
const searchParams = useSearchParams();
@@ -474,10 +479,16 @@ export default function DeviceApprovalPage() {
} | null>(null);
useEffect(() => {
setRequest(null);
if (!user || !userCode) return;
let active = true;
authClient.device({ query: { user_code: userCode } }).then(({ data }) => {
setRequest(data);
// Owner-only details are missing when another session claimed the code.
if (active && data?.client_id) setRequest(data);
});
return () => {
active = false;
};
}, [user, userCode]);
const handleApprove = async () => {
@@ -519,7 +530,11 @@ export default function DeviceApprovalPage() {
return (
<div>
<h2>Device Authorization Request</h2>
<p>Client: {request?.client_id}</p>
<p>
Client: {request?.client_id
? knownClients[request.client_id] ?? `Unrecognized client (${request.client_id})`
: "Loading..."}
</p>
<p>Scopes: {request?.scope || "None"}</p>
<p>
Resources: {Array.isArray(request?.resource)