@ollaid/native-sso 1.0.8 → 2.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +276 -74
- package/dist/hooks/useLogout.d.ts +41 -0
- package/dist/hooks/useTokenHealthCheck.d.ts +11 -5
- package/dist/index.cjs +150 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +150 -44
- package/dist/index.js.map +1 -1
- package/dist/services/api.d.ts +26 -0
- package/dist/services/nativeAuth.d.ts +1 -1
- package/dist/types/native.d.ts +5 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,11 +22,12 @@ Package NPM Frontend-First pour l'authentification Native SSO Ollaid.
|
|
|
22
22
|
13. [Migration Laravel](#migration-laravel)
|
|
23
23
|
14. [Flux d'authentification](#flux-dauthentification)
|
|
24
24
|
15. [Session & localStorage](#session--localstorage)
|
|
25
|
-
16. [
|
|
26
|
-
17. [
|
|
27
|
-
18. [
|
|
28
|
-
19. [
|
|
29
|
-
20. [
|
|
25
|
+
16. [Déconnexion synchronisée](#déconnexion-synchronisée)
|
|
26
|
+
17. [OnboardingModal](#onboardingmodal)
|
|
27
|
+
18. [useTokenHealthCheck](#usetokenhealthcheck)
|
|
28
|
+
19. [Sécurité](#sécurité)
|
|
29
|
+
20. [Exports](#exports)
|
|
30
|
+
21. [Publication & Installation npm](#publication--installation-npm)
|
|
30
31
|
|
|
31
32
|
---
|
|
32
33
|
|
|
@@ -155,21 +156,30 @@ déclenche la redirection côté frontend :
|
|
|
155
156
|
|
|
156
157
|
## Déconnexion externe (sans le composant)
|
|
157
158
|
|
|
158
|
-
Quand l'utilisateur se déconnecte **depuis votre SaaS** (ex : bouton logout dans le backoffice), le composant `NativeSSOPage` n'est pas impliqué. Vous devez
|
|
159
|
+
Quand l'utilisateur se déconnecte **depuis votre SaaS** (ex : bouton logout dans le backoffice), le composant `NativeSSOPage` n'est pas impliqué. Vous devez utiliser la fonction `logout()` du package pour garantir une déconnexion complète et synchronisée.
|
|
160
|
+
|
|
161
|
+
> ⚠️ **RÈGLE OBLIGATOIRE** : Toute déconnexion frontend **DOIT** passer par `logout()`. Ne jamais effacer le `localStorage` manuellement ni utiliser `clearAuthToken()` seul — cela laisserait des sessions orphelines actives sur l'IAM.
|
|
159
162
|
|
|
160
163
|
### Utilisation
|
|
161
164
|
|
|
162
165
|
```tsx
|
|
163
|
-
import {
|
|
166
|
+
import { logout } from '@ollaid/native-sso';
|
|
164
167
|
|
|
165
168
|
const handleLogout = async () => {
|
|
166
|
-
await
|
|
167
|
-
clearAuthToken(); // nettoie les 5 clés localStorage du package
|
|
169
|
+
await logout(); // ✅ Double revocation (SaaS + IAM) + nettoyage localStorage
|
|
168
170
|
navigate('/auth/login');
|
|
169
171
|
};
|
|
170
172
|
```
|
|
171
173
|
|
|
172
|
-
###
|
|
174
|
+
### Que fait `logout()` ?
|
|
175
|
+
|
|
176
|
+
1. **Révoque le token SaaS** — `POST /api/native/logout` (supprime le Sanctum token)
|
|
177
|
+
2. **Révoque la session IAM** — `POST /api/iam/disconnect` (avec `sanctum_token` + `app_access_token_ref`)
|
|
178
|
+
3. **Nettoie le localStorage** — supprime les 6 clés du package
|
|
179
|
+
|
|
180
|
+
Les appels réseau sont en `Promise.allSettled` (best-effort) : même si le serveur est injoignable, le localStorage est **toujours** nettoyé.
|
|
181
|
+
|
|
182
|
+
### Clés localStorage nettoyées
|
|
173
183
|
|
|
174
184
|
| Clé | Description |
|
|
175
185
|
|-----|-------------|
|
|
@@ -178,8 +188,23 @@ const handleLogout = async () => {
|
|
|
178
188
|
| `user` | Objet utilisateur (avec `iam_reference`, `alias_reference`) |
|
|
179
189
|
| `account_type` | Type de compte (`user` ou `client`) |
|
|
180
190
|
| `alias_reference` | Référence de l'alias de connexion |
|
|
191
|
+
| `app_access_token_ref` | Référence de l'`AppAccessToken` IAM (pour revocation optimisée) |
|
|
192
|
+
|
|
193
|
+
### ⛔ `clearAuthToken()` est déprécié
|
|
194
|
+
|
|
195
|
+
`clearAuthToken()` ne fait que vider le localStorage **sans révoquer les sessions** côté SaaS et IAM. Son utilisation seule crée des sessions orphelines.
|
|
196
|
+
|
|
197
|
+
```tsx
|
|
198
|
+
// ❌ NE PAS FAIRE — sessions orphelines sur l'IAM
|
|
199
|
+
import { clearAuthToken } from '@ollaid/native-sso';
|
|
200
|
+
clearAuthToken();
|
|
201
|
+
|
|
202
|
+
// ✅ FAIRE — double revocation garantie
|
|
203
|
+
import { logout } from '@ollaid/native-sso';
|
|
204
|
+
await logout();
|
|
205
|
+
```
|
|
181
206
|
|
|
182
|
-
> **Important :** Si vous ne
|
|
207
|
+
> **Important :** Si vous ne déconnectez pas proprement, l'utilisateur verra l'écran "Déconnexion" au lieu du formulaire de connexion en revenant sur la page SSO.
|
|
183
208
|
|
|
184
209
|
---
|
|
185
210
|
|
|
@@ -334,13 +359,14 @@ class NativeConfigController extends Controller
|
|
|
334
359
|
$payload = json_encode(['secret_key' => $secretKey, 'ts' => time()]);
|
|
335
360
|
$key = hash('sha256', $secretKey, true);
|
|
336
361
|
$iv = random_bytes(16);
|
|
337
|
-
$encrypted = openssl_encrypt($payload, 'AES-256-CBC', $key,
|
|
338
|
-
$encryptedCredentials = base64_encode($iv . '::' . $encrypted);
|
|
362
|
+
$encrypted = openssl_encrypt($payload, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
|
|
363
|
+
$encryptedCredentials = base64_encode($iv . '::' . base64_encode($encrypted));
|
|
339
364
|
|
|
340
365
|
return response()->json([
|
|
341
366
|
'success' => true,
|
|
342
367
|
'app_key' => $appKey,
|
|
343
368
|
'encrypted_credentials' => $encryptedCredentials,
|
|
369
|
+
'iam_api_url' => config("services.{$prefix}.api_url", 'https://identityam.ollaid.com/api'),
|
|
344
370
|
'credentials_ttl' => 300,
|
|
345
371
|
'debug' => $debug,
|
|
346
372
|
]);
|
|
@@ -707,6 +733,7 @@ $secretKey = $payload['secret_key'];
|
|
|
707
733
|
"success": true,
|
|
708
734
|
"token": "1|abc123def456ghi789...",
|
|
709
735
|
"expires_at": "2026-04-23T03:12:32.000000Z",
|
|
736
|
+
"app_access_token_ref": "aat_ref_XXXXXXXX",
|
|
710
737
|
"user": {
|
|
711
738
|
"id": 1,
|
|
712
739
|
"reference": "USR-XXXXXXXX",
|
|
@@ -766,11 +793,15 @@ Route::post('/native/exchange', function (Request $request) {
|
|
|
766
793
|
// Générer un token Sanctum
|
|
767
794
|
$token = $user->createToken('native-sso')->plainTextToken;
|
|
768
795
|
|
|
796
|
+
// Récupérer app_access_token_ref depuis la réponse IAM
|
|
797
|
+
$appAccessTokenRef = $response->json('user_infos.app_access_token_ref');
|
|
798
|
+
|
|
769
799
|
return response()->json([
|
|
770
|
-
'success'
|
|
771
|
-
'token'
|
|
772
|
-
'expires_at'
|
|
773
|
-
'
|
|
800
|
+
'success' => true,
|
|
801
|
+
'token' => $token,
|
|
802
|
+
'expires_at' => now()->addDays(30)->toISOString(),
|
|
803
|
+
'app_access_token_ref' => $appAccessTokenRef,
|
|
804
|
+
'user' => [
|
|
774
805
|
'id' => $user->id,
|
|
775
806
|
'reference' => $user->reference,
|
|
776
807
|
'alias_reference' => $user->alias_reference,
|
|
@@ -790,16 +821,15 @@ Route::post('/native/exchange', function (Request $request) {
|
|
|
790
821
|
|
|
791
822
|
### `POST /api/native/check-token`
|
|
792
823
|
|
|
793
|
-
Vérifie la validité du token Sanctum et retourne les
|
|
824
|
+
Vérifie la validité du token Sanctum et retourne les infos utilisateur fraîches. Appelé périodiquement par le package (60 secondes après login, puis toutes les 2 minutes).
|
|
794
825
|
|
|
795
826
|
**Headers requis :** `Authorization: Bearer {token}`
|
|
796
827
|
|
|
797
828
|
**Réponse succès (200) :**
|
|
798
829
|
```json
|
|
799
830
|
{
|
|
800
|
-
"
|
|
801
|
-
"
|
|
802
|
-
"user_infos": {
|
|
831
|
+
"status": "connected",
|
|
832
|
+
"user": {
|
|
803
833
|
"name": "John Doe",
|
|
804
834
|
"email": "john@example.com",
|
|
805
835
|
"ccphone": "+221",
|
|
@@ -821,9 +851,8 @@ Route::post('/native/check-token', function (Request $request) {
|
|
|
821
851
|
$user = $request->user();
|
|
822
852
|
|
|
823
853
|
return response()->json([
|
|
824
|
-
'
|
|
825
|
-
'
|
|
826
|
-
'user_infos' => [
|
|
854
|
+
'status' => 'connected',
|
|
855
|
+
'user' => [
|
|
827
856
|
'name' => $user->name,
|
|
828
857
|
'email' => $user->email,
|
|
829
858
|
'ccphone' => $user->ccphone,
|
|
@@ -916,17 +945,19 @@ class NativeAuthController extends Controller
|
|
|
916
945
|
$aesKey = hash('sha256', $secretKey, true);
|
|
917
946
|
$iv = random_bytes(16);
|
|
918
947
|
$payload = json_encode([
|
|
919
|
-
'app_key' => $appKey,
|
|
920
948
|
'secret_key' => $secretKey,
|
|
921
949
|
'ts' => time(),
|
|
922
950
|
]);
|
|
923
951
|
|
|
924
|
-
$encrypted = openssl_encrypt($payload, 'aes-256-cbc', $aesKey,
|
|
952
|
+
$encrypted = openssl_encrypt($payload, 'aes-256-cbc', $aesKey, OPENSSL_RAW_DATA, $iv);
|
|
953
|
+
$encryptedCredentials = base64_encode($iv . '::' . base64_encode($encrypted));
|
|
925
954
|
|
|
926
955
|
return response()->json([
|
|
927
956
|
'success' => true,
|
|
928
957
|
'app_key' => $appKey, // En clair (non sensible, sert d'identifiant pour l'IAM)
|
|
929
|
-
'encrypted_credentials' =>
|
|
958
|
+
'encrypted_credentials' => $encryptedCredentials,
|
|
959
|
+
'iam_api_url' => config('services.iam.api_url', 'https://identityam.ollaid.com/api'),
|
|
960
|
+
'credentials_ttl' => 300,
|
|
930
961
|
'debug' => (bool) config('services.iam.debug'), // Contrôlé par IAM_DEBUG dans .env
|
|
931
962
|
]);
|
|
932
963
|
}
|
|
@@ -955,18 +986,17 @@ class NativeAuthController extends Controller
|
|
|
955
986
|
], 400);
|
|
956
987
|
}
|
|
957
988
|
|
|
958
|
-
// ⚠️ IMPORTANT : Remplacer les espaces par '+' (encodage URL)
|
|
959
|
-
$callbackToken = str_replace(' ', '+', $callbackToken);
|
|
960
|
-
|
|
961
989
|
try {
|
|
962
|
-
$response = Http::timeout(30)
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
'
|
|
966
|
-
'
|
|
967
|
-
'
|
|
968
|
-
]
|
|
969
|
-
|
|
990
|
+
$response = Http::timeout(30)
|
|
991
|
+
->withHeaders([
|
|
992
|
+
'Content-Type' => 'application/json',
|
|
993
|
+
'Accept' => 'application/json',
|
|
994
|
+
'X-IAM-App-Key' => config('services.iam.app_key'),
|
|
995
|
+
'X-IAM-Secret-Key' => config('services.iam.secret_key'),
|
|
996
|
+
])
|
|
997
|
+
->post(config('services.iam.api_url') . '/iam/auth/decrypt', [
|
|
998
|
+
'token' => $callbackToken,
|
|
999
|
+
]);
|
|
970
1000
|
|
|
971
1001
|
if (!$response->successful() || !$response->json('success')) {
|
|
972
1002
|
$error = $response->json();
|
|
@@ -1024,11 +1054,22 @@ class NativeAuthController extends Controller
|
|
|
1024
1054
|
$expiresAt = now()->addDays(30);
|
|
1025
1055
|
$token = $user->createToken('native-sso', ['*'], $expiresAt);
|
|
1026
1056
|
|
|
1057
|
+
// Récupérer app_access_token_ref depuis la racine de la réponse IAM
|
|
1058
|
+
$appAccessTokenRef = $data['app_access_token_ref'] ?? null;
|
|
1059
|
+
|
|
1060
|
+
// Stocker la ref dans le token Sanctum pour le webhook de révocation
|
|
1061
|
+
if ($appAccessTokenRef) {
|
|
1062
|
+
$token->accessToken->forceFill([
|
|
1063
|
+
'app_access_token_ref' => $appAccessTokenRef,
|
|
1064
|
+
])->save();
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1027
1067
|
return response()->json([
|
|
1028
|
-
'success'
|
|
1029
|
-
'token'
|
|
1030
|
-
'expires_at'
|
|
1031
|
-
'
|
|
1068
|
+
'success' => true,
|
|
1069
|
+
'token' => $token->plainTextToken,
|
|
1070
|
+
'expires_at' => $expiresAt->toIso8601String(),
|
|
1071
|
+
'app_access_token_ref' => $appAccessTokenRef,
|
|
1072
|
+
'user' => [
|
|
1032
1073
|
'id' => $user->id,
|
|
1033
1074
|
'reference' => $iamReference,
|
|
1034
1075
|
'alias_reference' => $aliasReference,
|
|
@@ -1083,9 +1124,9 @@ class NativeAuthController extends Controller
|
|
|
1083
1124
|
$user = $request->user();
|
|
1084
1125
|
|
|
1085
1126
|
return response()->json([
|
|
1086
|
-
'
|
|
1087
|
-
'
|
|
1088
|
-
'
|
|
1127
|
+
'status' => 'connected',
|
|
1128
|
+
'message' => 'Utilisateur connecté',
|
|
1129
|
+
'user' => [
|
|
1089
1130
|
'name' => $user->name,
|
|
1090
1131
|
'email' => $user->email,
|
|
1091
1132
|
'ccphone' => $user->ccphone,
|
|
@@ -1099,21 +1140,46 @@ class NativeAuthController extends Controller
|
|
|
1099
1140
|
}
|
|
1100
1141
|
|
|
1101
1142
|
// ════════════════════════════════════════
|
|
1102
|
-
// POST /api/native/logout
|
|
1143
|
+
// POST /api/native/logout (déconnexion synchronisée)
|
|
1103
1144
|
// ════════════════════════════════════════
|
|
1104
1145
|
|
|
1105
1146
|
/**
|
|
1106
|
-
* Révoque
|
|
1147
|
+
* Révoque le token Sanctum courant (single-session)
|
|
1148
|
+
* ET notifie l'IAM pour révoquer l'AppAccessToken lié.
|
|
1107
1149
|
* Route protégée par auth:sanctum.
|
|
1108
1150
|
*/
|
|
1109
1151
|
public function logout(Request $request): JsonResponse
|
|
1110
1152
|
{
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1153
|
+
try {
|
|
1154
|
+
$user = $request->user();
|
|
1155
|
+
|
|
1156
|
+
if ($user) {
|
|
1157
|
+
$sanctumTokenPlain = $request->bearerToken();
|
|
1158
|
+
$currentToken = $user->currentAccessToken();
|
|
1159
|
+
$appAccessTokenRef = $currentToken?->app_access_token_ref ?? null;
|
|
1160
|
+
|
|
1161
|
+
// Supprimer le token APRÈS avoir lu la ref
|
|
1162
|
+
$currentToken?->delete();
|
|
1163
|
+
|
|
1164
|
+
// Notifier l'IAM (fire-and-forget, timeout 5s)
|
|
1165
|
+
if ($sanctumTokenPlain || $appAccessTokenRef) {
|
|
1166
|
+
$iamPrefix = $request->attributes->get('iam_prefix', 'iam');
|
|
1167
|
+
$iamApiUrl = config("services.{$iamPrefix}.api_url", 'https://identityam.ollaid.com/api');
|
|
1168
|
+
try {
|
|
1169
|
+
Http::timeout(5)->post("{$iamApiUrl}/iam/disconnect", array_filter([
|
|
1170
|
+
'sanctum_token' => $sanctumTokenPlain,
|
|
1171
|
+
'app_access_token_ref' => $appAccessTokenRef,
|
|
1172
|
+
]));
|
|
1173
|
+
} catch (\Exception $e) {
|
|
1174
|
+
Log::warning("[NativeSSO] IAM disconnect failed (non-blocking)", ['error' => $e->getMessage()]);
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
return response()->json(['success' => true, 'message' => 'Déconnexion réussie']);
|
|
1180
|
+
} catch (\Exception $e) {
|
|
1181
|
+
return response()->json(['success' => true, 'message' => 'Déconnexion effectuée']);
|
|
1182
|
+
}
|
|
1117
1183
|
}
|
|
1118
1184
|
}
|
|
1119
1185
|
```
|
|
@@ -1830,7 +1896,7 @@ Toutes les APIs IAM retournent le même objet `user_infos` avec exactement **9 c
|
|
|
1830
1896
|
|
|
1831
1897
|
## Session & localStorage
|
|
1832
1898
|
|
|
1833
|
-
Le package utilise **
|
|
1899
|
+
Le package utilise **6 clés** dans `localStorage` pour persister la session :
|
|
1834
1900
|
|
|
1835
1901
|
| Clé | Contenu | Source | Valeurs possibles |
|
|
1836
1902
|
|-----|---------|--------|-------------------|
|
|
@@ -1839,6 +1905,7 @@ Le package utilise **5 clés** dans `localStorage` pour persister la session :
|
|
|
1839
1905
|
| `user` | Objet utilisateur enrichi sérialisé en JSON | Réponse de `/api/native/exchange` ou mise à jour via health check | `{"iam_reference":"USR-XXX","alias_reference":"ALI-XXX","name":"...","email":"...",...}` |
|
|
1840
1906
|
| `account_type` | Type de compte | Déterminé lors du `exchange` selon le mode d'inscription | `"user"` (défaut) ou `"client"` (inscription phone-only) |
|
|
1841
1907
|
| `alias_reference` | Référence alias utilisée lors de la connexion | Réponse de `/api/native/exchange` (champ `user.alias_reference`) | `"ALI-XXXXXXXX"` |
|
|
1908
|
+
| `app_access_token_ref` | Référence de l'`AppAccessToken` IAM | Réponse de `/api/native/exchange` (champ `app_access_token_ref`) | `"42"` ou `"aat_ref_abc123"` |
|
|
1842
1909
|
|
|
1843
1910
|
> **Note :** `account_type` et `alias_reference` sont stockés **séparément** de l'objet `user` en plus d'être inclus dans le JSON de la clé `user`.
|
|
1844
1911
|
|
|
@@ -1850,7 +1917,7 @@ L'objet `user` stocké en localStorage contient deux champs ajoutés automatique
|
|
|
1850
1917
|
|
|
1851
1918
|
### Nettoyage
|
|
1852
1919
|
|
|
1853
|
-
Lors du `logout()`, les
|
|
1920
|
+
Lors du `logout()`, les 6 clés sont supprimées via `clearAuthToken()`.
|
|
1854
1921
|
|
|
1855
1922
|
### Accès programmatique
|
|
1856
1923
|
|
|
@@ -1865,7 +1932,127 @@ const aliasRef = localStorage.getItem('alias_reference'); // string | null
|
|
|
1865
1932
|
|
|
1866
1933
|
---
|
|
1867
1934
|
|
|
1868
|
-
##
|
|
1935
|
+
## Déconnexion synchronisée
|
|
1936
|
+
|
|
1937
|
+
Le package implémente une **double revocation** ultra-fiable : il contacte **en parallèle** le SaaS **et** l'IAM pour garantir la révocation de toutes les sessions.
|
|
1938
|
+
|
|
1939
|
+
### Architecture — Double Revocation
|
|
1940
|
+
|
|
1941
|
+
```
|
|
1942
|
+
┌─────────────────┐
|
|
1943
|
+
│ Package │──────────────────────────────────┐
|
|
1944
|
+
│ logout() │ │
|
|
1945
|
+
└──────┬──────────┘ │
|
|
1946
|
+
│ ① POST /native/logout │ ② POST /iam/disconnect
|
|
1947
|
+
│ (Sanctum token) │ (sanctum_token + app_access_token_ref)
|
|
1948
|
+
▼ ▼
|
|
1949
|
+
┌─────────────────┐ ┌─────────────────┐
|
|
1950
|
+
│ SaaS API │──③ fire-and-forget──────▶│ IAM API │
|
|
1951
|
+
│ │ POST /iam/disconnect │ │
|
|
1952
|
+
└─────────────────┘ └─────────────────┘
|
|
1953
|
+
```
|
|
1954
|
+
|
|
1955
|
+
**Trois niveaux de sécurité :**
|
|
1956
|
+
|
|
1957
|
+
| Scénario | Résultat |
|
|
1958
|
+
|----------|----------|
|
|
1959
|
+
| ✅ Tout fonctionne | SaaS + IAM révoquent tous les deux |
|
|
1960
|
+
| ⚠️ SaaS injoignable | L'IAM révoque quand même via l'appel direct ② |
|
|
1961
|
+
| ⚠️ IAM injoignable | Le SaaS révoque via ① puis retente via ③ |
|
|
1962
|
+
| ❌ Les deux injoignables | localStorage nettoyé, le health check détectera le 401 plus tard |
|
|
1963
|
+
|
|
1964
|
+
### Comportement automatique
|
|
1965
|
+
|
|
1966
|
+
Quand `useNativeAuth.logout()` est appelé (ou que l'utilisateur clique "Se déconnecter" dans `NativeSSOPage`) :
|
|
1967
|
+
|
|
1968
|
+
1. **Appels parallèles** (via `Promise.allSettled`, jamais bloquants) :
|
|
1969
|
+
- `POST /api/native/logout` → SaaS supprime le Sanctum token + notifie l'IAM (fire-and-forget)
|
|
1970
|
+
- `POST /api/iam/disconnect` → IAM révoque directement l'`AppAccessToken` via `sanctum_token` + `app_access_token_ref` (lookup optimisé)
|
|
1971
|
+
2. **Nettoyage local garanti** : les 6 clés localStorage sont supprimées (`token`, `auth_token`, `user`, `account_type`, `alias_reference`, `app_access_token_ref`)
|
|
1972
|
+
3. **Aucun blocage** : même si les deux appels échouent (offline, timeout), la déconnexion locale est instantanée
|
|
1973
|
+
|
|
1974
|
+
> **Fiabilité** : `Promise.allSettled` + `.catch()` sur chaque appel. L'appel IAM a un timeout court (5s) pour ne jamais ralentir l'UX.
|
|
1975
|
+
|
|
1976
|
+
### API IAM de revocation — `POST /api/iam/disconnect`
|
|
1977
|
+
|
|
1978
|
+
Le package contacte directement l'IAM pour révoquer l'`AppAccessToken` lié à la session.
|
|
1979
|
+
|
|
1980
|
+
| Paramètre | Type | Description |
|
|
1981
|
+
|-----------|------|-------------|
|
|
1982
|
+
| `sanctum_token` | `string` | Le token Sanctum stocké localement, utilisé pour identifier l'`AppAccessToken` à révoquer |
|
|
1983
|
+
| `app_access_token_ref` | `string` | (recommandé) Référence directe de l'`AppAccessToken` IAM — lookup instantané par PK |
|
|
1984
|
+
|
|
1985
|
+
L'IAM cherche d'abord par `app_access_token_ref` (lookup par PK, O(1)), puis fallback sur `sanctum_token` (index), puis `iam_token` (hash). Le passage de `app_access_token_ref` est **recommandé** pour des performances optimales.
|
|
1986
|
+
|
|
1987
|
+
### Déconnexion externe (hors package)
|
|
1988
|
+
|
|
1989
|
+
Si votre application gère une déconnexion **en dehors** de `NativeSSOPage` (ex : backoffice, bouton custom) :
|
|
1990
|
+
|
|
1991
|
+
> ⚠️ **OBLIGATOIRE** : utilisez `logout()` — jamais `clearAuthToken()` seul.
|
|
1992
|
+
|
|
1993
|
+
```ts
|
|
1994
|
+
import { logout } from '@ollaid/native-sso';
|
|
1995
|
+
|
|
1996
|
+
// Double revocation complète (SaaS + IAM) + nettoyage localStorage
|
|
1997
|
+
await logout();
|
|
1998
|
+
|
|
1999
|
+
// Rediriger vers la page de connexion
|
|
2000
|
+
navigate('/auth/login');
|
|
2001
|
+
```
|
|
2002
|
+
|
|
2003
|
+
`logout()` effectue automatiquement :
|
|
2004
|
+
1. `POST /api/native/logout` → révoque le Sanctum token
|
|
2005
|
+
2. `POST /api/iam/disconnect` → révoque l'AppAccessToken IAM (avec `sanctum_token` + `app_access_token_ref`)
|
|
2006
|
+
3. `clearAuthToken()` → nettoie les 6 clés localStorage
|
|
2007
|
+
|
|
2008
|
+
### Hook `useLogout()` (recommandé pour React)
|
|
2009
|
+
|
|
2010
|
+
Pour une intégration React avec gestion d'état (loading, error) et callbacks :
|
|
2011
|
+
|
|
2012
|
+
```tsx
|
|
2013
|
+
import { useLogout } from '@ollaid/native-sso';
|
|
2014
|
+
import { useNavigate } from 'react-router-dom';
|
|
2015
|
+
|
|
2016
|
+
const LogoutButton = () => {
|
|
2017
|
+
const navigate = useNavigate();
|
|
2018
|
+
const { logout, loading, error } = useLogout({
|
|
2019
|
+
onSuccess: () => navigate('/auth/login'),
|
|
2020
|
+
onError: (err) => console.error('Logout failed:', err.message),
|
|
2021
|
+
});
|
|
2022
|
+
|
|
2023
|
+
return (
|
|
2024
|
+
<>
|
|
2025
|
+
<button onClick={logout} disabled={loading}>
|
|
2026
|
+
{loading ? 'Déconnexion...' : 'Se déconnecter'}
|
|
2027
|
+
</button>
|
|
2028
|
+
{error && <p className="text-red-500">{error}</p>}
|
|
2029
|
+
</>
|
|
2030
|
+
);
|
|
2031
|
+
};
|
|
2032
|
+
```
|
|
2033
|
+
|
|
2034
|
+
| Propriété | Type | Description |
|
|
2035
|
+
|-----------|------|-------------|
|
|
2036
|
+
| **Options** | | |
|
|
2037
|
+
| `onSuccess` | `() => void` | Appelé après déconnexion réussie (redirection, toast, etc.) |
|
|
2038
|
+
| `onError` | `(error: Error) => void` | Appelé en cas d'échec |
|
|
2039
|
+
| **Retour** | | |
|
|
2040
|
+
| `logout` | `() => Promise<void>` | Déclenche la double revocation complète |
|
|
2041
|
+
| `loading` | `boolean` | `true` pendant l'appel |
|
|
2042
|
+
| `error` | `string \| null` | Message d'erreur ou `null` |
|
|
2043
|
+
|
|
2044
|
+
### Détection automatique des sessions révoquées
|
|
2045
|
+
|
|
2046
|
+
Le [health check](#usetokenhealthcheck) (toutes les 2 min) détecte automatiquement si un token a été révoqué côté SaaS (par un admin, par la limite de sessions, etc.). Si le serveur répond `401`, le package **révoque aussi l'IAM** (`POST /api/iam/disconnect` avec `sanctum_token` + `app_access_token_ref`) puis déconnecte proprement l'utilisateur.
|
|
2047
|
+
|
|
2048
|
+
> **Important** : seul un `401` explicite déclenche la déconnexion automatique. Les erreurs réseau ou serveur (5xx) ne déclenchent **pas** de déconnexion pour éviter les déconnexions intempestives.
|
|
2049
|
+
|
|
2050
|
+
### Prérequis backend
|
|
2051
|
+
|
|
2052
|
+
1. Votre endpoint `POST /api/native/logout` **doit** notifier l'IAM après avoir supprimé le token Sanctum (voir [BACKEND_INTEGRATION.md](./BACKEND_INTEGRATION.md))
|
|
2053
|
+
2. L'IAM **doit** exposer `POST /api/iam/disconnect` acceptant `{ sanctum_token, app_access_token_ref }` pour la revocation directe par le package
|
|
2054
|
+
|
|
2055
|
+
---
|
|
1869
2056
|
|
|
1870
2057
|
Modal post-connexion qui invite l'utilisateur à compléter les informations manquantes de son profil.
|
|
1871
2058
|
|
|
@@ -1928,42 +2115,55 @@ import { OnboardingModal } from '@ollaid/native-sso';
|
|
|
1928
2115
|
|
|
1929
2116
|
## useTokenHealthCheck
|
|
1930
2117
|
|
|
1931
|
-
Hook qui vérifie périodiquement la validité du token Sanctum via `POST /api/native/check-token
|
|
2118
|
+
Hook qui vérifie périodiquement la validité du token Sanctum via `POST /api/native/check-token` du SaaS.
|
|
2119
|
+
|
|
2120
|
+
**C'est le package qui gère tout automatiquement** — le SaaS doit juste exposer l'endpoint auth check (voir [BACKEND_INTEGRATION.md](./BACKEND_INTEGRATION.md)).
|
|
1932
2121
|
|
|
1933
2122
|
### Timing
|
|
1934
2123
|
|
|
1935
2124
|
| Événement | Délai |
|
|
1936
2125
|
|-----------|-------|
|
|
1937
|
-
| Premier check après login | **
|
|
1938
|
-
| Checks suivants | **toutes les
|
|
1939
|
-
| Requêtes par heure | ~
|
|
2126
|
+
| Premier check après login | **60 secondes** |
|
|
2127
|
+
| Checks suivants | **toutes les 2 minutes** |
|
|
2128
|
+
| Requêtes par heure | ~30 |
|
|
1940
2129
|
|
|
1941
2130
|
### Comportement réseau
|
|
1942
2131
|
|
|
1943
2132
|
| Situation | Action |
|
|
1944
2133
|
|-----------|--------|
|
|
1945
|
-
| ✅ 200
|
|
1946
|
-
| ❌ 401
|
|
2134
|
+
| ✅ 200 `status: 'connected'` | Met à jour `user_infos` en localStorage |
|
|
2135
|
+
| ❌ 401 `status: 'disconnected'` | Révoque l'IAM + déconnecte le frontend |
|
|
1947
2136
|
| ⚠️ Erreur réseau / timeout / 500 | **Aucune action** — la session est conservée |
|
|
1948
2137
|
| 📴 Appareil hors ligne | Le check échoue silencieusement, session conservée |
|
|
1949
2138
|
|
|
2139
|
+
### Revocation automatique sur 401
|
|
2140
|
+
|
|
2141
|
+
Quand le SaaS retourne un 401 (token révoqué par un admin, session expirée, etc.) :
|
|
2142
|
+
|
|
2143
|
+
1. **Appel IAM** — Le package appelle `POST /api/iam/disconnect` avec `sanctum_token` + `app_access_token_ref` (fire-and-forget, 5s timeout) pour révoquer l'`AppAccessToken` correspondant
|
|
2144
|
+
2. **Nettoyage localStorage** — Les 6 clés de session sont supprimées
|
|
2145
|
+
3. **Réinitialisation de l'état** — L'interface revient à l'écran de connexion
|
|
2146
|
+
|
|
1950
2147
|
> **Philosophie** : Ne déconnecter que sur un rejet explicite du serveur (401). Jamais sur un problème réseau.
|
|
1951
2148
|
|
|
1952
|
-
###
|
|
2149
|
+
### Format de réponse attendu du SaaS
|
|
1953
2150
|
|
|
1954
|
-
```
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
2151
|
+
```json
|
|
2152
|
+
// Connecté (200)
|
|
2153
|
+
{
|
|
2154
|
+
"status": "connected",
|
|
2155
|
+
"user": { "name": "...", "email": "...", "avatar": "..." }
|
|
2156
|
+
}
|
|
2157
|
+
|
|
2158
|
+
// Déconnecté (401)
|
|
2159
|
+
{
|
|
2160
|
+
"status": "disconnected",
|
|
2161
|
+
"message": "Utilisateur non connecté"
|
|
2162
|
+
}
|
|
1965
2163
|
```
|
|
1966
2164
|
|
|
2165
|
+
> Voir [BACKEND_INTEGRATION.md — Section 2.3](./BACKEND_INTEGRATION.md) pour l'implémentation PHP complète.
|
|
2166
|
+
|
|
1967
2167
|
---
|
|
1968
2168
|
|
|
1969
2169
|
## Sécurité
|
|
@@ -2035,6 +2235,7 @@ if (config('services.iam.debug')) {
|
|
|
2035
2235
|
- `useMobilePassword` — Hook récupération mot de passe
|
|
2036
2236
|
- `useMobileRegistration` — Hook inscription
|
|
2037
2237
|
- `useTokenHealthCheck` — Hook vérification périodique du token
|
|
2238
|
+
- `useLogout` — Hook déconnexion sécurisée avec callbacks
|
|
2038
2239
|
|
|
2039
2240
|
### Provider
|
|
2040
2241
|
- `NativeSSOProvider` — Provider React pour configuration centralisée
|
|
@@ -2045,6 +2246,7 @@ if (config('services.iam.debug')) {
|
|
|
2045
2246
|
- `mobilePasswordService` — Service mot de passe
|
|
2046
2247
|
- `setNativeAuthConfig` — Configuration manuelle des URLs
|
|
2047
2248
|
- `iamAccountService` — Service APIs IAM Account (link-phone, link-email, refresh-user-info, update-avatar, reset-avatar)
|
|
2249
|
+
- `logout` — Déconnexion complète (double révocation SaaS + IAM + nettoyage localStorage)
|
|
2048
2250
|
- `getAuthToken` — Récupérer le token depuis localStorage
|
|
2049
2251
|
- `getAuthUser` — Récupérer l'utilisateur depuis localStorage
|
|
2050
2252
|
- `getAccountType` — Récupérer le type de compte depuis localStorage
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook React pour la déconnexion sécurisée.
|
|
3
|
+
*
|
|
4
|
+
* Wrape `logout()` avec gestion d'état (loading, error)
|
|
5
|
+
* et callbacks (onSuccess, onError) pour une intégration UX simple.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* import { useLogout } from '@ollaid/native-sso';
|
|
10
|
+
*
|
|
11
|
+
* const LogoutButton = () => {
|
|
12
|
+
* const { logout, loading, error } = useLogout({
|
|
13
|
+
* onSuccess: () => navigate('/auth/login'),
|
|
14
|
+
* onError: (err) => toast.error(err.message),
|
|
15
|
+
* });
|
|
16
|
+
*
|
|
17
|
+
* return (
|
|
18
|
+
* <button onClick={logout} disabled={loading}>
|
|
19
|
+
* {loading ? 'Déconnexion...' : 'Se déconnecter'}
|
|
20
|
+
* </button>
|
|
21
|
+
* );
|
|
22
|
+
* };
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @version 1.0.0
|
|
26
|
+
*/
|
|
27
|
+
export interface UseLogoutOptions {
|
|
28
|
+
/** Callback appelé après une déconnexion réussie (redirection, toast, etc.) */
|
|
29
|
+
onSuccess?: () => void;
|
|
30
|
+
/** Callback appelé en cas d'erreur (notification, log, etc.) */
|
|
31
|
+
onError?: (error: Error) => void;
|
|
32
|
+
}
|
|
33
|
+
export interface UseLogoutReturn {
|
|
34
|
+
/** Déclenche la déconnexion complète (double revocation SaaS + IAM) */
|
|
35
|
+
logout: () => Promise<void>;
|
|
36
|
+
/** `true` pendant l'appel de déconnexion */
|
|
37
|
+
loading: boolean;
|
|
38
|
+
/** Message d'erreur si la déconnexion a échoué, `null` sinon */
|
|
39
|
+
error: string | null;
|
|
40
|
+
}
|
|
41
|
+
export declare const useLogout: (options?: UseLogoutOptions) => UseLogoutReturn;
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Hook de vérification périodique du token Sanctum
|
|
2
|
+
* Hook de vérification périodique du token Sanctum (Auth Check)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Le package consulte l'endpoint POST /api/native/check-token du SaaS
|
|
5
|
+
* pour vérifier si l'utilisateur est toujours connecté.
|
|
6
|
+
*
|
|
7
|
+
* - Premier check 60s après login
|
|
8
|
+
* - Checks suivants toutes les 2 min
|
|
9
|
+
* - Si status === 'connected' → met à jour user_infos en localStorage
|
|
10
|
+
* - Si 401 → révoque l'IAM (POST /iam/disconnect) + nettoie le frontend
|
|
6
11
|
* - Ne déconnecte PAS si offline ou serveur inaccessible
|
|
7
|
-
* - Déconnecte UNIQUEMENT si le backend retourne 401 (token invalide)
|
|
8
12
|
*
|
|
9
|
-
* @version
|
|
13
|
+
* @version 2.0.0
|
|
10
14
|
*/
|
|
11
15
|
import type { UserInfos } from '../types/native';
|
|
12
16
|
export interface UseTokenHealthCheckOptions {
|
|
@@ -14,6 +18,8 @@ export interface UseTokenHealthCheckOptions {
|
|
|
14
18
|
enabled: boolean;
|
|
15
19
|
/** SaaS API base URL */
|
|
16
20
|
saasApiUrl: string;
|
|
21
|
+
/** IAM API base URL (for revocation on 401) */
|
|
22
|
+
iamApiUrl: string;
|
|
17
23
|
/** Called when the backend explicitly invalidates the token (401) */
|
|
18
24
|
onTokenInvalid: () => void;
|
|
19
25
|
/** Called when fresh user_infos are received */
|