Skip to main content
OAuth Device Flow (RFC 8628) is designed for CLI tools, headless servers, and devices without browsers. Users authorize on their device, and the CLI receives tokens without handling passwords.

Why Device Flow?

Traditional OAuth (Browser-based)

Problem: Browser redirect complex for CLI tools.

Device Flow (CLI-friendly)

Better: CLI asks user to visit URL, handles polling.

How Device Flow Works

Device Flow Endpoints

Step 1: Request Device Code

POST https://api.noorle.com/oauth/device/authorize

Body:
{
  "client_id": "noorle-cli"
}

Response:
{
  "device_code": "abcd1234567890",
  "user_code": "XYZ-789",
  "verification_uri": "https://auth.noorle.com/device",
  "verification_uri_complete": "https://auth.noorle.com/device?user_code=XYZ-789",
  "expires_in": 1800,          // 30 minutes
  "interval": 5                // poll every 5 seconds
}

Step 2: User Authorizes

User visits verification URL:
https://auth.noorle.com/device?user_code=XYZ-789
CLI should display:
Visit: https://auth.noorle.com/device
Code:  XYZ-789

Waiting for authorization...

Step 3: Poll for Token

CLI polls until user approves (or times out):
POST https://api.noorle.com/oauth/token

Body:
{
  "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
  "device_code": "abcd1234567890",
  "client_id": "noorle-cli"
}

Response (pending):
{
  "error": "authorization_pending"
}

(wait 5 seconds, retry)

Response (after user approves):
{
  "access_token": "eyJhbGciOiJIUzI1NiIs...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIs...",
  "expires_in": 3600,        // 1 hour
  "token_type": "Bearer",
  "scope": "read write execute"
}

CLI Example: Noorle Login

$ noorle login
? Which account? (use arrow keys)
 account-1 (alice@example.com)
  account-2 (work account)

To authorize, visit:
  https://auth.noorle.com/device

Code: XYZ-789

Waiting for authorization...

Successfully authenticated!
Token saved to ~/.noorle/credentials.json

Behind the Scenes

#!/bin/bash

# Step 1: Request device code
RESPONSE=$(curl -s -X POST \
  https://api.noorle.com/oauth/device/authorize \
  -d "client_id=noorle-cli")

DEVICE_CODE=$(echo $RESPONSE | jq '.device_code')
USER_CODE=$(echo $RESPONSE | jq '.user_code')
VERIFY_URL=$(echo $RESPONSE | jq '.verification_uri_complete')

# Step 2: Display to user
echo "Visit: $VERIFY_URL"
echo "Code: $USER_CODE"
echo "Waiting for authorization..."

# Step 3: Poll for token
while true; do
  TOKEN=$(curl -s -X POST \
    https://api.noorle.com/oauth/token \
    -d "grant_type=urn:ietf:params:oauth:grant-type:device_code" \
    -d "device_code=$DEVICE_CODE" \
    -d "client_id=noorle-cli")

  ERROR=$(echo $TOKEN | jq '.error')
  if [ "$ERROR" != "authorization_pending" ]; then
    break
  fi

  sleep 5
done

# Step 4: Store token
echo $TOKEN | jq '.access_token' > ~/.noorle/token.json
echo "Authorized!"

Token Storage

Tokens should be stored securely:

Linux/Mac

# Store in secure file with restricted permissions
~/.noorle/credentials.json
(permissions: 600 - owner readable/writable only)

Content:
{
  "access_token": "eyJhbGc...",
  "refresh_token": "eyJhbGc...",
  "expires_at": 1711183000,
  "scope": "read write execute"
}

Windows

%APPDATA%\Noorle\credentials.json
(permissions: NTFS ACLs - user only)

Content: (same as Linux/Mac)

Docker/Container

# Pass token via environment variable
docker run -e NOORLE_TOKEN="eyJhbGc..." myapp

Token Refresh

