deviceflow

package
v0.0.16 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 11, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

README

Device Flow (RFC 8628)

OAuth 2.0 Device Authorization Grant implementation for input-constrained devices.

Overview

The device flow allows devices with limited input capabilities (smart TVs, IoT devices, CLI tools) to authenticate users through a secondary device with better input capabilities (like a smartphone or computer).

How It Works

  1. Device Initiates: Device requests a device code and user code
  2. User Receives Code: Device displays the user code to the user
  3. User Authorizes: User visits verification URI on another device and enters the code
  4. Device Polls: Device polls the token endpoint until user completes authorization
  5. Tokens Issued: Once authorized, device receives access and refresh tokens

Configuration

Enable device flow in your OIDC provider configuration:

auth:
  oidcprovider:
    deviceFlow:
      enabled: true              # Enable device flow
      codeExpiry: "10m"         # Device code lifetime (default: 10 minutes)
      userCodeLength: 8         # Number of characters in user code (default: 8)
      userCodeFormat: "XXXX-XXXX" # User code format (default: XXXX-XXXX)
      pollingInterval: 5        # Minimum seconds between polls (default: 5)
      verificationUri: "/device" # Verification page path (default: /device)
      maxPollAttempts: 120      # Maximum poll attempts (default: 120)
      cleanupInterval: "5m"     # Cleanup job interval (default: 5 minutes)

API Endpoints

Device Authorization

POST /oauth2/device/authorize

Initiates device authorization flow.

Request:

curl -X POST https://auth.example.com/oauth2/device/authorize \
  -d "client_id=your_client_id" \
  -d "scope=openid profile email"

Response:

{
  "device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS",
  "user_code": "WDJB-MJHT",
  "verification_uri": "https://auth.example.com/device",
  "verification_uri_complete": "https://auth.example.com/device?user_code=WDJB-MJHT",
  "expires_in": 600,
  "interval": 5
}
Token Exchange

POST /oauth2/token

Device polls this endpoint to exchange device code for tokens.

Request:

curl -X POST https://auth.example.com/oauth2/token \
  -d "grant_type=urn:ietf:params:oauth:grant-type:device_code" \
  -d "device_code=GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS" \
  -d "client_id=your_client_id"

Success Response (200):

{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "refresh_abc123...",
  "id_token": "eyJhbGciOiJSUzI1NiIs...",
  "scope": "openid profile email"
}

Pending Response (400):

{
  "error": "authorization_pending",
  "error_description": "user has not yet authorized the device"
}

Slow Down Response (400):

{
  "error": "slow_down",
  "error_description": "polling too frequently, slow down by 5 seconds"
}

User Verification Flow

1. Code Entry Page

GET /device

Shows HTML form where user enters the device code.

Optional query parameter: user_code (pre-fills the code)

2. Code Verification

POST /device/verify

Validates the user code and shows consent screen if valid.

Request:

  • user_code: The code displayed on the device
3. Authorization Decision

POST /device/authorize

Handles user's authorization decision.

Request:

  • user_code: The code being authorized
  • action: Either approve or deny

Error Codes

Device flow uses OAuth 2.0 error codes:

  • authorization_pending - User hasn't authorized yet (device should continue polling)
  • slow_down - Device is polling too frequently (add 5 seconds to interval)
  • expired_token - Device code has expired (device must restart flow)
  • access_denied - User denied the authorization request
  • invalid_grant - Invalid device code or already consumed

Security Considerations

User Code Generation
  • Uses base20 charset (BCDFGHJKLMNPQRSTVWXZ) to avoid ambiguous characters
  • Collision detection with retry mechanism
  • Configurable length and format
Rate Limiting
  • Enforces minimum polling interval (default 5 seconds)
  • Returns slow_down error if device polls too frequently
  • Tracks poll count and enforces maximum attempts
Code Expiration
  • Device codes expire after configured duration (default 10 minutes)
  • Background job cleans up expired codes every 5 minutes
  • Old consumed codes are purged after 7 days
Brute Force Protection
  • User code format makes brute force attacks impractical
  • Device code is long and cryptographically secure (32 bytes)
  • Single-use codes are marked as consumed after token exchange

Implementation Details

Database Schema

The device_codes table stores:

  • Device code (long, secure, URL-safe)
  • User code (short, human-typable)
  • Client ID and scope
  • Status (pending, authorized, denied, expired, consumed)
  • User and session IDs (set upon authorization)
  • Poll count and last polled timestamp
  • PKCE support (optional code challenge)
Status Transitions
pending → authorized → consumed (success)
        → denied (user rejected)
        → expired (timeout)
Polling Behavior
  1. Device polls every interval seconds
  2. If too frequent, returns slow_down error
  3. If pending, returns authorization_pending
  4. If authorized, issues tokens and marks as consumed
  5. If denied/expired, returns appropriate error

