Skip to Content

Device Authorization Flow

La plupart des flux OAuth supposent que l’utilisateur est sur un appareil avec un navigateur web et un clavier pour saisir ses credentials. Cette supposition s’effondre pour :

  • Smart TV et lecteurs multimédias : Interface télécommande, pas de saisie texte facile
  • Outils CLI : Opèrent dans un terminal, pas de navigateur intégré
  • Appareils IoT : Microcontrôleurs sans couche UI
  • Consoles de jeux : Interfaces limitées sans accès navigateur

Le Device Authorization Flow (RFC 8628) résout ce problème en laissant l’utilisateur s’authentifier sur un appareil secondaire (son smartphone ou ordinateur) pendant que l’appareil principal attend.

Le Flux

┌─────────────────┐ ┌─────────────────┐ │ Appareil CLI │ │ Serveur Auris │ │ (ou Smart TV) │ │ │ └────────┬────────┘ └────────┬────────┘ │ │ │ 1. POST /api/oauth/device │ │ client_id=xyz&scope=openid │ │─────────────────────────────────────>│ │ │ │ 2. device_code, user_code, │ │ verification_uri, expires_in │ │<─────────────────────────────────────│ │ │ │ 3. Afficher: "Aller sur │ │ accounts.altovar.net/activate │ │ Entrer le code: WDJB-MJHT" │ │ │ │ 4. Poll POST /api/auth/token │ │ (toutes les interval secondes) │ │─────────────────────────────────────>│ │ │ │ [Utilisateur s'authentifie │ │ sur son téléphone/ordi] │ │ │ │ 5. 200 OK avec access_token │ │<─────────────────────────────────────│

Étape 1 : Demander les Codes Appareil

POST /api/oauth/device Content-Type: application/x-www-form-urlencoded client_id=CLI_CLIENT_ID&scope=openid+profile+email

Étape 2 : Réception des Codes

{ "device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS", "user_code": "WDJB-MJHT", "verification_uri": "https://auth.yourdomain.com/activate", "verification_uri_complete": "https://auth.yourdomain.com/activate?user_code=WDJB-MJHT", "expires_in": 600, "interval": 5 }
  • device_code : Utilisé par l’appareil pour le polling — ne jamais l’afficher à l’utilisateur
  • user_code : Court et lisible — affiché à l’utilisateur pour le saisir sur l’appareil secondaire
  • verification_uri : URL où l’utilisateur va s’authentifier
  • verification_uri_complete : URL avec le code pré-rempli (pour les QR codes)
  • expires_in : Secondes avant expiration des deux codes (600 = 10 minutes)
  • interval : Secondes minimum entre les tentatives de polling (5 secondes)

Étape 3 : Afficher les Instructions à l’Utilisateur

L’appareil affiche les instructions claires :

Auris CLI — Connexion Requise Ouvre ton navigateur sur : https://auth.yourdomain.com/activate Entre le code : WDJB-MJHT En attente d'autorisation... (expire dans 10 minutes)

Étape 4 : Polling du Token Endpoint

Pendant que l’utilisateur s’authentifie sur son appareil secondaire, l’appareil principal poll le token endpoint :

POST /api/auth/token Content-Type: application/x-www-form-urlencoded grant_type=urn:ietf:params:oauth:grant-type:device_code &device_code=GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS &client_id=CLI_CLIENT_ID

Réponses possibles pendant le polling :

RéponseAction
error: authorization_pendingContinuer le polling
error: slow_downAugmenter l’intervalle de 5 secondes
error: expired_tokenRecommencer le flux depuis l’étape 1
error: access_deniedL’utilisateur a refusé — arrêter
200 OK avec access_tokenSuccès — stocker les tokens

Étape 5 : Succès

{ "access_token": "eyJhbGciOiJFUzI1NiJ9...", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "8xLOxBtZp8", "scope": "openid profile email", "id_token": "eyJhbGciOiJSUzI1NiJ9..." }

Exemple TypeScript — CLI Tool

async function loginDevice(clientId: string): Promise<TokenSet> { // Étape 1 : Demander les codes appareil const deviceRes = await fetch('https://auth.yourdomain.com/api/oauth/device', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ client_id: clientId, scope: 'openid profile email offline_access', }), }) const { device_code, user_code, verification_uri, expires_in, interval } = await deviceRes.json() // Étape 2 : Afficher les instructions console.log(`\nOuvre : ${verification_uri}`) console.log(`Entre le code : ${user_code}\n`) // Étape 3 : Polling const deadline = Date.now() + expires_in * 1000 let pollInterval = interval * 1000 while (Date.now() < deadline) { await new Promise((resolve) => setTimeout(resolve, pollInterval)) const tokenRes = await fetch('https://auth.yourdomain.com/api/auth/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'urn:ietf:params:oauth:grant-type:device_code', device_code, client_id: clientId, }), }) const data = await tokenRes.json() if (tokenRes.ok) return data if (data.error === 'slow_down') { pollInterval += 5000 } else if (data.error === 'access_denied') { throw new Error('Autorisation refusée par l\'utilisateur') } else if (data.error === 'expired_token') { throw new Error('Code expiré. Réessaie.') } // authorization_pending → continuer le polling } throw new Error('Timeout — code expiré') }

Design du User Code

Les user codes doivent être courts et faciles à taper sur des interfaces difficiles (télécommande TV, clavier virtuel). Auris génère des codes avec :

  • Alphabet de 20 caractères : Pas de voyelles (évite les mots offensants accidentels), pas de caractères ambigus (0/O, 1/I/l)
  • 8 caractères avec un tiret au milieu (WDJB-MJHT) pour la lisibilité
  • Espace de 25,6 milliards de possibilités — difficile à brute-forcer dans la fenêtre de 10 minutes

Le device_code interne est stocké comme hash SHA-256, jamais en clair.

Comparaison des Grant Types

Grant TypeInitié parAppareil secondaireUsage Principal
Authorization Code + PKCEUtilisateur (navigateur)NonWeb apps, mobile apps
Device FlowAppareilOuiCLI, Smart TV, IoT
Client CredentialsServeurNonServices M2M
CIBAServeur/AgentOui (push)Call center, terminaux POS

Concepts Associés

  • OAuth 2.0 & OIDC — Les concepts d’autorisation sous-jacents
  • CIBA — Authentification backchannel initiée côté serveur
  • Tokens — Access tokens, refresh tokens et leurs propriétés
  • PKCE Flow — Le flux pour web apps et mobile