Skip to content

Device Authorization

The OAuth 2.0 Device Authorization Grant (RFC 8628) — the "enter this code on another device" flow for TVs and CLIs. Mirrors the TS deviceAuthorization() plugin.

Enable

python
from better_auth import BetterAuth
from better_auth.plugins_ext import DeviceAuthorizationPlugin

auth = BetterAuth(
    secret="a-strong-32-character-minimum-secret",
    plugins=[DeviceAuthorizationPlugin(expires_in="30m", interval="5s")],
)

Options

OptionTypeDefaultDescription
expires_instr"30m"Device/user-code lifetime (duration string).
intervalstr"5s"Minimum polling interval (duration string).
device_code_lengthint40Length of the device code.
user_code_lengthint8Length of the user-facing code.
generate_device_codecallable | NoneNoneCustom device-code generator.
generate_user_codecallable | NoneNoneCustom user-code generator.
validate_clientcallable | NoneNone(client_id) -> bool gate on /device/code.
on_device_auth_requestcallable | NoneNoneObserver called when a device requests a code.
verification_uristr | NoneNoneOverride the advertised verification URI.

Endpoints

MethodPath
POST/device/code
POST/device/token
GET/device
POST/device/approve
POST/device/deny

Schema

TableColumns
deviceCodedeviceCode, userCode, userId, expiresAt, status, lastPolledAt, pollingInterval, clientId, scope

Notes

  • Errors are OAuth-shaped on the wire ({"error", "error_description"}, RFC 6749 style), not this port's usual {"code", "message"} envelope — matching TS.
  • Redemption of an approved code is atomic (delete-and-return): concurrent pollers race on the same delete and exactly one mints a session. The pending claim and polling-interval bump use a guarded compare-and-swap, closing the race behind TS's GHSA-cq3f-vc6p-68fh fix.
  • Pairs naturally with OAuth Provider when you are the authorization server.

MIT licensed · API-compatible with better-auth