Aller au contenu

Sécurité & Authentification — Architecture DYNORS

Ce document décrit l'architecture cible de l'authentification dans l'écosystème DYNORS. Elle remplace toute auth locale par app, tout token en localStorage/sessionStorage, et tout cookie partagé entre apps.

Cookies (prod) : ADR-0014 — noms neutres sid (access) / rid (refresh), préfixes __Host-/__Secure-, host-only (pas de Domain parent). Source unique : com.dynors.commons.web.DynorsAccessCookie.

Autorisation runtime : rôles et droits produit via dynors-entitlements — pas dans le JWT utilisateur.


1. Vue d'ensemble — dynors-platform/auth

L'authentification est centralisée dans un service dédié : dynors-auth, hébergé dans dynors-platform et routé via SLY sur le préfixe /auth/**.

┌─────────────────────────────────────────────────────────────────┐
│                   DYNORS AUTH  (dynors-platform)                 │
│                                                                  │
│  Provider email/password ──► apps SaaS (SuperGest, Mediconnect, │
│                               TRACIUM, BOOKS, ELISA, DAWALALE…)  │
│  Provider LDAP ─────────────► apps internes (RAGNAR, TAKKU,     │
│                               HEISENBERG, RED)                   │
│  Provider JWT refresh ──────► toutes les apps                    │
│                                                                  │
│  Sortie : JWT signé, posé en cookie HttpOnly Secure SameSite=Lax │
└─────────────────────────────────────────────────────────────────┘

dynors-security reste dans chaque application et ne change pas de rôle : il valide les JWT. Ce qui change, c'est que l'émission des tokens est désormais la responsabilité exclusive de dynors-auth, pas de chaque app.


2. Rôles et responsabilités

Composant Responsabilité
dynors-auth (dynors-platform) Émission des JWT, refresh, logout, switch tenant. Providers : email/pwd, LDAP.
dynors-security (dynors-core) Validation des JWT dans chaque app. TenantContextFilter, JwtAuthFilter, SlyTransitFilter.
dynors-db (dynors-core) Isolation données par tenant, alimenté par TenantContext.
InterAppCallService (dynors-commons) Appels inter-app via SLY — ajoute X-Source-App, X-Tenant-Id, X-Request-Id et signature HMAC.

3. Structure du JWT utilisateur

Le JWT émis par dynors-auth contient uniquement l'identité (ADR-0011 / ADR-0013) :

{
  "iss": "dynors-auth",
  "sub": "user-uuid",
  "tenant": "nom-du-tenant",
  "aud": "sgst",
  "exp": 1752000000
}

Ce qui n'est PAS dans le JWT utilisateur

  • roles / permissions produitGET /entitlements/me (dynors-entitlements).
  • source-app → header X-Source-App posé par InterAppCallService (inter-app M2M uniquement).

Le token est posé en cookie HttpOnly par dynors-auth (sid en prod avec préfixe __Host-). Jamais en localStorage ni en sessionStorage. Le navigateur n'utilise pas Authorization: Bearer pour la session utilisateur.


4. Endpoints de dynors-auth

