---
layout: article
title: Device flow
description: Authorize TVs, CLIs, and other input-constrained devices against your Appwrite OAuth2 server with the device authorization grant.
back: /docs/products/auth/oauth-server
---

The device authorization grant (RFC 8628) lets a client request access even when it cannot open a browser or accept a callback. A TV app, command-line tool, or hardware device shows the user a code, and the user completes authorization on a phone or computer.

This flow involves two applications:

- The **device client** is the third-party application requesting access. It communicates with Appwrite over HTTP.
- The **verification page** belongs to your project. You build this page with an Appwrite Client SDK so the user can sign in, review the request, and approve or reject it.

# How it works

![The device flow between the device, the OAuth2 server, and a second device like the user's phone or laptop](/images/docs/oauth-server/diagram-device-flow.avif)

1. The device client starts a device authorization. Appwrite returns a device code, a shorter user code, and the URL of your verification page.
2. The device shows the user code and verification URL to the user.
3. The user opens the verification page on a second device, confirms the code, signs in, and reviews the request.
4. The verification page connects the pending request to the signed-in user and asks them to approve or reject it.
5. While this happens, the original device polls the token endpoint. After approval, the next successful poll returns tokens directly to the device.

# Configure device flow

Device flow must be enabled in both the OAuth2 server settings and the client settings.

## Configure the OAuth2 server

Use `project.updateOAuth2Server` from a Server SDK to configure the verification page and code behavior:

| Setting | Purpose |
| --- | --- |
| `verificationUrl` | The URL of the verification page you host, such as `https://your-product.com/activate`. This setting is required for device flow. |
| `userCodeLength` | The number of characters in the user code. It can be from 6 to 12 and defaults to 8. |
| `userCodeFormat` | The characters used in the user code: `numeric`, `alphabetic`, or `alphanumeric`. The default is `alphanumeric`. |
| `deviceCodeDuration` | How long the device code and user code remain valid, in seconds. It can be from 60 to 1,800 and defaults to 600. |

**The call replaces the whole configuration**

`updateOAuth2Server` sets the full OAuth2 server configuration in one call. Include your existing authorization URL, scopes, and other settings when you add the device flow settings.

## Enable device flow for the client

The client must also have **Device flow** enabled. Turn it on when you create or update the client in the Console, or set `deviceFlow: true` with a Server SDK. Devices that cannot protect a client secret should be registered as public clients. See [Clients](/docs/products/auth/oauth-server/clients#register) for client registration.

# Integrate the device client

The device client uses HTTP for the device authorization and token requests.

## 1. Start a device authorization

Send the client ID and requested scopes to the device authorization endpoint as JSON:

```curl
curl -X POST 'https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/device_authorization' \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -d '{
    "client_id": "<CLIENT_ID>",
    "scope": "openid profile tasks.read"
  }'
```
```hurl
POST https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/device_authorization
Content-Type: application/json
Accept: application/json
{
    "client_id": "<CLIENT_ID>",
    "scope": "openid profile tasks.read"
}
```

The request can also include:

- `authorization_details` for a JSON-encoded rich authorization request.
- `resource` for one resource indicator URI or an array of URIs.
- `audience` as a compatibility alias for one resource indicator.

See [Scopes](/docs/products/auth/oauth-server/scopes#rich-authorization-requests) for authorization details and resource restrictions.

Appwrite returns the codes, expiration, and polling interval:

```json
{
    "device_code": "8575375669b9031cb5371b0ee39985c2...",
    "user_code": "3RG9K9QF",
    "verification_uri": "https://your-product.com/activate",
    "verification_uri_complete": "https://your-product.com/activate?user_code=3RG9K9QF",
    "expires_in": 600,
    "interval": 1
}
```

Show the `user_code` and `verification_uri` on the device. You can also present `verification_uri_complete` as a link or QR code, which opens the verification page with the code already filled in.

## 2. Poll for tokens

Start polling the token endpoint in the background. Wait at least the number of seconds in `interval` between requests:

```curl
curl -X POST 'https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/token' \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -d '{
    "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
    "device_code": "<DEVICE_CODE>",
    "client_id": "<CLIENT_ID>"
  }'
```
```hurl
POST https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/token
Content-Type: application/json
Accept: application/json
{
    "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
    "device_code": "<DEVICE_CODE>",
    "client_id": "<CLIENT_ID>"
}
```

Handle each response according to its `error` value:

- `authorization_pending`: The user has not finished authorization. Wait for `interval`, then poll again.
- `slow_down`: The client is polling too quickly. Increase the delay before the next request.
- `expired_token`: The device code expired. Start a new device authorization and show the new user code.
- `access_denied`: The user rejected the request. Stop polling and let them restart if they want to try again.

# Build the verification page

The `verificationUrl` page runs on the user's second device and completes the interactive part of the flow.

1. Read `user_code` from the URL search parameters. If it is missing, show an input where the user can enter the code displayed on the original device.
2. Show the code and ask the user to confirm that it matches the original device.
3. Make sure the user is signed in to your project. If they are signed out, send them through your sign-in or sign-up flow and return them to the verification page with the user code intact.
4. Pass the confirmed code to `oauth2.createGrant`. This connects the pending device request to the signed-in user and returns the grant record.

```client-web
import { Client, Oauth2 } from 'appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const oauth2 = new Oauth2(client);

const result = await oauth2.createGrant({
    userCode: '<USER_CODE>'
});

console.log(result);
```
```client-flutter
import 'package:appwrite/appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

Oauth2 oauth2 = Oauth2(client);

Oauth2Grant result = await oauth2.createGrant(
    userCode: '<USER_CODE>',
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

let oauth2 = Oauth2(client)

let oauth2Grant = try await oauth2.createGrant(
    user_code: "<USER_CODE>"
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Oauth2

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

val oauth2 = Oauth2(client)

val result = oauth2.createGrant(
    user_code = "<USER_CODE>",
)
```
```client-android-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Oauth2;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>"); // Your project ID

Oauth2 oauth2 = new Oauth2(client);

oauth2.createGrant(
    "<USER_CODE>", // user_code
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        Log.d("Appwrite", result.toString());
    })
);
```
```client-react-native
import { Client, Oauth2 } from 'react-native-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const oauth2 = new Oauth2(client);

const result = await oauth2.createGrant({
    userCode: '<USER_CODE>'
});

console.log(result);
```

Use the returned grant to show the client, requested scopes, and authorization details. Then let the user approve or reject it with the same [consent flow](/docs/products/auth/oauth-server/authorization#consent) used for browser authorization.

# Finish on the original device

The original device keeps polling while the user completes the verification page. After approval, the token endpoint returns access and refresh tokens, plus an ID token when `openid` was granted. The device uses these tokens directly. Device flow has no authorization code or redirect callback on the original device.

```json
{
    "access_token": "<ACCESS_TOKEN>",
    "refresh_token": "<REFRESH_TOKEN>",
    "id_token": "<ID_TOKEN>",
    "token_type": "Bearer",
    "expires_in": 3600,
    "scope": "openid profile tasks.read",
    "authorization_details": null
}
```

Store the newest refresh token securely and use the token response's granted scopes to determine which features are available. See [Tokens](/docs/products/auth/oauth-server/tokens) for validation, refresh, and revocation.
