Contexte utilisateur — /me
Récupérer le profil complet, les rôles et les droits applicatifs d'un utilisateur authentifié.
Contexte utilisateur — /me
/meest votre source de vérité côté frontend pour tout ce qui concerne l'identité et les droits de l'utilisateur courant. Appelez-le une fois après le login, stockez le résultat dans un context React.
Pourquoi cette étape
Le JWT Keycloak contient l'identité minimale (sub, email, niveaux admin). Mais pour construire une UI conditionnelle (masquer/afficher des boutons selon les droits, afficher le bon nom d'organisation, etc.), vous avez besoin du payload complet que seule l'API /me retourne.
/me agrège les données de plusieurs sources (Keycloak claims, DB config, profils, équipes) en un seul appel.
Concepts clés
- adminLevel : niveau hiérarchique admin de l'utilisateur (WakaAdmin → None).
- wakaRoles : rôles métier transversaux (CONFIG, EXPLOIT, DPO, AUDIT, BILLING, CYBER).
- appRights : liste de droits applicatifs de type
resource.actionaccumulés depuis les profils. - features : features activées en cascade App ∧ Network ∧ Customer.
- profiles : profils Wakastart assignés à l'utilisateur (détermine les appRights).
- teams : équipes dont l'utilisateur est membre, avec son level et ses droits dans chaque équipe.
Appel
httpGET /api/me Authorization: Bearer <keycloak_access_token> x-enriched-token: <wakastart_token>
Les deux headers sont requis. La réponse est la même que l'utilisateur appelle avec un Bearer JWT ou une API key.
Réponse complète
json{ "id": "550e8400-e29b-41d4-a716-446655440000", "wid": "WKST05", "email": "john@acme.com", "firstName": "John", "lastName": "Doe", "keycloakId": "kc-550e8400-e29b-41d4-a716-446655440000", "isActive": true, "adminLevel": "CustomerAdmin", "userLevel": "ADMIN", "wakaRoles": ["CONFIG", "EXPLOIT"], "hdsRoles": [], "customer": { "id": "uuid", "wid": "ACM001", "name": "Acme Corp", "subdomain": "acme" }, "partner": { "id": "uuid", "wid": "PTR001", "name": "Wakastellar" }, "network": { "id": "uuid", "wid": "NET001", "name": "Production" }, "teams": [ { "id": "uuid", "wid": "TEAM01", "name": "DevOps", "teamLevel": "ADMIN", "appRights": "rw" }, { "id": "uuid", "wid": "TEAM02", "name": "Support", "teamLevel": "MEMBER", "appRights": "r" } ], "profiles": [ { "id": "uuid", "wid": "PRF001", "name": "Administrateur" } ], "appRights": [ "partners.read", "users.read", "users.write", "users.create", "users.update", "users.delete", "apps.read", "apps.create", "teams.read", "teams.members.add", "infra.read", "infra.deploy", "audit.read", "profiles.assign", "api-keys.read", "api-keys.create", "api-keys.revoke" ], "features": ["invitations", "antivirus"], "lastLoginAt": "2026-05-19T10:30:00.000Z", "createdAt": "2026-01-15T08:00:00.000Z" }
Description de chaque champ
Identité
| Champ | Type | Description |
|---|---|---|
id | UUID | Identifiant interne en base |
wid | string | Identifiant court (6 chars) pour les URLs |
email | string | Email de l'utilisateur (lowercase) |
firstName / lastName | string | Prénom / Nom |
keycloakId | string | UUID Keycloak (sub du JWT) |
isActive | boolean | Compte actif. Si false, l'enrichissement échoue |
Niveaux d'accès
| Champ | Type | Description |
|---|---|---|
adminLevel | string | Niveau admin hiérarchique (voir tableau ci-dessous) |
userLevel | string | Niveau utilisateur : NONE, VIEWER, CONTRIBUTOR, MEMBER, MANAGER, ADMIN, INVITE |
Hiérarchie adminLevel :
textWakaAdmin → Accès total plateforme (super-admin Wakastellar) OwnerAdmin → Accès total plateforme (propriétaire partner) AppsAdmin → Applications, profils, droits NetworkAdmin → Réseaux, customers, apps CustomerAdmin → Customers, utilisateurs, équipes User → Accès basique (propre profil) None → Aucun accès admin
Hiérarchique : un NetworkAdmin possède aussi les droits de CustomerAdmin et User.
Contexte multi-tenant
| Champ | Type | Description |
|---|---|---|
customer | object | Customer auquel l'utilisateur appartient |
partner | object | Partner parent du customer |
network | object | Network parent du customer |
Rôles métier
| Champ | Type | Description |
|---|---|---|
wakaRoles | string[] | Rôles Waka : CONFIG, EXPLOIT, DPO, AUDIT, BILLING, CYBER |
hdsRoles | string[] | Rôles HDS (Healthcare Data Security) : HDS_ADMIN, HDS_PATIENT, etc. |
Équipes
json"teams": [ { "id": "uuid", "wid": "TEAM01", "name": "DevOps", "teamLevel": "ADMIN", "appRights": "rw" } ]
appRights dans le contexte équipe est un champ libre encodé sur la team (distinct des appRights globaux de l'utilisateur).
Profils et droits applicatifs
profiles: profils assignés. Un profil = un ensemble deAppRightcôté DB.appRights: tableau dédupliqué et fusionné de tous les droits provenant de tous les profils. Format :{resource}.{action}(ex:users.create,infra.deploy).features: features activées en cascade (App ∧ Network ∧ Customer). Exemple :invitations,antivirus,hds.
Ce qui est dans /me vs dans le JWT
| Donnée | JWT enrichi (wakaToken) | /me (API) |
|---|---|---|
| Identité (email, sub) | oui (via Keycloak) | oui |
adminLevel (wadl) | oui | oui |
| Partner/Network/Customer WIDs | oui (pid, nid, cid) | oui (objets complets) |
wakaRoles | oui | oui |
hdsRoles | oui | oui |
Team rights (wgrl) | oui (encodé) | oui (décodé, tableaux) |
| Profils | non | oui |
appRights (liste complète) | non | oui |
features | non | oui |
userLevel | non | oui |
firstName, lastName | non | oui |
lastLoginAt | non | oui |
Le JWT contient le minimum pour le routage et la vérification rapide des droits. /me contient tout le reste pour construire l'UI.
Quand appeler /me
| Situation | Appeler /me ? |
|---|---|
| Au login (après enrichissement) | Oui — stocker dans le context |
| Après un refresh token | Oui — les droits peuvent avoir changé |
| À chaque navigation | Non — utiliser les données en cache |
| Après un changement de profil (si l'admin a modifié les droits) | Oui — invalider le cache et re-fetcher |
| Après un logout partiel ou changement d'organisation | Oui |
Pattern React recommandé :
typescript// lib/auth-context.tsx import { createContext, useContext, useState, useCallback } from "react"; interface MeUser { id: string; wid: string; email: string; firstName: string; lastName: string; adminLevel: string; wakaRoles: string[]; appRights: string[]; features: string[]; customer: { id: string; wid: string; name: string }; // ... autres champs } interface AuthContextValue { user: MeUser | null; loading: boolean; refresh: () => Promise<void>; } const AuthContext = createContext<AuthContextValue>({ user: null, loading: false, refresh: async () => {}, }); export function AuthProvider({ children }: { children: React.ReactNode }) { const [user, setUser] = useState<MeUser | null>(null); const [loading, setLoading] = useState(false); const refresh = useCallback(async () => { setLoading(true); try { const res = await fetch("/api/proxy/me"); // via votre proxy serveur if (res.ok) setUser(await res.json()); } finally { setLoading(false); } }, []); return ( <AuthContext.Provider value={{ user, loading, refresh }}> {children} </AuthContext.Provider> ); } export const useAuth = () => useContext(AuthContext);
Mise en cache et TTL recommandé
/me est appelé une fois au login. La réponse est stable tant que :
- Les droits de l'utilisateur n'ont pas été modifiés par un admin.
- Le token n'a pas été rafraîchi.
Stratégie recommandée :
- Stocker dans un React context (en mémoire, pas en localStorage).
- Invalider le cache après chaque refresh token (les droits peuvent avoir changé).
- Exposer un hook
useAuth().refresh()pour forcer le rechargement si l'admin notifie un changement.
Bonnes pratiques
- Ne stockez pas le payload
/medanslocalStorage— il contient des droits sensibles. - Utilisez les
appRightsuniquement pour l'affichage conditionnel côté front. Le backend est la source d'autorité. - Si
adminLevel === "None"etappRights.length === 0, l'utilisateur n'a aucun droit — affichez une page dédiée. - Vérifiez
isActiveau chargement : sifalse, redirigez vers une page "Compte désactivé". - Utilisez
featurespour masquer des sections entières (ex: si"invitations"absent, masquer tout le module invitations).
Pièges classiques
- Droits stale : les droits sont modifiés par un admin mais le cache
/men'est pas invalidé. Ajouter un mécanisme de polling léger ou une notification WebSocket. appRightsvide mais user actif : l'utilisateur est actif mais n'a aucun profil assigné. Contacter l'admin du Customer.adminLevelmal interprété : le niveau est une string, pas un entier — comparer par valeur exacte ou par inclusion dans un tableau ordonné.
Aller plus loin
- Contrôle d'accès : comment utiliser
appRightsdans vos guards - Utilisation de l'API : comment passer les tokens dans vos requêtes