Sari la conținut

Autentificare și autorizare

ASYDATA folosește ASP.NET Core Identity nativ pentru autentificare, cu Google OAuth pentru login. Nu există server de identitate extern (Keycloak a fost respins): totul rulează în procesul .NET, sub jurisdicția noastră (PII de login = date sensibile). Această pagină descrie ce e implementat, cum se leagă de multi-tenancy și ce urmează.

Configurarea trăiește în AddAsyAuthentication (asydata-app-backend-dotnet/src/Asy.Infrastructure/Auth/AuthExtensions.cs):

ElementStare
ASP.NET Core Identity peste EF Core (Postgres pooled)✅
Google OAuth (Microsoft.AspNetCore.Authentication.Google)✅ wired
Sesiune pe cookie după login (asy.session, HttpOnly, SameSite=Lax, 14 zile sliding)✅
/api/auth/google/login → 302 Google (PKCE S256) → callback → cookie✅
/api/auth/me ([Authorize], cookie-protected)✅
Roluri Identity + token providers default✅
Personal Access Tokens (PAT) pentru agenți AI pe /api/mcp✅
SPA ─► /api/auth/google/login ─► 302 Google (scope: openid email profile)
│
Google callback ─► upsert AppUser ─► sign-in cookie "asy.session"

Cookie-ul e schema default pentru tot (inclusiv challenge): pe /api/* neautentificat se întoarce 401 (nu 302 către Google) — login-ul Google se declanșează explicit din /api/auth/google/login. Scope-urile Google sunt doar non-sensitive (openid, email, profile) → fără ecran „app neverificat”. Login = doar identitate; emailurile tranzacționale se trimit separat.

Există deja schele pentru MFA: un cookie scurt separat (asy.mfa, 15 min) emis după Google când userul are TwoFactorEnabled; sesiunea completă se emite doar după /api/auth/mfa/verify. Vezi MfaTokenService (asydata-app-backend-dotnet/src/Asy.Infrastructure/Auth/MfaTokenService.cs). Planul (docs/SECURITY_PLAN.md): cod 6 cifre prin email (Gmail), nu TOTP — publicul țintă fiind contabilii, friction-ul authenticator-ului e prea mare; TOTP rămâne opt-in pentru roluri sensibile.

Identity nu are concept nativ de tenant. Modelul ASYDATA:

  • ApplicationUser (AppUser) extins cu apartenența la companii;
  • relația user ↔ companie prin CompanyMember (user_id, company_id, role, status);
  • tenantul efectiv al unui request se rezolvă separat de identitate, în TenantContextMiddleware, din header X-Firm-Id sau (canonical) claim firm_id — vezi Multi-tenancy și RLS.

Astfel un user poate aparține mai multor firme, iar firma activă vine din context (header/claim), nu din identitatea fixă.

Peste autentificare, accesul pe modul/acțiune se decide cu filtrul RequirePermission (asydata-app-backend-dotnet/src/Asy.Platform/Authorization/RequirePermission.cs):

routes.MapGet(...).RequirePermission("efactura", "view");

Decizia (Allow / Deny / Unauthenticated) e delegată unui IPermissionEnforcer din Infrastructure (care vede DbContext-ul: rolul userului în firmă + permisiunile rolului). Pentru harta rolurilor și permisiunilor, vezi Actori și roluri.

Ce nu avem încă (docs/AUTH.md, docs/SECURITY_PLAN.md):

LipsăDe ce contează
JWT access tokensnecesar pentru mobile native sau SPA pe alt domeniu (acum SPA e same-origin → cookie ajunge)
Refresh tokens cu rotationcookie-ul de 14 zile e „all-or-nothing”
Token revocation / session revocationsuspendarea unui user nu invalidează imediat cookie-ul
CSRF explicit, rate limiting, audit logîntăriri pre-prod
Magic-link prin emailplanificat (flow propriu peste Identity)

Două direcții majore, pending de la dev (Alex Cozlovschi):

  • Opțiunea A — BFF cookie-only (recomandată cât timp SPA stă în wwwroot, same-origin): SPA nu vede niciodată JWT, serverul ține tokenurile.
  • Opțiunea B — JWT bearer + refresh cu rotation: necesară doar dacă apare mobile app sau SPA pe alt domeniu.

Pentru session revocation, planul preferat e SecurityStamp (Identity nativ): schimbi stamp-ul la suspend/role-change → cookie-urile vechi devin invalide la următorul tick.