Access tokens expire (default 1 hour). Use refresh token to get new access token:
POST https://api.noorle.com/oauth/token

Body:
{
  "grant_type": "refresh_token",
  "refresh_token": "eyJhbGc...",
  "client_id": "noorle-cli"
}

Response:
{
  "access_token": "new-eyJhbGc...",
  "refresh_token": "new-eyJhbGc...",
  "expires_in": 3600,
  "token_type": "Bearer"
}
CLI should auto-refresh before expiration:
# Check expiration
EXPIRES_AT=$(jq '.expires_at' ~/.noorle/credentials.json)
NOW=$(date +%s)

if [ $EXPIRES_AT -lt $((NOW + 300)) ]; then
  # Token expires in < 5 minutes, refresh now
  curl -X POST ... (refresh token request)
  # Store new token
fi

Using Tokens in CLI

Once authenticated, CLI sends token automatically:
# Get stored token
TOKEN=$(jq -r '.access_token' ~/.noorle/credentials.json)

# Make API request
curl -H "Authorization: Bearer $TOKEN" \
     https://api.noorle.com/agents
Most CLI tools hide this:
# User just runs commands
$ noorle agents list
Research Agent
Support Bot
Analyst

# CLI automatically:
# 1. Loads token from ~/.noorle/credentials.json
# 2. Includes it in Authorization header
# 3. Refreshes if needed

Logout

Remove tokens:
$ noorle logout

Are you sure? (y/n) y
Logged out. Token removed.
Implementation:
# Remove token file
rm ~/.noorle/credentials.json

# Optionally revoke token on server
curl -X POST https://api.noorle.com/oauth/revoke \
  -d "token=eyJhbGc..."

Scopes

Request specific permissions via scope:
POST https://api.noorle.com/oauth/device/authorize

Body:
{
  "client_id": "noorle-cli",
  "scope": "read execute"   // only read + execute, no manage
}
Available scopes:
  • read - View resources (list, get)
  • execute - Run agents, call tools
  • manage - Create, update resources
  • admin - Delete, full control
  • offline_access - Request refresh token
Example:
# Read-only CLI (can't accidentally break anything)
noorle login --scope "read"

# Bot that executes only
noorle login --scope "execute"

# Admin tool with full access
noorle login --scope "read write execute manage admin offline_access"

Security Considerations

1. User Code

User code (e.g., XYZ-789) is short and memorable. Users must carefully enter it. Risks:
Risk: User types wrong code
  └─ Typo grants access to attacker

Mitigation:
  ├─ Code is only valid for 30 minutes
  ├─ Code must be entered exactly
  └─ Mismatches logged

2. Device Code

Device code is the secret. Keep it private:
Risk: Device code leaked
  └─ Attacker can poll and get tokens

Mitigation:
  ├─ Only valid for 30 minutes
  ├─ One-time use (can't reuse same code)
  └─ Revoked on first error

3. Token Storage

Access token on disk is sensitive:
Risk: Disk compromised
  └─ Attacker gets access token

Mitigation:
  ├─ File permissions: 600 (owner only)
  ├─ Use OS secure storage if possible
  ├─ Tokens expire after 1 hour
  └─ Refresh tokens rotated on use

4. Revocation

Users can revoke tokens anytime:
Console → Settings → Connected Apps
├─ CLI: noorle-cli
│  ├─ Authorized: 2024-03-22
│  ├─ Last used: 2024-03-22 14:30
│  └─ [Revoke]
│     └─ All tokens immediately invalid

Troubleshooting

ProblemSolution
”authorization_pending” (forever)User didn’t approve. Check verification URL was visited.
”invalid_request”Check client_id is correct. Verify device code format.
”Token expired”Refresh token. Use refresh_token endpoint.
Code expires in 30 minUser took too long. Start over with noorle login.
Can’t find stored tokenCheck ~/.noorle/credentials.json exists. Verify permissions.

Next: Learn about JWT Tokens for stateless authentication.