Device Authorization plugin implements RFC 8628
The Device Authorization plugin implements the code issuance and approval flow from the OAuth 2.0 Device Authorization Grant (RFC 8628) for limited-input devices such as smart TVs, CLI applications, IoT devices, and gaming consoles. It can be used on its own for Better Auth session tokens, or composed with the OAuth Provider to issue OAuth access tokens.
Device Authorization plugin installation steps
To install the Device Authorization plugin: (1) Add the deviceAuthorization() plugin to the auth config with verificationUri option set to the verification page path (e.g., '/device'). (2) Run npx auth migrate or npx auth generate to add necessary tables to the database. (3) Add deviceAuthorizationClient() plugin to the client configuration.
Device Authorization plugin import paths
Import the plugin from 'better-auth/plugins' on the server side: import { deviceAuthorization } from 'better-auth/plugins'. Import the client plugin from 'better-auth/client/plugins': import { deviceAuthorizationClient } from 'better-auth/client/plugins'.
deviceAuthorization plugin configuration example
Example server-side configuration: betterAuth({ plugins: [deviceAuthorization({ verificationUri: '/device' })] }). The verificationUri can be an absolute URL like 'https://example.com/device' or a relative path like '/device'. Default is '/device'.
deviceAuthorizationClient plugin configuration
The client plugin has no client-specific configuration options. Add it to the client with: createAuthClient({ plugins: [deviceAuthorizationClient()] }).
Device Authorization flow steps
The device flow follows these steps: (1) Device requests codes - the device requests a device code and user code from the authorization server. (2) User authorizes - the user visits a verification URL and enters the user code. (3) Device polls for token - the device polls the server until the user completes authorization. (4) Access granted - once authorized, the device receives the session token or OAuth access token.
Device Authorization token endpoints and use cases
Two token endpoints serve different use cases: (1) /device/token returns a Better Auth session token when using deviceAuthorization() alone to sign your own device into the same Better Auth application. (2) /oauth2/token returns a scoped OAuth access token when using jwt(), oauthProvider(), and oauthDeviceAuthorization() together to let a registered CLI, TV app, or other public client call an OAuth-protected API.
POST /device/code API endpoint
The /device/code endpoint accepts POST requests with the following parameters: client_id (string, required) - the device client identifier; scope (string, optional) - space-separated list of requested scopes; user_id (string, optional) - the user ID to which the device code should be pre-bound (only that user can approve or deny the code). This parameter should only be passed from trusted server-side code.
POST /device/token API endpoint
The /device/token endpoint accepts POST requests with the following parameters: grant_type (string, required) - must be 'urn:ietf:params:oauth:grant-type:device_code'; device_code (string, required) - the device code from the initial request; client_id (string, required) - the device client identifier. The response's access_token field contains a Better Auth session token.
Device code response fields
The /device/code response includes: device_code - the device verification code; user_code - the user-friendly code for verification; verification_uri - the URL where users can verify the code; verification_uri_complete - complete verification URL (e.g., with QR code); interval - polling interval in seconds (default 5).
GET /device endpoint for code verification
The GET /device endpoint validates the user code and claims the pending device code for the calling session. Users must be authenticated when calling this endpoint because it binds the pending device code to that session. Only the same session can later approve or deny. When OAuth Provider integration is enabled, this endpoint also returns the approved resource context.
POST /device/approve API endpoint
The /device/approve endpoint accepts POST requests with userCode (string, required) - the user code to approve. This endpoint requires the user to be authenticated.
POST /device/deny API endpoint
The /device/deny endpoint accepts POST requests with userCode (string, required) - the user code to deny. This endpoint requires the user to be authenticated.
Device Authorization client methods
The deviceAuthorizationClient plugin adds the following methods to the auth client: device() - verify user code validity; device.code() - request device and user codes; device.token() - poll for access token; device.approve() - approve device (requires authentication); device.deny() - deny device (requires authentication).
User code pre-binding with user_id parameter
When requesting a device code, passing user_id pre-binds the code to that user. The code is then bound to that user from the start, skips the claiming step, and only the bound user can approve or deny it. Any other signed-in user receives an access_denied error. This is useful when the user code is displayed where others can see it. The user_id parameter should only be passed from trusted server-side code.
Device Authorization error codes
The device flow defines the following error codes: authorization_pending - user hasn't approved yet (continue polling); slow_down - polling too frequently (increase interval); expired_token - device code has expired; access_denied - user denied the authorization; invalid_grant - invalid device code or client ID.
Device Authorization server configuration options
Server configuration options: verificationUri (string, default '/device') - URL of the verification page; expiresIn (string, default '30m') - expiration time for device codes; interval (string, default '5s') - minimum polling interval; userCodeLength (number, default 8, max 191) - length of the user code; deviceCodeLength (number, default 40, max 191) - length of the device code; generateDeviceCode (function) - custom function to generate device codes returning string or Promise<string>; generateUserCode (function) - custom function to generate user codes returning string or Promise<string>; validateClient (function) - function to validate client IDs taking clientId returning boolean or Promise<boolean>; onDeviceAuthRequest (function) - hook called when device authorization is requested taking clientId and optional scope.
deviceCode table schema
The deviceCode table stores device authorization data with the following fields: id (string, primary key) - unique identifier for the device authorization request; deviceCode (string, unique) - the device verification code; userCode (string, unique) - the user-friendly code for verification; userId (string, optional) - the ID of the user who approved/denied; clientId (string, optional) - the device client identifier; scope (string, optional) - requested scopes; status (string) - current status: pending, approved, or denied; expiresAt (Date) - when the device code expires; lastPolledAt (Date, optional) - last time the device polled for status; pollingInterval (number, optional) - minimum seconds between polls.
oauthDeviceAuthorization extends deviceCode table
The oauthDeviceAuthorization() function extends the deviceCode table with optional oauthClientId and resources fields. Standalone Device Authorization installations do not add those fields and do not expose the RFC 8707 resource request parameter.
Production security requirements for Device Authorization
RFC 8628 requires the device to make outbound HTTPS requests and the user to authenticate at the verification URI in a secure TLS-protected session. In production, serve the verification and approval UI, device-code requests, and token polling over HTTPS. Use HTTP only for explicitly local development.
Approval UI security requirements
The approval UI must: ask the user to enter the user_code, or when using verification_uri_complete (such as a QR code), ask the user to confirm that the displayed code matches the code on the device; show what is being authorized (the client, requested scopes, and when using OAuth Provider, the requested resource); require an explicit approval or denial; and tell the user they are authorizing a device in their possession and warn them not to approve unexpected requests or codes supplied through phishing messages.
Default user code character set and format
Default user codes are case-insensitive and accept whitespace or punctuation inserted for readability when submitted to verification, approve, or deny endpoints. The default character set is ABCDEFGHJKLMNPQRSTUVWXYZ23456789, which excludes 0, O, 1, I to avoid confusion. Custom user codes are matched exactly when they contain characters outside the default alphabet.
Device code collision handling
Device-code issuance makes up to 3 attempts to overcome unique-key collisions. If all attempts collide, the /device/code endpoint returns server_error.
Verification polling rate limit
The /device endpoint is limited to 5 requests over a window equal to the configured device-code lifetime. The /device/token endpoint keeps its own polling interval and slow_down behavior.
Authenticating to /device/code with OAuth clients
Public clients use token_endpoint_auth_method 'none' because an installed CLI cannot keep a shared secret confidential. A confidential client using client_secret_basic may authenticate at /device/code with an Authorization: Basic header and omit the body client_id; client_secret_post sends both values in the form body.
Registering public native OAuth client for CLI
Example code to register a public native CLI client: await auth.api.adminCreateOAuthClient({ headers, body: { token_endpoint_auth_method: 'none', type: 'native', grant_types: [DEVICE_CODE_GRANT_TYPE, 'refresh_token'], scope: 'openid profile offline_access api:read', resources: ['https://api.example.com'] } }).
OAuth device authorization with JWT and OAuth Provider
To authorize a CLI to call an API with OAuth tokens, configure: jwt(), oauthProvider({ loginPage: '/sign-in', consentPage: '/consent', scopes: ['openid', 'profile', 'offline_access', 'api:read'], resources: ['https://api.example.com'] }), and oauthDeviceAuthorization({ verificationUri: '/device' }).
Do not use authClient.device.token for OAuth flow
Do not call authClient.device.token for the OAuth device authorization flow. That client method polls /device/token and returns a Better Auth session token. Registered OAuth clients must poll /oauth2/token instead.
Both Device Authorization token paths can coexist
Both token paths can coexist. First-party device login keeps using /device/token. Registered OAuth clients use /oauth2/token; oauthDeviceAuthorization() prevents their device codes from being redeemed for a Better Auth session token.
Custom code generation example
Example custom code generation configuration: deviceAuthorization({ generateDeviceCode: async () => { return crypto.randomBytes(32).toString('hex'); }, generateUserCode: async () => { const charset = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; let code = ''; for (let i = 0; i < 8; i++) { code += charset[Math.floor(Math.random() * charset.length)]; } return code; } }).
Client validation example
Example client validation configuration: deviceAuthorization({ validateClient: async (clientId) => { const client = await db.oauth_clients.findOne({ id: clientId }); return client && client.allowDeviceFlow; }, onDeviceAuthRequest: async (clientId, scope) => { await logDeviceAuthRequest(clientId, scope); } }).
User authentication required for GET /device
Users must be authenticated when calling GET /device because the verification step binds the pending device code to that session. Only the same session can later approve or deny. If the user is not authenticated when entering the code, redirect them to the login page with a return URL and re-call GET /device after sign-in.
User code format requirements
User codes use a limited character set (excluding similar-looking characters like 0/O, 1/I) to reduce typing errors. The user code is typically formatted with dashes for readability, e.g., ABCD-1234, with a maximum of 12 characters displayed.
Device Authorization plugin for limited-input devices
Better Auth includes a Device Authorization plugin that provides OAuth 2.0 Device Authorization Grant for limited-input devices.