Device Authorization
Device authorization flow for TVs, consoles, CLIs, and other limited-input clients.
The DeviceAuthorizationPlugin implements a device authorization flow for clients that cannot easily complete an interactive sign-in flow, such as TVs, game consoles, smart displays, and terminal apps.
When enabled, the plugin issues a short user_code plus a longer device_code, lets a signed-in user approve or deny the request on another device, and returns a Better Auth session token from the polling endpoint once approved.
Setup
use better_auth::plugins::DeviceAuthorizationPlugin;
let auth = BetterAuth::new(config)
.database(database)
.plugin(
DeviceAuthorizationPlugin::new()
.enabled(true)
.verification_uri("/device")
.interval(5)
.expires_in(1800)
)
.build()
.await?;Configuration
| Option | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Enables the device authorization endpoints |
verification_uri | String | "/device" | URL or path the user should open to enter the user_code |
interval | i64 | 5 | Minimum polling interval in seconds for /device/token |
expires_in | i64 | 1800 | Lifetime in seconds for the device authorization request |
verification_uri is returned to the device client exactly as configured. In practice, this should usually point to a user-facing page in your app where the user can sign in and submit the userCode.
How It Works
Device Flow
- The device client calls
POST /device/codewith aclient_idand optionalscope. - The API returns
device_code,user_code,verification_uri,expires_in, andinterval. - The user opens
verification_urion another device, signs in if needed, and enters theuser_code. - Your verification UI calls
POST /device/approveorPOST /device/denywith theuserCode. - The original device polls
POST /device/tokenwith thedevice_codeuntil the request is approved or denied. - On approval,
/device/tokenreturns anaccess_tokencontaining a Better Auth session token.
The optional scope value is stored with the device authorization record so applications can track the requested scope, but the plugin currently creates a standard Better Auth session when the request is approved.
API Endpoints
The Device Authorization plugin exposes 4 endpoints. The request and response shapes documented below are the current reference for this plugin.
| Endpoint | Method | Auth | Description |
|---|---|---|---|
/device/code | POST | No | Issue a new device_code and user_code |
/device/token | POST | No | Poll for approval and exchange the device_code for a session token |
/device/approve | POST | Yes | Approve a pending device authorization request |
/device/deny | POST | Yes | Deny a pending device authorization request |
Request and Response Shapes
Create a device code:
{
"client_id": "living-room-tv",
"scope": "openid profile"
}Example response:
{
"device_code": "a-long-random-device-code",
"user_code": "AB12CD34",
"verification_uri": "/device",
"expires_in": 1800,
"interval": 5
}Approve from a signed-in browser session:
{
"userCode": "AB12CD34"
}Successful token exchange:
{
"access_token": "session_..."
}Building the Verification Page
The plugin provides the backend endpoints, but you still need a small verification UI in your app:
- Render a page at the same URL you use for
verification_uri. - Let the user sign in if they do not already have a session.
- Collect the
userCodeshown on the device. - Call
POST /device/approveorPOST /device/denywith{ "userCode": "..." }.
Because approval and denial require a valid Better Auth session, the verification page should run in a browser or app context where the user can authenticate normally.
Polling Behavior
The /device/token endpoint uses the current device authorization state to guide the client:
| Status | Condition |
|---|---|
200 | Request approved, returns { "access_token": "session_..." } |
400 authorization_pending | The user has not approved or denied the request yet |
400 slow_down | The client polled faster than the configured interval |
400 access_denied | The user explicitly denied the request |
400 invalid_grant | The device_code is unknown, expired, or has already been consumed |
Once a device code is successfully exchanged, it is consumed and cannot be reused.
Errors
| Status | Condition |
|---|---|
401 | /device/approve or /device/deny was called without a valid session |
404 | The supplied userCode was not found |
404 | The plugin is disabled and the device endpoints are not available |