Tous les endpoints sont accessibles via SLY au préfixe /auth/**.

Méthode Chemin Description
POST /auth/login Login email/password → JWT en cookie
POST /auth/ldap/login Login LDAP (apps internes) → JWT en cookie
POST /auth/refresh Renouvelle le JWT à partir du refresh token cookie
POST /auth/logout Révoque les tokens, efface les cookies
GET /auth/me Retourne l'identité de l'utilisateur connecté
POST /auth/tenant/switch Bascule sur un autre tenant (si multi-tenant user)

5. Flux de connexion (app SaaS)

Utilisateur
    │
    │  GET /login  (frontend app, ex. SuperGest)
    │
    ▼
Frontend (Angular)
    │
    │  Redirect vers SLY : GET sly.dynors.com/auth/login?app=supergest&return=https://supergest.dynors.com/dashboard
    │
    ▼
dynors-auth (via SLY /auth/**)
    │  POST /auth/login  { email, password, tenant }
    │  → vérifie credentials
    │  → génère JWT { sub, tenant, aud }
    │  → Set-Cookie: __Host-sid=<JWT>; HttpOnly; Secure; SameSite=Lax; Path=/
    │
    ▼
Redirect vers return URL (SuperGest dashboard)
    │
    ▼
Requêtes suivantes — navigateur envoie le cookie automatiquement
    │
    ▼
dynors-security (JwtAuthFilter dans SuperGest-API)
    → valide le JWT
    → alimente TenantContext
    → autorise la requête

6. Flux de connexion (app interne LDAP)

Employé DYNORS
    │
    │  GET /login  (frontend RAGNAR / RED / TAKKU / HEISENBERG)
    │
    ▼
dynors-auth (via SLY /auth/ldap/login)
    │  → vérifie credentials LDAP
    │  → génère JWT { sub, tenant:"dynors-internal", aud }
    │  → Set-Cookie: cookie host-only (sid / __Host-sid en prod)
    │
    ▼
App interne (RAGNAR, RED…)
    → dynors-security valide, TenantContext alimenté

7. Appels inter-app (service-to-service)

Les appels entre apps DYNORS ne passent jamais par le token utilisateur directement. Ils utilisent InterAppCallService qui signe chaque requête HMAC et ajoute :

X-Source-App: supergest
X-Tenant-Id: nom-du-tenant
X-Request-Id: uuid-trace
X-Sly-Timestamp: <epoch-ms>
X-Sly-Signature: <hmac-sha256>
// Exemple dans SuperGest — appel vers FISCAL via SLY
@Service @RequiredArgsConstructor
public class FiscalClient {
    private final InterAppCallService interApp;

    public FiscalResponse certify(String tenant, FiscalPayload payload) {
        return interApp.post(
            "/fiscal/api/v1/certify",
            payload,
            FiscalResponse.class,
            tenant
        );
    }
}

SlyTransitFilter côté FISCAL vérifie la signature HMAC avant de traiter la requête.


8. Intégration dans chaque app — ce qui ne change pas

dynors-security s'utilise de la même façon dans chaque app. La seule différence est que /auth/** est désormais délégué à SLY → dynors-auth et non géré localement.

@Configuration @EnableWebSecurity
public class SecurityConfig {

    @Autowired
    private JwtAuthFilter jwtAuthFilter;       // fourni par dynors-security
    @Autowired
    private SlyTransitFilter slyTransitFilter; // fourni par dynors-commons

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable())
            .sessionManagement(s -> s.sessionCreationPolicy(STATELESS))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/actuator/health", "/v3/api-docs/**").permitAll()
                .anyRequest().authenticated()
            )
            .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
            .addFilterBefore(slyTransitFilter, JwtAuthFilter.class);

        return http.build();
    }
}

Plus d'endpoint /api/auth/login local

Chaque app ne gère plus son propre endpoint de login. Elle reçoit des requêtes avec un cookie JWT déjà émis par dynors-auth. Le SecurityConfig n'a plus de permitAll() sur /api/auth/** car cet endpoint n'existe plus dans l'app.


9. Configuration application.yml

dynors:
  security:
    jwt:
      secret: ${JWT_SECRET}              # même clé que dynors-auth (symétrique) ou clé publique RSA
      expiration: 3600                   # 1h — le refresh est géré par dynors-auth

  interapp:
    source-app: supergest                # code de l'app courante
    gateway-url: ${SLY_BASE_URL:https://sly.dynors.com}
    return-base-url: ${SLY_BASE_URL}/supergest
    sly-forward-secret: ${SLY_FORWARD_SECRET}
    sly-signature-window-ms: 30000

10. Cookies et sécurité frontend

Règle Détail
Host-only Pas de Domain parent (.dynors.com) — ADR-0014.
HttpOnly Cookie non accessible depuis JavaScript (anti-XSS).
Secure Obligatoire en prod ; désactivé en local (http://localhost).
SameSite=Lax Atténuation CSRF ; Strict sur refresh si applicable.
Noms neutres Base sid / rid — pas de dynors_token.
Jamais localStorage Interdit en production DYNORS.

Le frontend Angular lit l'identité via GET /auth/me, puis les droits via GET /entitlements/mesans décoder le cookie côté client.


11. Autorisation — entitlements et RBAC

Droits produit (scopes, tiers, features) : résolus à runtime par dynors-entitlements via EntitlementsClient + EntitlementsGuard (module partagé dynors-entitlements-client). Appels via SLY uniquement.

RBAC employé / rôles Spring (apps internes ou rôles techniques JWT legacy transitoires) : @PreAuthorize + dynors-security — ne pas confondre avec les entitlements SaaS.

@Service @RequiredArgsConstructor
public class InvoiceService {
    private final EntitlementsGuard entitlements;

    public InvoiceDto get(String id) {
        entitlements.require("app:supergest", "invoice:read");
        // ...
    }
}

12. Partenaires B2B — SLY Pass

Les intégrations techniques partenaires (webhooks entrants, API B2B) utilisent le mécanisme X-Sly-Pass géré par SLY, distinct du flux de connexion utilisateur. Voir Gateway SelebeYone.


13. Références

Sujet Document
Architecture complète dynors-auth dynors-auth
Cookies isolés par app ADR-0014
État d'implémentation auth/entitlements Auth · Entitlements · Sécurité — implémentation
SLY — Gateway et routage Gateway SelebeYone
Routage inter-app Routage inter-app
Multi-tenancy Multi-tenancy
Secrets — Vault DevOps — Vault
Base de données multi-tenant Base de données multi-tenant