Device pairing
Approve a device by code with its claims, IP and platform; show an OAuth device-code with a big copyable code and link; and hand a browser sign-in to a native app.
Code
import { DeviceApproval } from "@fadymondy/nasaq/web";export function Approve({ request, expiresAt }: { request: React.ComponentProps<typeof DeviceApproval>["request"]; expiresAt: number }) { return ( <DeviceApproval request={request} expiresAt={expiresAt} onApprove={async () => { await api.approve(request.code); }} onDeny={async () => { await api.deny(request.code); }} /> );}declare const api: { approve(c: string): Promise<void>; deny(c: string): Promise<void> };Auth · beta
Live examples and controls: Device pairing in the lab.
Install
npx shadcn@latest add https://docs.nasaqui.com/r/device-pairing.jsonFour screens for signing in a device that cannot easily sign in itself. DeviceCodeEntry is where a person types the
code from a TV or CLI. DeviceApproval shows what is asking and lets them approve or deny. DeviceCodeDisplay is the
device's own screen with the code, link and QR. DeviceHandoff passes a finished browser sign-in to a native app.
When to use
- OAuth device flow (RFC 8628) for CLIs, TVs and agents.
- Signing in a native app through the browser.
When not to use
- Ordinary sign-in: use
LoginForm. - Managing already-linked devices: use a sessions list.
Import
import { DeviceApproval, DeviceCodeDisplay, DeviceCodeEntry, DeviceHandoff } from "@fadymondy/nasaq/web";
// inside this monorepo: "@nasaq/web"Quick start
import { DeviceApproval } from "@fadymondy/nasaq/web";
export function Approve({ request, expiresAt }: { request: React.ComponentProps<typeof DeviceApproval>["request"]; expiresAt: number }) {
return (
<DeviceApproval
request={request}
expiresAt={expiresAt}
onApprove={async () => {
await api.approve(request.code);
}}
onDeny={async () => {
await api.deny(request.code);
}}
/>
);
}
declare const api: { approve(c: string): Promise<void>; deny(c: string): Promise<void> };Anatomy
DeviceApproval data-slot="device-approval"
├─ code (large, ltr) to compare with the device
├─ request details device, platform, browser, IP, place, time
├─ scopes list
├─ warning + account who is granting access
├─ Approve · Deny
└─ outcome screens approved, denied, expired (with Enter another)
DeviceCodeDisplay code, CopyButton, link, QrCode, live status
DeviceHandoff Open the app, state line, browser and typed-code fallbacks
DeviceCodeEntry OtpInput-style code field, auto-submits when completeAPI
- DeviceCodeEntry:
formprops plusonSubmit(code)(normalised, e.g.WDJBMJHT; return{ error }if unknown),length(8),defaultCode,labels. - DeviceApproval:
request: DeviceRequest(code,client,deviceName,platform,browser,ip,location,requestedAt,scopes),status(pending,approved,denied,expired),expiresAt,account,onApprove,onDeny,onEnterAnother,labels. A pending request pastexpiresAtshows as expired. - DeviceCodeDisplay:
code,verificationUri,verificationUriComplete(QR content),status,expiresAt,onRefresh,mark,labels. - DeviceHandoff:
appName,href(deep link),state(opening,opened,failed),onOpen,browserHref,fallbackCode,onCancel,mark,labels. It never navigates by itself.
Helpers: normalizeUserCode, formatUserCode, isUserCodeComplete, codeSecondsLeft, effectiveCodeStatus, USER_CODE_ALPHABET (consonants only, per RFC 8628).
Examples
The device screen
import { DeviceCodeDisplay } from "@fadymondy/nasaq/web";
export function Screen({ status }: { status: "pending" | "approved" | "denied" | "expired" }) {
return (
<DeviceCodeDisplay
code="WDJBMJHT"
verificationUri="https://example.com/device"
verificationUriComplete="https://example.com/device?user_code=WDJB-MJHT"
status={status}
/>
);
}Accessibility
- Codes are
dir="ltr"and read as separate groups; the copy button announces "Copied". - Status changes on the device screen are announced politely; failures use
role="alert". - The QR code has a text alternative and the link is always shown, so it is never the only route.
RTL & i18n
- English and Arabic are built in; pass
labelsto override. - Codes, IPs and URLs stay left to right inside an RTL page. Logos are never mirrored.
Styling & tokens
- Built from
Button,Card,Alert,QrCodeandCopyButton; tokens only. The QR sits on a white tile so it scans in dark mode.
Do / Don't
- Do show the IP, place and platform, and warn that approving grants access.
- Do expire codes quickly and rate-limit entry on the server.
- Don't approve on page load: a person must press Approve.
- Don't put a secret token in the QR code: only the verification URL and user code.
Related
Lab
https://docs.nasaqui.com/?path=/docs/components-auth-pages-device-pairing--docs
DesktopLoginScreen
A desktop OS sign-in screen. Wallpaper, a clock with a greeting, and a card with the mark and the Nasaq LoginForm, plus optional power buttons.
ForgotPasswordForm
Request a password reset link by email, then a Check your inbox state with a resend countdown and a change-email link; en and ar built in.