Guide — Appairage d'un appareil mobile (OAuth 2.1 PKCE)
Flux nominal (Universal Links)
App mobile → Navigateur système → Page web Pyxaura → App mobile → API token
1. Générer PKCE + paramètres
// Swift
import CryptoKit, Foundation
let codeVerifier = Data(UUID().uuidString.utf8 + UUID().uuidString.utf8)
.base64EncodedString()
.replacingOccurrences(of: "+", with: "-")
.replacingOccurrences(of: "/", with: "_")
.replacingOccurrences(of: "=", with: "")
let codeChallenge = Data(SHA256.hash(data: Data(codeVerifier.utf8)))
.base64EncodedString()
.replacingOccurrences(of: "+", with: "-")
.replacingOccurrences(of: "/", with: "_")
.replacingOccurrences(of: "=", with: "")
let state = UUID().uuidString
let deviceId = UIDevice.current.identifierForVendor?.uuidString ?? UUID().uuidString
// Kotlin
import java.security.MessageDigest
import java.util.Base64, UUID
val codeVerifier = Base64.getUrlEncoder().withoutPadding()
.encodeToString(UUID.randomUUID().toString().toByteArray())
val digest = MessageDigest.getInstance("SHA-256").digest(codeVerifier.toByteArray())
val codeChallenge = Base64.getUrlEncoder().withoutPadding().encodeToString(digest)
val state = UUID.randomUUID().toString()
val deviceId = /* Settings.Secure.ANDROID_ID ou identifiant persisté */
2. Ouvrir le navigateur système
GET https://pyxaura.com/oauth/authorize?
client_id=mobile_ios
&redirect_uri=https://votre-app.com/app/callback
&response_type=code
&code_challenge=<codeChallenge>
&code_challenge_method=S256
&state=<state>
&device_id=<deviceId>
&device_name=iPhone+de+Tony
&device_platform=ios
&scope=user:profile+user:spaces+user:query+user:chat
iOS : ASWebAuthenticationSession
Android : Chrome Custom Tabs
3. Recevoir le callback
L'app reçoit https://votre-app.com/app/callback?code=ac_...&state=<state>.
Vérifier que state correspond à celui envoyé (anti-CSRF).
4. Échanger le code contre des tokens
curl -X POST https://pyxaura.com/api/v1/auth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "authorization_code",
"client_id": "mobile_ios",
"code": "ac_...",
"code_verifier": "<codeVerifier>",
"redirect_uri": "https://votre-app.com/app/callback",
"device_id": "<deviceId>"
}'
{
"token_type": "Bearer",
"access_token": "eyJhbGciOiJFUzI1NiIsImtpZCI6...",
"expires_in": 900,
"refresh_token": "rt_...",
"refresh_token_expires_in": 5184000,
"device": { "id": "dev_31", "name": "iPhone de Tony", "platform": "ios" },
"user": { "id": "usr_17", "name": "Tony", "email": "tony@example.com", "plan": "pro" },
"spaces": [{ "id": "spc_9f2", "name": "Projet Alpha" }]
}
Stocker : access_token en mémoire, refresh_token dans Keychain (iOS) / EncryptedSharedPreferences + Keystore (Android). Jamais en localStorage.
5. Rafraîchir le token
L'access token expire après 15 minutes. Rafraîchir avant chaque appel ou à la réception d'un 401 :
curl -X POST https://pyxaura.com/api/v1/auth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "refresh_token",
"client_id": "mobile_ios",
"refresh_token": "rt_...",
"device_id": "<deviceId>"
}'
⚠️ Rotation systématique : chaque rafraîchissement invalide le précédent refresh_token. Un rejeu révoque immédiatement l'appareil.
6. Appeler l'API avec le JWT
curl https://pyxaura.com/api/v1/spaces/spc_9f2/query \
-H "Authorization: Bearer <access_token>" \
-H "Content-Type: application/json" \
-d '{"question": "Quel est le délai de retour ?"}'
7. Révoquer l'appareil
# Depuis l'app
curl -X DELETE https://pyxaura.com/api/v1/devices/dev_31 \
-H "Authorization: Bearer <access_token>"
# Tous les appareils (déconnexion complète)
curl -X POST "https://pyxaura.com/api/v1/auth/logout?all=true" \
-H "Authorization: Bearer <access_token>"
Variante device_code (TV, CLI, borne, QR code)
# 1. Initier
curl -X POST https://pyxaura.com/api/v1/auth/device_authorization \
-d '{"client_id":"mobile_ios","device_id":"<uuid>"}'
# → {"user_code":"PYX-4F7Q","verification_uri":"https://pyxaura.com/appareil","expires_in":600}
# 2. Afficher le QR code vers verification_uri_complete et le user_code
# 3. Poll toutes les 5 s
curl -X POST https://pyxaura.com/api/v1/auth/token \
-d '{"grant_type":"urn:ietf:params:oauth:grant-type:device_code","client_id":"mobile_ios","device_code":"dc_..."}'
# → 400 {"error":"authorization_pending"} ou 200 avec tokens
Sécurité
- PKCE S256 obligatoire —
plainrefusé. - Universal Links prioritaires sur le schéma privé
pyxia://(empêche l'interception). - WebViews embarquées refusées côté serveur.
- Email d'alerte envoyé à chaque nouvel appairage approuvé avec lien de révocation.
- Révocation immédiate via
session_version: l'access token est invalidé en < 1 min après révocation de l'appareil.