Testing

Run device flow tests:

go test ./plugins/oidcprovider/deviceflow/...
Key Test Coverage
  • Code generation (uniqueness, format)
  • Device code lifecycle (status transitions)
  • Rate limiting (polling intervals)
  • Expiration handling
  • Configuration defaults

Example Usage

See the CLI example in /examples/deviceflow-cli/ for a complete implementation demonstrating:

  • Device authorization initiation
  • User code display
  • Token polling with proper intervals
  • Error handling
  • Token refresh

References

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ValidateUserCodeFormat

func ValidateUserCodeFormat(format string) error

ValidateUserCodeFormat validates the user code format string.

Types

type CodeGenerator

type CodeGenerator struct {
	// contains filtered or unexported fields
}

CodeGenerator generates device codes and user codes.

func NewCodeGenerator

func NewCodeGenerator(userCodeLength int, userCodeFormat string) *CodeGenerator

NewCodeGenerator creates a new code generator.

func (*CodeGenerator) GenerateDeviceCode

func (g *CodeGenerator) GenerateDeviceCode() (string, error)

GenerateDeviceCode generates a long, secure device code (32+ bytes, URL-safe base64).

func (*CodeGenerator) GenerateUserCode

func (g *CodeGenerator) GenerateUserCode() (string, error)

GenerateUserCode generates a short, human-typable code Uses base20 charset (BCDFGHJKLMNPQRSTVWXZ) to avoid ambiguous characters.

type Config

type Config struct {
	Enabled          bool          `json:"enabled"`
	DeviceCodeExpiry time.Duration `json:"deviceCodeExpiry"` // e.g., 10 minutes
	UserCodeLength   int           `json:"userCodeLength"`   // e.g., 8 characters
	UserCodeFormat   string        `json:"userCodeFormat"`   // e.g., "XXXX-XXXX"
	PollingInterval  int           `json:"pollingInterval"`  // e.g., 5 seconds
	VerificationURI  string        `json:"verificationUri"`  // e.g., "/device"
	AllowedClients   []string      `json:"allowedClients"`   // optional whitelist
	MaxPollAttempts  int           `json:"maxPollAttempts"`  // max polls before requiring new request
	CleanupInterval  time.Duration `json:"cleanupInterval"`  // how often to clean up expired codes
}

Config holds device flow configuration.

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns the default device flow configuration.

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service handles device flow business logic.

func NewService

func NewService(repo *repo.DeviceCodeRepository, config Config) *Service

NewService creates a new device flow service.

func (*Service) AuthorizeDevice

func (s *Service) AuthorizeDevice(ctx context.Context, userCode string, userID, sessionID xid.ID) error

AuthorizeDevice marks a device as authorized by a user.

func (*Service) CleanupExpiredCodes

func (s *Service) CleanupExpiredCodes(ctx context.Context) (int, error)

CleanupExpiredCodes removes expired device codes.

func (*Service) CleanupOldConsumedCodes

func (s *Service) CleanupOldConsumedCodes(ctx context.Context, olderThan time.Duration) (int, error)

CleanupOldConsumedCodes removes old consumed device codes.

func (*Service) ConsumeDeviceCode

func (s *Service) ConsumeDeviceCode(ctx context.Context, deviceCode string) error

ConsumeDeviceCode marks a device code as consumed after token exchange.

func (*Service) DenyDevice

func (s *Service) DenyDevice(ctx context.Context, userCode string) error

DenyDevice marks a device authorization as denied.

func (*Service) GetConfig

func (s *Service) GetConfig() Config

GetConfig returns the device flow configuration.

func (*Service) GetDeviceCodeByDeviceCode

func (s *Service) GetDeviceCodeByDeviceCode(ctx context.Context, deviceCode string) (*schema.DeviceCode, error)

GetDeviceCodeByDeviceCode retrieves a device code by device code.

func (*Service) GetDeviceCodeByUserCode

func (s *Service) GetDeviceCodeByUserCode(ctx context.Context, userCode string) (*schema.DeviceCode, error)

GetDeviceCodeByUserCode retrieves a device code by user code.

func (*Service) InitiateDeviceAuthorization

func (s *Service) InitiateDeviceAuthorization(ctx context.Context, clientID string, scope string, appID, envID xid.ID, orgID *xid.ID) (*schema.DeviceCode, error)

InitiateDeviceAuthorization generates a new device code and user code.

func (*Service) PollDeviceCode

func (s *Service) PollDeviceCode(ctx context.Context, deviceCode string) (*schema.DeviceCode, bool, error)

PollDeviceCode handles device polling for authorization status Returns: dc (*schema.DeviceCode), shouldSlowDown (bool), error.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL