Connecter un appareil (device flow)
Sur cette page
Un téléviseur, un terminal ou un objet connecté n’a ni clavier confortable ni navigateur pour afficher la page de connexion de TOSIAM. Le device flow (autorisation d’appareil) contourne le problème : l’appareil affiche un code court et une adresse ; l’utilisateur ouvre l’adresse sur son téléphone ou son ordinateur, se connecte, saisit le code et accepte ; pendant ce temps, l’appareil interroge TOSIAM jusqu’à recevoir ses jetons. L’appareil ne voit jamais le mot de passe.
Le code est dans le dépôt tosiam-samples, dossier tosiam-device-flow : un appareil en ligne de commande, Node.js sans dépendance.
-
Appareil vers TOSIAM
Demande un code sur
/oauth2/ref/device/code -
TOSIAM vers Appareil
Renvoie
device_code,user_code,verification_url,intervaletexpires_in - Appareil Affiche l’adresse et le code
- Utilisateur (téléphone) vers TOSIAM Ouvre l’adresse, se connecte, saisit le code
- TOSIAM vers Utilisateur (téléphone) Demande le consentement
-
Appareil vers TOSIAM
Interroge
/oauth2/ref/access_tokentoutes les 5 s (code=device_code) -
TOSIAM vers Appareil
Répond
authorization_pending - Utilisateur (téléphone) vers TOSIAM Autorise l’appareil
- TOSIAM vers Appareil Renvoie l’access token et le refresh token
-
Appareil vers TOSIAM
Lit le profil sur
userinfo, puis révoque ses jetons
Étape 1 : le client de l’appareil#
L’appareil est un client public : distribué à des milliers d’exemplaires, il ne peut pas garder de secret. Dans Clients, Créer un agent ouvre l’assistant :
- étape Identité : l’identifiant
appareil-tv, sans URI de redirection (l’appareil n’en reçoit pas) ; - étape Sécurité : type de client Public, qui ne demande pas de secret et fixe la méthode d’authentification à
none; dans les types d’autorisation, cochez Device Code et Refresh Token et décochez les autres ; scopeprofileseulement.
Créez le client, puis vérifiez sa fiche. Onglet Général :
profile (2).Le Nom du client (Appareil TV dans l’exemple) est le nom que l’utilisateur lira sur l’écran de consentement : choisissez-le pour qu’il reconnaisse son appareil.
Onglet Avancé, en haut :
token (1) et méthode d’authentification aucune (2).- Types de réponse :
token. L’assistant ne le propose pas ; ordevice/codeexige unresponse_type, qui doit figurer ici. Sans lui, l’utilisateur tombe surunsupported_response_typeaprès avoir saisi le code. - Méthode d’authentification au point d’accès des jetons :
aucune(none), celle des clients publics.
Plus bas, dans le même onglet :
- Consentement implicite : désactivé. L’utilisateur doit voir quel appareil il autorise, et quoi.
- Types d’autorisation : Device Code (
http://oauth.net/grant_type/device/1.0) et Refresh Token.
Pour un client public, l’assistant annonce PKCE obligatoire : ce réglage, s’il est activé, ne gêne pas le device flow.
Étape 2 : demander un code#
L’appareil commence par demander un code à device/code. client_id, scope et response_type sont obligatoires. Le type de réponse doit figurer dans la liste du client, et les scopes dans ses scopes : TOSIAM ne le vérifie pas à cette étape, l’utilisateur tomberait sur unsupported_response_type ou invalid_scope après avoir saisi le code.
async requestCode(scope) {
const { response, data, text } = await call('/device/code',
{ form: { client_id: clientId, scope, response_type: 'token' } });
if (!response.ok) throw errorFor(response.status, data, text);
if (!data.device_code || !data.user_code || !data.verification_url || !Number.isFinite(data.expires_in)) {
throw unexpected(); // une page HTML ou autre chose que TOSIAM : TOSIAM_URL à vérifier
}
return { deviceCode: data.device_code, userCode: data.user_code, interval: data.interval,
expiresIn: data.expires_in, verificationUrl: data.verification_url };
},TOSIAM répond :
{
"device_code": "9b3e2f4a-…",
"user_code": "hc6FLmLQ",
"verification_url": "http://localhost:8080/tosiam/oauth2/ref/device/user",
"interval": 5,
"expires_in": 300
}device_code: le secret de l’appareil, qu’il présentera pour obtenir les jetons. Il ne s’affiche pas.user_code: le code que l’utilisateur recopie. La page l’accepte sans tenir compte de la casse : l’exemple l’affiche en majuscules, plus lisibles à l’écran et plus faciles à taper sur un téléphone.verification_url: la page du royaume du client, où l’utilisateur saisit le code.intervaletexpires_in: l’appareil attend 5 secondes entre deux appels, et le code expire au bout de 5 minutes. Les deux se règlent dans le fournisseur OAuth2 (« Intervalle d’interrogation des appareils », « Durée de vie du code d’appareil (secondes) »).
Étape 3 : afficher le code#
const code = await client.requestCode(values.scope);
const userCode = code.userCode.toUpperCase();
out(`Sur votre téléphone ou votre ordinateur, ouvrez : ${code.verificationUrl}`);
out(`et saisissez le code : ${userCode}`);
out(`Lien direct : ${code.verificationUrl}?user_code=${encodeURIComponent(userCode)}`);Le lien direct contient déjà le code : l’utilisateur n’a plus qu’à se connecter et accepter. C’est ce qu’un vrai appareil affiche en QR code, à côté du code pour qui préfère le saisir.
Étape 4 : attendre l’utilisateur#
L’appareil interroge ensuite l’endpoint de jeton avec le grant de l’appareil et son device_code, dans le paramètre code :
async poll(deviceCode) {
const { response, data, text } = await call('/access_token',
{ form: { client_id: clientId, grant_type: DEVICE_GRANT, code: deviceCode } });
if (response.ok) {
if (!data.access_token) throw unexpected();
return { tokens: { accessToken: data.access_token, refreshToken: data.refresh_token,
expiresIn: data.expires_in, scope: data.scope } };
}
if (data?.error === 'authorization_pending') return { pending: true };
if (data?.error === 'slow_down') return { slowDown: true };
throw errorFor(response.status, data, text);
},| Réponse de TOSIAM | Signification | Ce que fait l’appareil |
|---|---|---|
authorization_pending | L’utilisateur n’a pas encore validé | Attend l’intervalle et recommence |
slow_down | L’appareil interroge trop vite | Ajoute 5 secondes à l’intervalle, pour de bon |
200 avec access_token et refresh_token | L’utilisateur a accepté | Passe à l’étape 6 |
authorization_declined | L’utilisateur a refusé (ou le code a déjà servi) | S’arrête (code de sortie 1) |
expired_token | Le code a expiré | S’arrête et propose de relancer (code de sortie 1) |
La boucle attend avant chaque appel et s’arrête d’elle-même à l’expiration du code, même si TOSIAM répond encore authorization_pending :
export async function waitForTokens(client, code, { sleep = ms => new Promise(r => setTimeout(r, ms)),
now = Date.now, onWait } = {}) {
const deadline = now() + code.expiresIn * 1000;
let interval = code.interval >= 1 ? code.interval * 1000 : DEFAULT_INTERVAL;
for (;;) {
await sleep(interval);
if (now() >= deadline) throw new TosiamError('expired', 'Le code a expiré');
const result = await client.poll(code.deviceCode);
if (result.tokens) return result.tokens;
if (result.slowDown) interval += SLOW_DOWN_STEP;
else onWait?.(Math.round((deadline - now()) / 1000));
}
}Étape 5 : côté utilisateur#
L’utilisateur ouvre l’adresse affichée par l’appareil, saisit le code (1) et valide (2) :
S’il n’est pas connecté, TOSIAM lui présente sa page de connexion, puis le ramène au code :
L’écran de consentement nomme l’appareil (1) et ce qu’il demande (2) ; l’utilisateur refuse (3) ou autorise (4) :
Enfin, TOSIAM confirme. La même page s’affiche après un refus : c’est l’appareil qui l’indique.
Avec le lien direct, l’utilisateur passe directement de la connexion au consentement.
Étape 6 : le profil, puis la révocation#
Avec son access token, l’appareil lit le profil de l’utilisateur à userinfo, puis révoque le refresh token : l’appareil de l’exemple s’arrête là, et révoquer le refresh token met aussi fin à l’access token. La révocation est dans un finally : une fois les jetons obtenus, elle a lieu même si la lecture du profil échoue. Une révocation refusée n’est qu’un avertissement : la connexion a réussi.
try {
const profile = await client.userinfo(tokens.accessToken);
out(`Connecté : ${displayName(profile)}`);
out(`Jeton d'accès valable ${minutes(tokens.expiresIn)} min (scope : ${tokens.scope ?? values.scope}).`);
} finally {
try {
await client.revoke(tokens.refreshToken ?? tokens.accessToken);
out('Jetons révoqués.');
} catch (e) {
err(`Révocation impossible (${e.message}) : les jetons restent valables jusqu'à leur expiration.`);
}
}Étape 7 : lancer et tester#
cd tosiam-device-flow
./up.sh
cd appareil
node appareil.jsAppareil « appareil-tv » — TOSIAM http://localhost:8080/tosiam, royaume ref
Sur votre téléphone ou votre ordinateur, ouvrez : http://localhost:8080/tosiam/oauth2/ref/device/user
et saisissez le code : HC6FLMLQ
Lien direct : http://localhost:8080/tosiam/oauth2/ref/device/user?user_code=HC6FLMLQ
En attente de l'utilisateur (code valable 5 min)…Ouvrez l’adresse, connectez-vous avec dduck / Donald-Duck-2026, saisissez le code et choisissez Autoriser. Quelques secondes plus tard :
Connecté : Donald DUCK (dduck)
Jeton d'accès valable 60 min (scope : profile).
Jetons révoqués.Avec Refuser, ou sans réponse pendant 5 minutes :
Refusé : l'utilisateur n'a pas autorisé l'appareil (ou le code a déjà servi).Code expiré : l'utilisateur ne l'a pas validé à temps. Relancez l'appareil pour obtenir un nouveau code.| Code de sortie | Signification |
|---|---|
| 0 | Appareil autorisé, profil lu, jetons révoqués |
| 1 | Refusé, code expiré, ou réponse inattendue de TOSIAM |
| 2 | Client ou royaume inconnu, ou client refusé par TOSIAM |
| 3 | TOSIAM injoignable |
| 130 | Attente interrompue (Ctrl-C) |
En cas de problème#
| Symptôme | Cause probable |
|---|---|
400 client_id, scope and response_type are required parameters | response_type absent : TOSIAM l’exige, contrairement à la RFC 8628 |
invalid_grant Unknown Grant Type, urn:ietf:params:oauth:grant-type:device_code | Grant de la RFC 8628 : TOSIAM attend http://oauth.net/grant_type/device/1.0 |
400 code is a required parameter | Code de l’appareil envoyé dans device_code : TOSIAM l’attend dans code |
La page affiche unsupported_response_type | Le type de réponse n’est pas dans la liste du client, ou token seul avec le scope openid |
La page affiche invalid_scope | Un scope demandé par l’appareil n’est pas dans les scopes du client |
Réponse inattendue de TOSIAM : vérifiez TOSIAM_URL ou HTTP 404 | TOSIAM_URL désigne autre chose que TOSIAM (sans /tosiam, page d’un proxy…) |
| La page affiche « The code you entered cannot be found » | Code mal recopié, expiré, ou déjà utilisé |
slow_down à chaque appel | L’appareil n’attend pas l’intervalle entre deux appels |
invalid_client | Client inconnu dans ce royaume |
Invalid realm, … | Royaume inconnu (--royaume) |
| Le téléphone n’ouvre pas l’adresse | localhost désigne le téléphone lui-même : il faut une adresse de TOSIAM que le téléphone joint |
En production#
- HTTPS partout : le code de l’appareil et les jetons circulent dans les requêtes, et l’utilisateur se connecte sur la page de vérification.
- Le
device_codeest un secret : jamais affiché ni journalisé. Seul leuser_codeest montré. - Un client public, sans secret : un appareil distribué ne garde pas de secret. Limitez ses scopes au strict nécessaire, et gardez le consentement : l’utilisateur doit voir quel appareil il autorise.
- Respectez
intervalet ralentissez surslow_down; arrêtez d’interroger à l’expiration du code. - Les jetons sur l’appareil : rangez le refresh token dans le stockage protégé de l’appareil, et révoquez-le quand l’utilisateur dissocie l’appareil de son compte.
- L’hameçonnage par code : quelqu’un peut envoyer à une victime un code qu’il a lui-même demandé. L’écran de consentement doit nommer clairement l’appareil ; des durées de code courtes limitent le risque.
Pour aller plus loin#
- Grant types : le device flow de TOSIAM et ses différences avec la RFC 8628.
- Authentification REST sans navigateur : une application de votre organisation qui se connecte sans page de connexion, avec le mot de passe.
- Service à service avec client_credentials : un service qui agit pour son propre compte, sans utilisateur.
Mis à jour le