Device authorization¶
Sign in on a device with no browser or keyboard — a CLI, a smart TV, an IoT box — using the OAuth 2.0 Device Authorization Grant (RFC 8628). The device shows a short code; the user approves it on a phone or laptop where they are already signed in; the device then receives a session token.
Install¶
Ships with the core; there is no extra to install.
Setup¶
import deadbolt as db
from deadbolt.plugins.device_authorization import device_authorization
auth = db.Auth(
adapter=db.MemoryAdapter(),
secret="a-32-byte-or-longer-secret......",
email_and_password=db.EmailPassword(enabled=True),
plugins=[device_authorization(verification_uri="https://app.example.com/device")],
)
Run the schema generator (or your migration) so the device_request table exists.
Configuration¶
| Parameter | Type | Default | Description |
|---|---|---|---|
verification_uri |
string | — | Required. The page where the user enters the code. |
expires_in |
integer | 600 |
Request lifetime in seconds. |
interval |
integer | 5 |
Minimum seconds between device polls; faster polling gets slow_down. |
user_code_length |
integer | 8 |
Characters in the user code (shown as XXXX-XXXX). |
The flow¶
device deadbolt user (signed in)
│ POST /device/code │ │
│─────────────────────────────▶│ device_code + user_code │
│◀─────────────────────────────│ │
│ show user_code + uri ─────────────────────────────────────────▶ │ visits /device
│ POST /device/token (poll) ───▶│ authorization_pending │ GET /device?user_code=…
│◀──────────────────────────────│ │ POST /device/approve
│ POST /device/token (poll) ───▶│ access_token + user ◀────────────│
API¶
POST /device/code¶
Starts a request. Auth: public. Request: optional { "client_id": "..." }.
Response 200: device_code, user_code, verification_uri, verification_uri_complete,
expires_in, interval.
POST /device/token¶
The device polls here with { "device_code": "..." }. Auth: public. On success returns
{ "access_token": "...", "token_type": "Bearer", "user": { ... } } plus the session cookie — the
access_token is a session credential usable with the bearer plugin. Until then it
returns 400 with one of these codes:
| Code | Meaning |
|---|---|
authorization_pending |
The user has not acted yet; keep polling. |
slow_down |
Polling faster than interval; back off. |
access_denied |
The user denied the request. |
expired_token |
The request lifetime elapsed. |
invalid_grant |
Unknown device code (or the approving user was deleted). |
GET /device¶
Validates a code so the approval page can name the device. Auth: session required.
Query: user_code. Response: { "user_code": "...", "client_id": "...", "status": "pending" }.
POST /device/approve · POST /device/deny¶
The signed-in user approves or denies { "user_code": "..." }. Auth: session required.
Response: { "success": true }.
Notes¶
- Device code is hashed and single-use. Only
SHA-256(device_code)is stored, and the row is deleted the moment a token is issued, denied, or expired. - User code is unambiguous. Codes use an alphabet without
0/O/1/Iso they are easy to read off a screen and type. - Approval binds the user. The session the device receives belongs to whoever approved the code, and is a normal, independently revocable deadbolt session.