@ollaid/native-sso 2.8.1 → 2.8.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/dist/components/LoginModal.d.ts +2 -1
- package/dist/components/NativeSSOPage.d.ts +2 -1
- package/dist/components/SignupModal.d.ts +2 -1
- package/dist/components/ui.d.ts +7 -0
- package/dist/index.cjs +219 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +219 -26
- package/dist/index.js.map +1 -1
- package/docs/10_login-redirect.md +103 -0
- package/docs/11_session-refresh-resilience.md +130 -0
- package/docs/12_login_mode.md +55 -0
- package/docs/13_must_have_working.md +60 -0
- package/docs/14_backend_env_key.md +50 -0
- package/docs/1_quick-start.md +87 -0
- package/docs/2_backend-contract.md +199 -0
- package/docs/3_storage-security.md +54 -0
- package/docs/4_webhooks.md +48 -0
- package/docs/5_iam-account.md +190 -0
- package/docs/6_advanced-services.md +115 -0
- package/docs/7_migration-notes.md +16 -0
- package/docs/8_update_infos.md +139 -0
- package/docs/9_password_magic_link.md +84 -0
- package/docs/README.md +66 -0
- package/package.json +6 -3
- package/scripts/copy-docs.mjs +36 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Login Redirect & Access Flow
|
|
2
|
+
|
|
3
|
+
Version: `2.8.3`
|
|
4
|
+
|
|
5
|
+
Ce document décrit le contrat à respecter pour éviter les deux régressions les plus fréquentes:
|
|
6
|
+
|
|
7
|
+
- rester bloqué sur la page SSO après une connexion réussie
|
|
8
|
+
- afficher une connexion réussie alors que l'utilisateur n'a pas encore d'accès à l'application SaaS
|
|
9
|
+
|
|
10
|
+
## 1) Redirection après connexion
|
|
11
|
+
|
|
12
|
+
La page autonome `NativeSSOPage` doit être utilisée avec un `redirectAfterLogin` lorsque vous voulez renvoyer automatiquement l'utilisateur vers une page de votre SaaS après authentification.
|
|
13
|
+
|
|
14
|
+
Exemple:
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
<NativeSSOPage
|
|
18
|
+
saasApiUrl="https://your-saas.com/api"
|
|
19
|
+
iamApiUrl="https://identityam.ollaid.com/api"
|
|
20
|
+
redirectAfterLogin="https://your-saas.com/app"
|
|
21
|
+
redirectAfterLogout="https://your-saas.com/"
|
|
22
|
+
/>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Règle d'intégration:
|
|
26
|
+
|
|
27
|
+
- si `redirectAfterLogin` est fourni, la page SSO doit rediriger automatiquement dès qu'une session valide existe
|
|
28
|
+
- si `redirectAfterLogin` n'est pas fourni, la page peut rester sur l'écran SSO avec le bouton `Déconnexion`
|
|
29
|
+
- `onLoginSuccess` sert au callback métier du frontend, mais ne remplace pas la redirection
|
|
30
|
+
|
|
31
|
+
Comportement attendu côté page:
|
|
32
|
+
|
|
33
|
+
- connexion réussie -> callback `onLoginSuccess` puis redirection vers `redirectAfterLogin`
|
|
34
|
+
- session déjà valide au chargement -> redirection automatique vers `redirectAfterLogin`
|
|
35
|
+
- déconnexion -> redirection vers `redirectAfterLogout` si ce paramètre est fourni
|
|
36
|
+
|
|
37
|
+
## 2) Contrôle d'accès par application
|
|
38
|
+
|
|
39
|
+
Le fait d'avoir un compte IAM ne signifie pas automatiquement que l'utilisateur a accès à chaque SaaS.
|
|
40
|
+
|
|
41
|
+
Flux attendu:
|
|
42
|
+
|
|
43
|
+
1. l'utilisateur saisit son email ou son téléphone
|
|
44
|
+
2. il passe l'authentification IAM
|
|
45
|
+
3. le backend vérifie s'il possède déjà un `AppAccess` pour cette application
|
|
46
|
+
4. si oui, il renvoie une connexion normale
|
|
47
|
+
5. si non, il renvoie `needs_access`
|
|
48
|
+
6. le package affiche alors le modal de confirmation d'accès
|
|
49
|
+
7. l'utilisateur confirme
|
|
50
|
+
8. seulement à ce moment-là la connexion peut être finalisée
|
|
51
|
+
|
|
52
|
+
Réponse attendue quand l'accès n'existe pas encore:
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"success": true,
|
|
57
|
+
"needs_access": true,
|
|
58
|
+
"process_token": "pst_xxx",
|
|
59
|
+
"application": {
|
|
60
|
+
"id": 12,
|
|
61
|
+
"name": "Mon SaaS",
|
|
62
|
+
"logo": "https://..."
|
|
63
|
+
},
|
|
64
|
+
"user": {
|
|
65
|
+
"id": 42,
|
|
66
|
+
"name": "John Doe",
|
|
67
|
+
"email": "john@example.com"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Règles backend:
|
|
73
|
+
|
|
74
|
+
- `needs_access` est le comportement par défaut quand l'accès à l'application n'existe pas encore
|
|
75
|
+
- `BYPASS=true` sur le SaaS permet au backend d'auto-créer l'accès et de finaliser la connexion sans afficher le modal
|
|
76
|
+
- le backend ne doit pas renvoyer une connexion finale tant que l'accès application n'a pas été validé, sauf si `BYPASS=true`
|
|
77
|
+
|
|
78
|
+
Règles frontend:
|
|
79
|
+
|
|
80
|
+
- si `needs_access` est reçu, afficher le modal de confirmation
|
|
81
|
+
- ne pas appeler le callback de redirection finale avant la confirmation
|
|
82
|
+
- ne pas marquer l'application comme connectée tant que `grantAccess()` n'a pas terminé
|
|
83
|
+
|
|
84
|
+
## 3) Symptôme classique à éviter
|
|
85
|
+
|
|
86
|
+
Si l'utilisateur voit:
|
|
87
|
+
|
|
88
|
+
- `Connexion réussie`
|
|
89
|
+
- puis reste sur la page SSO avec `Déconnexion`
|
|
90
|
+
|
|
91
|
+
alors l'intégration n'a probablement pas fourni `redirectAfterLogin`, ou bien le callback frontend ne redirige pas vers une route métier.
|
|
92
|
+
|
|
93
|
+
Si l'utilisateur se connecte pour la première fois sur un SaaS où il n'a jamais eu d'accès, mais qu'il voit directement `Connexion réussie` sans modal `needs_access` alors que `BYPASS` est désactivé, le backend a probablement validé la session trop tôt.
|
|
94
|
+
|
|
95
|
+
## 4) Contrat à respecter
|
|
96
|
+
|
|
97
|
+
Le package peut gérer l'UI, mais l'intégrateur SaaS doit fournir:
|
|
98
|
+
|
|
99
|
+
- `redirectAfterLogin`
|
|
100
|
+
- `redirectAfterLogout` si nécessaire
|
|
101
|
+
- un backend qui renvoie `needs_access` quand l'accès à l'application n'existe pas encore, sauf si `BYPASS=true`
|
|
102
|
+
|
|
103
|
+
Pour le détail du format des réponses backend, voir [Backend Contract](./2_backend-contract.md).
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Session Refresh & Resilience
|
|
2
|
+
|
|
3
|
+
Version: `2.8.3`
|
|
4
|
+
|
|
5
|
+
Ce document décrit le contrat de refresh token, la persistance des sessions et la stratégie de résilience attendue pour éviter toute déconnexion inutile en cas de réseau instable ou d'erreur transitoire.
|
|
6
|
+
|
|
7
|
+
## Objectif
|
|
8
|
+
|
|
9
|
+
Le but est simple:
|
|
10
|
+
|
|
11
|
+
- ne pas perdre une session valide à cause d'une coupure réseau temporaire
|
|
12
|
+
- ne pas déconnecter un utilisateur si le backend est indisponible quelques secondes
|
|
13
|
+
- ne considérer une session comme invalide que sur une réponse explicite du backend
|
|
14
|
+
|
|
15
|
+
## Contrat de session
|
|
16
|
+
|
|
17
|
+
Une session Native SSO peut contenir:
|
|
18
|
+
|
|
19
|
+
- `token` ou `auth_token`
|
|
20
|
+
- `expires_at`
|
|
21
|
+
- `refresh_token`
|
|
22
|
+
- `refresh_expires_at`
|
|
23
|
+
- `app_access_token_ref`
|
|
24
|
+
- `user`
|
|
25
|
+
|
|
26
|
+
Le package doit persister ces valeurs quand elles sont présentes.
|
|
27
|
+
|
|
28
|
+
## Contrat du refresh token
|
|
29
|
+
|
|
30
|
+
Le refresh token n'est pas un simple cache local. Il doit être traité comme une référence de session côté SaaS.
|
|
31
|
+
|
|
32
|
+
Le backend SaaS doit:
|
|
33
|
+
|
|
34
|
+
- créer un `refresh_token` au moment de l'exchange
|
|
35
|
+
- persister son hash dans `personal_access_tokens.refresh_token_hash`
|
|
36
|
+
- persister sa date d'expiration dans `personal_access_tokens.refresh_expires_at`
|
|
37
|
+
- retourner le nouveau `refresh_token` à chaque rotation
|
|
38
|
+
|
|
39
|
+
Le package doit:
|
|
40
|
+
|
|
41
|
+
- stocker `refresh_token` et `refresh_expires_at` quand la réponse les fournit
|
|
42
|
+
- utiliser `POST /api/native/refresh` seulement si `refresh_token` existe
|
|
43
|
+
- remplacer le refresh token local si le backend le rote
|
|
44
|
+
|
|
45
|
+
## Résultat attendu au refresh
|
|
46
|
+
|
|
47
|
+
Le backend peut répondre:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"success": true,
|
|
52
|
+
"expires_at": "2026-06-07T12:00:00+00:00",
|
|
53
|
+
"refresh_token": "rt_new_xxx",
|
|
54
|
+
"refresh_expires_at": "2026-08-07T12:00:00+00:00",
|
|
55
|
+
"app_access_token_ref": "aat_ref_xxx",
|
|
56
|
+
"user": {
|
|
57
|
+
"id": 42,
|
|
58
|
+
"name": "John Doe",
|
|
59
|
+
"email": "john@example.com"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Dans ce cas:
|
|
65
|
+
|
|
66
|
+
- la session reste active
|
|
67
|
+
- le package met à jour le token local si nécessaire
|
|
68
|
+
- le refresh token local est remplacé par le nouveau
|
|
69
|
+
|
|
70
|
+
## Réponses d'erreur
|
|
71
|
+
|
|
72
|
+
### `invalid_refresh`
|
|
73
|
+
|
|
74
|
+
Cela signifie:
|
|
75
|
+
|
|
76
|
+
- le refresh token ne correspond à aucune session connue
|
|
77
|
+
- ou il a expiré
|
|
78
|
+
- ou le hash stocké en base n'a jamais été persisté
|
|
79
|
+
- ou le backend a déjà rotaté le refresh token et le client utilise une ancienne valeur
|
|
80
|
+
|
|
81
|
+
Ce cas doit être traité comme une fin de session réelle.
|
|
82
|
+
|
|
83
|
+
### Erreur réseau / timeout / 5xx
|
|
84
|
+
|
|
85
|
+
Cela ne doit **pas** déconnecter immédiatement l'utilisateur.
|
|
86
|
+
|
|
87
|
+
Le bon comportement est:
|
|
88
|
+
|
|
89
|
+
- conserver la session locale
|
|
90
|
+
- garder le token actuel
|
|
91
|
+
- réessayer plus tard
|
|
92
|
+
- ne déconnecter que si le backend renvoie explicitement un échec définitif
|
|
93
|
+
|
|
94
|
+
## Règle de résilience
|
|
95
|
+
|
|
96
|
+
Le package doit différencier:
|
|
97
|
+
|
|
98
|
+
- **échec transitoire**: réseau, timeout, 502, 503, 504
|
|
99
|
+
- **échec définitif**: `invalid_refresh`, `session_expired_idle`, token révoqué, réponse `401` explicite
|
|
100
|
+
|
|
101
|
+
Un échec transitoire ne doit pas vider le storage de session.
|
|
102
|
+
|
|
103
|
+
## Recommandation côté frontend hôte
|
|
104
|
+
|
|
105
|
+
- ne pas appeler `logout()` automatiquement sur une simple erreur réseau
|
|
106
|
+
- ne pas supprimer le storage si `refresh()` échoue sur timeout
|
|
107
|
+
- afficher un état hors ligne ou un avertissement, mais garder l'utilisateur connecté
|
|
108
|
+
- ne forcer la déconnexion qu'après plusieurs échecs définitifs consécutifs ou une réponse `401` claire
|
|
109
|
+
|
|
110
|
+
## Recommandation côté backend SaaS
|
|
111
|
+
|
|
112
|
+
- persister `refresh_token_hash` au moment de l'exchange
|
|
113
|
+
- mettre à jour `refresh_expires_at` à chaque rotation
|
|
114
|
+
- ne pas réutiliser un ancien refresh token après rotation
|
|
115
|
+
- renvoyer toujours le nouveau `refresh_token` si une rotation est faite
|
|
116
|
+
|
|
117
|
+
## Dépannage
|
|
118
|
+
|
|
119
|
+
Si vous voyez `Refresh token invalide ou expiré`:
|
|
120
|
+
|
|
121
|
+
- vérifiez que la table `personal_access_tokens` contient bien `refresh_token_hash`
|
|
122
|
+
- vérifiez que le hash correspond au `refresh_token` stocké côté package
|
|
123
|
+
- vérifiez que `refresh_expires_at` est encore valide
|
|
124
|
+
- vérifiez que le frontend n'utilise pas un token d'une ancienne session
|
|
125
|
+
|
|
126
|
+
Si vous voyez des déconnexions pendant une panne réseau:
|
|
127
|
+
|
|
128
|
+
- vérifiez que votre code n'efface pas la session sur un simple timeout
|
|
129
|
+
- vérifiez que votre logique de refresh ne confond pas `network error` et `invalid_refresh`
|
|
130
|
+
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Needs Access & Bypass
|
|
2
|
+
|
|
3
|
+
Version: `2.8.3`
|
|
4
|
+
|
|
5
|
+
Ce document décrit le comportement attendu quand un utilisateur est authentifié mais n'a pas encore d'accès à une application SaaS.
|
|
6
|
+
|
|
7
|
+
## Règle par défaut
|
|
8
|
+
|
|
9
|
+
Le comportement par défaut doit être `needs_access`.
|
|
10
|
+
|
|
11
|
+
Cela signifie:
|
|
12
|
+
|
|
13
|
+
- si l'utilisateur a bien terminé son authentification IAM mais ne possède pas encore d'accès à l'application ciblée, le backend doit renvoyer `needs_access`
|
|
14
|
+
- le package affiche alors le modal de confirmation
|
|
15
|
+
- la connexion n'est finalisée qu'après `grantAccess()`
|
|
16
|
+
|
|
17
|
+
## `BYPASS=true`
|
|
18
|
+
|
|
19
|
+
Le SaaS peut activer un mode de bypass via son `.env`:
|
|
20
|
+
|
|
21
|
+
```env
|
|
22
|
+
BYPASS=true
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Dans ce cas:
|
|
26
|
+
|
|
27
|
+
- si l'utilisateur n'a pas encore d'accès à l'application, le backend auto-crée cet accès
|
|
28
|
+
- le modal `needs_access` n'est pas affiché
|
|
29
|
+
- la connexion est finalisée immédiatement comme si l'utilisateur avait confirmé manuellement
|
|
30
|
+
|
|
31
|
+
## Valeurs possibles
|
|
32
|
+
|
|
33
|
+
- `BYPASS=true` -> auto-acceptation du flux `needs_access`
|
|
34
|
+
- `BYPASS=false` -> modal `needs_access` obligatoire si l'accès manque
|
|
35
|
+
- `BYPASS` absent -> comportement identique à `false`
|
|
36
|
+
- `BYPASS=null` ou vide -> comportement identique à `false`
|
|
37
|
+
|
|
38
|
+
## Ce que le frontend doit faire
|
|
39
|
+
|
|
40
|
+
- si `needs_access` est reçu, afficher le modal de confirmation
|
|
41
|
+
- si la connexion a déjà été finalisée, rediriger vers la page métier prévue
|
|
42
|
+
- ne pas considérer l'utilisateur comme connecté tant que `grantAccess()` n'a pas terminé
|
|
43
|
+
|
|
44
|
+
## Ce que le backend doit faire
|
|
45
|
+
|
|
46
|
+
- vérifier si l'utilisateur a déjà un `AppAccess`
|
|
47
|
+
- si oui, finaliser la connexion normalement
|
|
48
|
+
- si non et `BYPASS` est désactivé, renvoyer `needs_access`
|
|
49
|
+
- si non et `BYPASS=true`, créer l'accès et finaliser la connexion
|
|
50
|
+
|
|
51
|
+
## Résumé
|
|
52
|
+
|
|
53
|
+
- `needs_access` = comportement standard
|
|
54
|
+
- `BYPASS=true` = auto-acceptation contrôlée par le SaaS
|
|
55
|
+
- le flux natif ne doit plus dépendre d'un mode legacy pour le comportement courant
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Must Have Working
|
|
2
|
+
|
|
3
|
+
Version: `2.8.3`
|
|
4
|
+
|
|
5
|
+
Cette page sert de vérification finale. Si un de ces points manque, l'intégration Native SSO n'est pas considérée comme complète.
|
|
6
|
+
|
|
7
|
+
## 1) Authentification de base
|
|
8
|
+
|
|
9
|
+
- **Connexion email**: l'utilisateur peut se connecter avec son email IAM.
|
|
10
|
+
- **Connexion téléphone**: l'utilisateur peut se connecter avec son numéro IAM.
|
|
11
|
+
- **OTP**: le code de vérification fonctionne quand le flux OTP est utilisé.
|
|
12
|
+
- **Mot de passe**: la validation du mot de passe IAM fonctionne sans bloquer sur un faux état d'accès.
|
|
13
|
+
|
|
14
|
+
## 2) Accès application
|
|
15
|
+
|
|
16
|
+
- **`needs_access`**: état renvoyé quand l'utilisateur est authentifié mais n'a pas encore d'accès à l'application SaaS.
|
|
17
|
+
- **Modal d'accès**: l'application affiche la demande de confirmation quand `needs_access` est reçu.
|
|
18
|
+
- **Création d'accès**: après validation manuelle, l'accès SaaS est créé et la connexion se termine.
|
|
19
|
+
- **`BYPASS=true`**: le SaaS peut auto-valider l'accès manquant et finaliser la connexion sans afficher le modal.
|
|
20
|
+
|
|
21
|
+
## 3) Redirection
|
|
22
|
+
|
|
23
|
+
- **`redirectAfterLogin`**: la page SSO redirige automatiquement après connexion réussie.
|
|
24
|
+
- **`redirectAfterLogout`**: la page SSO redirige vers la page prévue après déconnexion.
|
|
25
|
+
- **Pas de page bloquée**: l'utilisateur ne doit pas rester sur la page SSO avec `Déconnexion` après une connexion réussie.
|
|
26
|
+
|
|
27
|
+
## 4) Session et refresh
|
|
28
|
+
|
|
29
|
+
- **Refresh token**: la session peut être prolongée sans déconnexion inutile.
|
|
30
|
+
- **Résilience réseau**: une erreur réseau ne doit pas supprimer une session valide.
|
|
31
|
+
- **`invalid_refresh`**: ce statut signifie une vraie invalidation du refresh token, pas un simple timeout réseau.
|
|
32
|
+
|
|
33
|
+
## 5) Sécurité et cohérence
|
|
34
|
+
|
|
35
|
+
- **Clés application**: `app_key` et `secret_key` doivent correspondre à l'application ciblée.
|
|
36
|
+
- **`encrypted_credentials`**: le blob chiffré doit suivre le contrat documenté dans `backend-contract.md`.
|
|
37
|
+
- **Signature HMAC**: si l'intégration l'utilise, la signature doit être valide pour les requêtes signées.
|
|
38
|
+
|
|
39
|
+
## 6) Profil SSO
|
|
40
|
+
|
|
41
|
+
- **Onboarding profil**: les infos profil manquantes peuvent être complétées via la modal dédiée.
|
|
42
|
+
- **Synchronisation profil**: les changements de profil doivent remonter correctement entre IAM et SaaS.
|
|
43
|
+
|
|
44
|
+
## 7) Règle de validation finale
|
|
45
|
+
|
|
46
|
+
Une intégration est considérée comme prête si:
|
|
47
|
+
|
|
48
|
+
1. la connexion se fait
|
|
49
|
+
2. `needs_access` apparaît quand il manque un accès
|
|
50
|
+
3. `BYPASS=true` fonctionne quand il est activé
|
|
51
|
+
4. la redirection se fait vers la page métier attendue
|
|
52
|
+
5. la session survit aux erreurs réseau
|
|
53
|
+
6. les infos profil liées au SSO sont cohérentes
|
|
54
|
+
|
|
55
|
+
## Résumé
|
|
56
|
+
|
|
57
|
+
- si le flux `needs_access` marche, la partie contrôle d'accès est bonne
|
|
58
|
+
- si `BYPASS=true` fonctionne, le SaaS peut automatiser l'onboarding d'accès
|
|
59
|
+
- si la session survit au réseau, le refresh token est correctement géré
|
|
60
|
+
- si la redirection est propre, l'expérience utilisateur est complète
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Backend Env Key
|
|
2
|
+
|
|
3
|
+
Version: `2.8.3`
|
|
4
|
+
|
|
5
|
+
Ce document liste les variables d'environnement backend nécessaires pour faire fonctionner le flux Native SSO.
|
|
6
|
+
|
|
7
|
+
## Exemple de configuration
|
|
8
|
+
|
|
9
|
+
```env
|
|
10
|
+
# ==============================================================================
|
|
11
|
+
# IAM SSO CONFIGURATION
|
|
12
|
+
# ==============================================================================
|
|
13
|
+
IAM_API_URL=https://identityam.ollaid.com/api
|
|
14
|
+
IAM_AUTH_URL=https://iam.ollaid.com
|
|
15
|
+
|
|
16
|
+
IAM_APP_KEY=oiam_ak_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
17
|
+
IAM_PUBLIC_KEY=oiam_pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
18
|
+
IAM_SECRET_KEY=oiam_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
19
|
+
IAM_WEBHOOK_SECRET=oiam_whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
20
|
+
IAM_DEBUG=false
|
|
21
|
+
BYPASS=false
|
|
22
|
+
|
|
23
|
+
# ==============================================================================
|
|
24
|
+
# END IAM SSO CONFIGURATION
|
|
25
|
+
# ==============================================================================
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Rôle des variables
|
|
29
|
+
|
|
30
|
+
- `IAM_API_URL`: URL API du backend IAM
|
|
31
|
+
- `IAM_AUTH_URL`: URL d'authentification IAM utilisée par le backend ou les redirections
|
|
32
|
+
- `IAM_APP_KEY`: clé publique d'application utilisée pour identifier le SaaS
|
|
33
|
+
- `IAM_PUBLIC_KEY`: clé publique de chiffrement / vérification selon votre implémentation
|
|
34
|
+
- `IAM_SECRET_KEY`: clé secrète utilisée pour les signatures et validations serveur
|
|
35
|
+
- `IAM_WEBHOOK_SECRET`: secret utilisé pour signer les webhooks
|
|
36
|
+
- `IAM_DEBUG`: active ou désactive les logs de debug IAM
|
|
37
|
+
- `BYPASS`: active l'auto-acceptation du flux `needs_access` quand il vaut `true`
|
|
38
|
+
|
|
39
|
+
## Règles
|
|
40
|
+
|
|
41
|
+
- ne jamais committer les vraies clés dans la documentation
|
|
42
|
+
- utiliser les vraies valeurs uniquement dans le `.env` du projet consommateur
|
|
43
|
+
- `BYPASS=false` ou absent conserve le modal `needs_access`
|
|
44
|
+
- `BYPASS=true` auto-crée l'accès quand il manque
|
|
45
|
+
|
|
46
|
+
## Résumé
|
|
47
|
+
|
|
48
|
+
- ce fichier sert de modèle
|
|
49
|
+
- les valeurs sensibles doivent rester privées
|
|
50
|
+
- `BYPASS` est la seule variable ici qui modifie directement le comportement du flux d'accès
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Quick Start
|
|
2
|
+
|
|
3
|
+
Version: `2.8.3`
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @ollaid/native-sso
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Route de test
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { NativeSSOPage } from '@ollaid/native-sso';
|
|
15
|
+
|
|
16
|
+
<Route
|
|
17
|
+
path="/auth/native-sso"
|
|
18
|
+
element={
|
|
19
|
+
<NativeSSOPage
|
|
20
|
+
saasApiUrl="https://your-saas.com/api"
|
|
21
|
+
iamApiUrl="https://identityam.ollaid.com/api"
|
|
22
|
+
homeUrl="https://your-saas.com/"
|
|
23
|
+
onLoginSuccess={(token, user) => {
|
|
24
|
+
console.log(token, user);
|
|
25
|
+
}}
|
|
26
|
+
/>
|
|
27
|
+
}
|
|
28
|
+
/>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`homeUrl` est optionnel. S'il est fourni, le package affiche un lien "Retourner à l'accueil" sur les écrans racine de connexion et d'inscription. Sans valeur, le lien reste caché.
|
|
32
|
+
|
|
33
|
+
Si vous utilisez `NativeSSOPage`, fournissez aussi `redirectAfterLogin` pour éviter de rester sur la page SSO après une connexion réussie:
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
<NativeSSOPage
|
|
37
|
+
saasApiUrl="https://your-saas.com/api"
|
|
38
|
+
iamApiUrl="https://identityam.ollaid.com/api"
|
|
39
|
+
redirectAfterLogin="https://your-saas.com/app"
|
|
40
|
+
/>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Sans `redirectAfterLogin`, la page peut rester affichée avec le bouton `Déconnexion`, ce qui est normal pour un usage purement autonome.
|
|
44
|
+
|
|
45
|
+
## Côté frontend SaaS
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { getSsoSessionSnapshot, logout } from '@ollaid/native-sso';
|
|
49
|
+
|
|
50
|
+
const session = getSsoSessionSnapshot();
|
|
51
|
+
|
|
52
|
+
if (session.authToken) {
|
|
53
|
+
console.log(session.user);
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Forme du snapshot
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
{
|
|
61
|
+
authToken: string | null;
|
|
62
|
+
refreshToken: string | null;
|
|
63
|
+
refreshExpiresAt: string | null;
|
|
64
|
+
tokenExpiresAt: string | null;
|
|
65
|
+
appAccessTokenRef: string | null;
|
|
66
|
+
aliasReference: string | null;
|
|
67
|
+
accountType: string | null;
|
|
68
|
+
deviceId: string | null;
|
|
69
|
+
sessionUuid: string | null;
|
|
70
|
+
user: unknown | null;
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Déconnexion
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
const result = await logout();
|
|
78
|
+
// { success: true }
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Règles simples
|
|
82
|
+
|
|
83
|
+
- N’accédez pas au storage directement.
|
|
84
|
+
- Utilisez `getSsoSessionSnapshot()` pour lire la session.
|
|
85
|
+
- Utilisez `logout()` pour déconnecter.
|
|
86
|
+
- `clearAuthToken()` reste seulement pour compatibilité.
|
|
87
|
+
- Si vous êtes sur Capacitor, fournissez un `storage` natif si possible.
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Backend Contract
|
|
2
|
+
|
|
3
|
+
Version: `2.8.3`
|
|
4
|
+
|
|
5
|
+
## Endpoints SaaS requis
|
|
6
|
+
|
|
7
|
+
- `GET /api/native/config`
|
|
8
|
+
- `POST /api/native/exchange`
|
|
9
|
+
- `POST /api/native/check-token`
|
|
10
|
+
- `POST /api/native/refresh`
|
|
11
|
+
- `POST /api/native/logout`
|
|
12
|
+
- `POST /api/native/password-link` pour le flux de redirection mot de passe
|
|
13
|
+
|
|
14
|
+
## Exemple rapide
|
|
15
|
+
|
|
16
|
+
### 1) `GET /api/native/config`
|
|
17
|
+
|
|
18
|
+
Réponse:
|
|
19
|
+
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"success": true,
|
|
23
|
+
"app_key": "oiam_ak_xxx",
|
|
24
|
+
"encrypted_credentials": "base64(IV::ciphertext_base64)...",
|
|
25
|
+
"iam_api_url": "https://identityam.ollaid.com/api",
|
|
26
|
+
"credentials_ttl": 300,
|
|
27
|
+
"debug": false
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Format attendu pour `encrypted_credentials`:
|
|
32
|
+
|
|
33
|
+
- chiffrement `AES-256-CBC`
|
|
34
|
+
- IV aléatoire de 16 octets
|
|
35
|
+
- clé dérivée avec `hash('sha256', secret_key, true)`
|
|
36
|
+
- sérialisation finale: `base64(IV::ciphertext_base64)`
|
|
37
|
+
- contenu chiffré: un JSON avec au minimum `app_key` et `secret_key`
|
|
38
|
+
|
|
39
|
+
### 2) `POST /api/native/exchange`
|
|
40
|
+
|
|
41
|
+
Body:
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"callback_token": "cbk_xxx"
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Réponse:
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"success": true,
|
|
54
|
+
"token": "1|sanctum_token",
|
|
55
|
+
"expires_at": "2026-05-15T17:00:00+00:00",
|
|
56
|
+
"refresh_token": "ref_xxx",
|
|
57
|
+
"refresh_expires_at": "2026-06-15T17:00:00+00:00",
|
|
58
|
+
"app_access_token_ref": "aat_ref_xxx",
|
|
59
|
+
"user": {
|
|
60
|
+
"reference": "USR-XXXX",
|
|
61
|
+
"name": "John Doe",
|
|
62
|
+
"email": "john@example.com",
|
|
63
|
+
"alias_reference": "ALI-XXXX"
|
|
64
|
+
},
|
|
65
|
+
"user_infos": {
|
|
66
|
+
"name": "John Doe",
|
|
67
|
+
"email": "john@example.com",
|
|
68
|
+
"ccphone": "+221",
|
|
69
|
+
"phone": "771234567",
|
|
70
|
+
"address": "Rue 10, Plateau",
|
|
71
|
+
"town": "Dakar",
|
|
72
|
+
"country": "SN"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 2 bis) Flux `needs_access` sur une nouvelle application
|
|
78
|
+
|
|
79
|
+
Quand l'utilisateur est bien authentifié côté IAM mais qu'il n'a encore aucun accès sur l'application SaaS ciblée, le backend doit renvoyer un état `needs_access`.
|
|
80
|
+
|
|
81
|
+
Réponse attendue:
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"success": true,
|
|
86
|
+
"needs_access": true,
|
|
87
|
+
"process_token": "pst_xxx",
|
|
88
|
+
"application": {
|
|
89
|
+
"id": 12,
|
|
90
|
+
"name": "Mon SaaS",
|
|
91
|
+
"logo": "https://..."
|
|
92
|
+
},
|
|
93
|
+
"user": {
|
|
94
|
+
"id": 42,
|
|
95
|
+
"name": "John Doe",
|
|
96
|
+
"email": "john@example.com"
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Comportement du package:
|
|
102
|
+
|
|
103
|
+
- il affiche l'écran de confirmation "Confirmer la création de mon compte"
|
|
104
|
+
- il garde le `process_token` pour finaliser l'attribution d'accès
|
|
105
|
+
- il ne termine pas la connexion tant que l'utilisateur n'a pas confirmé
|
|
106
|
+
|
|
107
|
+
Cas de configuration backend:
|
|
108
|
+
|
|
109
|
+
- `BYPASS=true` sur le SaaS -> le backend auto-crée l'accès et finalise la connexion comme si le modal avait été validé
|
|
110
|
+
- `BYPASS` absent, `false` ou vide -> le backend renvoie `needs_access` dès que l'accès n'existe pas encore
|
|
111
|
+
|
|
112
|
+
Le flux natif ne dépend plus du mode legacy d'autorisation pour le comportement standard. Le contrat natif doit être centré sur `needs_access` par défaut.
|
|
113
|
+
|
|
114
|
+
### 3) `POST /api/native/check-token`
|
|
115
|
+
|
|
116
|
+
Réponse `200` si le token est valide:
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
{
|
|
120
|
+
"success": true,
|
|
121
|
+
"user": {
|
|
122
|
+
"name": "John Doe",
|
|
123
|
+
"email": "john@example.com"
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 4) `POST /api/native/refresh`
|
|
129
|
+
|
|
130
|
+
Le package tente ce refresh si `refresh_token` existe et que `check-token` renvoie `401`.
|
|
131
|
+
|
|
132
|
+
### 5) `POST /api/native/logout`
|
|
133
|
+
|
|
134
|
+
Réponse:
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
{ "success": true }
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### 6) `POST /api/native/password-link`
|
|
141
|
+
|
|
142
|
+
Réponse recommandée:
|
|
143
|
+
|
|
144
|
+
```json
|
|
145
|
+
{
|
|
146
|
+
"success": true,
|
|
147
|
+
"redirect_url": "https://identityam.ollaid.com/auth/auto-connect?magic_token=xxx",
|
|
148
|
+
"magic_token": "xxx",
|
|
149
|
+
"expires_at": "2026-05-18T12:00:00+00:00"
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Le backend SaaS doit relayer l'appel vers IAM sur `POST /api/backoffice/auth/password-link`, puis renvoyer au package une URL de redirection prête à ouvrir.
|
|
154
|
+
|
|
155
|
+
## Ce que le package envoie
|
|
156
|
+
|
|
157
|
+
- `X-Device-Id`
|
|
158
|
+
- `X-Session-UUID`
|
|
159
|
+
- `X-Device-Name`
|
|
160
|
+
- `X-Device-Model`
|
|
161
|
+
- `X-Device-Manufacturer`
|
|
162
|
+
- `X-Device-Operating-System`
|
|
163
|
+
- `X-Device-Os-Version`
|
|
164
|
+
- `X-Device-Platform`
|
|
165
|
+
- `X-Device-Browser`
|
|
166
|
+
- `X-Device-Language`
|
|
167
|
+
- `X-Device-WebView-Version`
|
|
168
|
+
- `X-Device-Is-Native`
|
|
169
|
+
- `X-Device-Label`
|
|
170
|
+
|
|
171
|
+
## Ce que le backend doit faire
|
|
172
|
+
|
|
173
|
+
- Répondre en JSON.
|
|
174
|
+
- Gérer `refresh_token` si disponible.
|
|
175
|
+
- Retourner `refresh_token` / `refresh_expires_at` si vous activez le refresh.
|
|
176
|
+
- Faire du `check-token` un signal de validité, avec tentative de refresh côté package après un `401`.
|
|
177
|
+
- Révoquer de façon ciblée via `app_access_token_ref`.
|
|
178
|
+
- Mettre à jour `last_active_at` si vous appliquez la politique d’activité.
|
|
179
|
+
- Exposer les sessions sous forme multi-device sans tuer les autres appareils.
|
|
180
|
+
- Renvoyer aussi les champs de profil utiles au package dans `user_infos` quand ils sont connus, notamment `name`, `email`, `ccphone`, `phone`, `address`, `town` et `country`.
|
|
181
|
+
|
|
182
|
+
## Dépannage
|
|
183
|
+
|
|
184
|
+
- `Identifiants application invalides` au moment du login natif signifie en pratique que `POST /api/iam/native/encrypt` n'a pas réussi à valider le couple `app_key` + `encrypted_credentials`.
|
|
185
|
+
- `Déchiffrement échoué` côté IAM veut dire que `encrypted_credentials` ne correspond pas à la `secret_key` attendue par l'application, ou que la valeur envoyée est mal formée.
|
|
186
|
+
- `Credentials expirés` veut dire que le blob de credentials reçu par le package est trop ancien par rapport au TTL configuré. Dans ce cas, le package doit refaire `GET /api/native/config`.
|
|
187
|
+
- Si l'utilisateur est authentifié mais n'a pas encore accès à l'application, le bon signal est `needs_access`. Le package doit afficher la demande de confirmation d'accès, pas un simple échec de login.
|
|
188
|
+
- Si ce flux n'apparaît plus, vérifiez que `BYPASS` n'est pas activé par erreur côté SaaS et que les endpoints IAM retournent bien `needs_access` après l'authentification.
|
|
189
|
+
- Si vous avez copié des clés depuis une application déjà en production et que le login échoue quand même, vérifiez d'abord que `GET /api/native/config` renvoie bien les clés attendues pour cette application.
|
|
190
|
+
- Vérifiez aussi que le SaaS ne sert pas un `encrypted_credentials` mis en cache avec une ancienne `secret_key`.
|
|
191
|
+
- Vérifiez enfin que le format chiffré reste `base64(IV::ciphertext_base64)` comme attendu par le package.
|
|
192
|
+
|
|
193
|
+
## APIs IAM Account
|
|
194
|
+
|
|
195
|
+
Si votre intégration utilise aussi les synchronisations utilisateur server-to-server, consultez [IAM Account](./5_iam-account.md).
|
|
196
|
+
|
|
197
|
+
## Session refresh et résilience
|
|
198
|
+
|
|
199
|
+
Le contrat de `refresh_token`, la rotation, la persistance des sessions et la gestion des erreurs réseau sont détaillés dans [Session Refresh & Resilience](./11_session-refresh-resilience.md).
|