Înapoi la prototip PRD · Auth

Cum funcționează Register și Login în Bono.

BP
de Echipa Produs · 15 min citire · Versiune 1.1 · 15 octombrie 2026

Autentificarea este primul lucru pe care îl face un user când ajunge în Bono. Este și ultima barieră înainte ca el să vadă produsul. Un flux greșit înseamnă abandon înainte ca userul să descopere valoarea. Un flux slab securizat înseamnă risc real pentru datele financiare ale clienților noștri. Acest document explică fiecare decizie luată, de ce am luat-o și cum funcționează în practică.

Documentul este scris pentru echipa tehnică — backend, frontend, QA — dar și pentru Product și Design. Scopul este ca toată lumea să înțeleagă nu doar ce am construit, ci și de ce.

Principii de design

Înainte de orice flux, câteva reguli după care am luat toate deciziile:

Fluxul de Register

Userul ajunge pe ecranul de Register. Vede un câmp de email și un buton Google. Butonul de email este inactiv (bej) până când introduce o adresă validă — în acel moment devine primar (roz) și Google devine secundar.

S1 · Register — email sau Google
↳ Google OAuth→Dublin (cont creat automat; Google e deja metoda lui de intrare)
↳ Email valid→sistem trimite cod de confirmare
S2 · Confirmarea adresei — cod de 6 cifre, valabil 10 minute
↳ Cod corect→S3 · Cum vrei să intri de acum?
↳ 3 greșeli consecutive→blocat 5 minute
↳ Cod expirat→poate cere cod nou după 30s
S3 · Cum vrei să intri de acum? — pas obligatoriu, o singură alegere
↳ Face ID / Touch ID (passkey; primul, cel mai vizibil)→Dublin
↳ Parolă (ultima opțiune; minim 8 caractere)→Dublin
Telefon — opțional, doar pentru suport și notificări; nu e metodă de intrare

Emailul există deja în baza de date

Dacă un user încearcă să se înregistreze cu o adresă care are deja cont, UI-ul nu confirmă asta. Trimitem tot un email cu cod, dar cu un mesaj diferit în corp. Userul vede același ecran neutru.

Email · Cont nou

„Bine ai venit! Confirmă-ți adresa cu codul de mai jos: [123456]. Valabil 10 minute."

Email · Cont existent detectat

„Am primit o cerere de creare cont pentru această adresă, dar ai deja un cont Bono. Dacă ești tu și nu mai știi cum intri, recuperează-ți accesul cu codul: [123456]. Dacă nu ești tu, ignoră acest email."

De ce nu confirmăm în UI?

Dacă am afișa „Contul tău este deja activ", un atacator ar putea afla că ana.popescu@exemplu.ro este client Bono pur și simplu încercând la register. Faptul că ești client Bono este o informație privată și protejată GDPR. Qonto, Revolut și alte fintechs europene aplică același standard.

Fluxul de Login

Login-ul pornește identic cu Register-ul — același ecran, același câmp de email. După email, toți userii văd același ecran, indiferent dacă au cont sau ce metodă au: altfel, cine încearcă adrese la rând ar afla cine e client Bono (decis pe 15.10.2026).

S1 · Login — email + checkbox „Rămâi autentificat"
↳ Google OAuth→Dublin
↳ Passkey propus de browser direct în câmpul de email→Face ID / Touch ID→Dublin
↳ Email→S2 · Parolă — același ecran pentru orice adresă
S2 · Parolă
↳ Parolă corectă→Dublin (eroarea e identică pentru email greșit și parolă greșită)
↳ link „Nu poți intra?"→Recuperare

Checkbox „Rămâi autentificat"

Bifat implicit. Când e bifat: sesiunea de pe server ține 30 de zile și se reînnoiește cât userul e activ. Când e debifat: sesiunea ține cât e deschis browserul și se încheie la închiderea lui. Util pentru userii care accesează Bono de pe calculatoare partajate.

Recuperarea contului

Pentru cine a uitat parola, a pierdut telefonul cu passkey-ul sau nu mai are acces la Google. Userul cere un cod pe email (răspunsul e același pentru orice adresă), îl introduce și primește o sesiune scurtă, bună doar ca să-și seteze o metodă nouă: passkey sau parolă. Imediat după, sesiunile de pe celelalte dispozitive se închid și userul primește un email de notificare. Recuperarea nu deschide direct aplicația.

Passkey — Face ID și Touch ID

Un passkey este o cheie criptografică păstrată de dispozitiv și de managerul lui de parole (iCloud Keychain, Google Password Manager). La înregistrare se generează o pereche: cheia privată rămâne la user, cheia publică ajunge la Bono. Cheia privată nu ajunge niciodată la Bono; se sincronizează doar între dispozitivele userului, criptată de Apple sau Google.

La autentificare, Bono trimite o provocare criptografică (string random). Dispozitivul o semnează cu cheia privată, dar numai după ce userul confirmă prin Face ID sau Touch ID. Bono verifică semnătura cu cheia publică. Dacă se potrivesc — autentificat.

De ce e mai sigur decât o parolă

Cum se activează

La înregistrare, passkey-ul e prima opțiune din ecranul „Cum vrei să intri de acum?". Cine alege parola sau Google primește propunerea de passkey la primele 3 logări; după 3 refuzuri nu mai insistăm. Oricând se poate activa din Setări → Securitate.

Cum funcționează login-ul cu Passkey activ

Când userul pune cursorul în câmpul de email, browserul (Safari, Chrome) îi propune singur passkey-ul salvat pentru Bono, în lista de completare automată. Userul îl alege și confirmă prin Face ID — intrat direct, fără cod și fără parolă. Serverul nu spune niciodată dacă un email are passkey: propunerea vine de la browser, din ce are salvat pe dispozitiv.

Sincronizare cross-device

Apple sincronizează prin iCloud Keychain. Google prin Google Password Manager. Dacă userul schimbă telefonul, passkey-ul este disponibil automat pe dispozitivul nou.

Fallback

Pe dispozitiv nou, browser incompatibil sau dacă userul refuză Face ID: userul intră cu parola sau Google, ori își recuperează contul cu cod pe email. Passkey-ul nu e niciodată singura cale de intrare.

Codul pe email — reguli și timing

ParametruValoare și motivație
FolosireDoar confirmarea adresei la înregistrare și recuperarea contului. Nu e mod obișnuit de intrare.
Format6 cifre numerice, generat criptografic (CSPRNG). Nu Math.random().
Valabilitate10 minute. Countdown vizibil în UI, devine galben la <60s, roșu la expirare.
Încercări maxime3 greșeli consecutive → blocare 5 minute cu countdown vizibil.
Cooldown resend30 secunde între două cereri de cod nou. Previne spam-ul pe email.
InvalidareCodul devine invalid după utilizare, expirare sau când se cere un cod nou.
CanalEmail exclusiv. Nu SMS — cost ridicat + vulnerabil SIM swap.
Rate limitingImplementat server-side, nu doar în UI. Contoare per email și per IP, într-un loc comun tuturor serverelor.
Limită pe 24 de oreMaxim 10 coduri greșite în 24 de ore pe același email, indiferent de IP. La a zecea greșeală, codul activ se anulează, verificarea prin cod rămâne oprită pentru acel email până ies greșelile vechi din fereastra de 24 de ore, iar userul primește pe email avertizarea „Cineva încearcă să intre în contul tău”. Parola, Google și passkey-ul merg în continuare, iar emailul de avertizare conține un link de recuperare de unică folosință, valabil 10 minute, care nu poate fi ghicit și se folosește apăsând un buton pe pagină (nu la simpla deschidere, pentru că programele de email ale firmelor deschid singure linkurile). Așa, cineva care știe emailul unui client nu îl poate bloca greșind intenționat codurile. Motivul limitei: blocarea de 5 minute singură permite cam 860 de încercări pe zi pe același cont, adică o șansă de circa una din patru pe an de a ghici un cod. Decis de Bogdan pe 15.10.2026.

Decizii de securitate

Verificare anti-robot doar peste limite

Un client obișnuit nu vede niciodată o verificare anti-robot. Ea pornește doar când cineva depășește limitele de încercări (de exemplu, la un atac pe multe conturi), cu o soluție automată și accesibilă, fără imagini de bifat — de exemplu Cloudflare Turnstile. Alegerea finală e a echipei tehnice; furnizorul se trece în nota de confidențialitate. Decis pe 15.10.2026, după propunerea lui Prodi.

Fără SMS OTP

Trei motive: (1) cost recurent per mesaj, (2) vulnerabilitate la SIM swap, (3) emailul este deja verificat la register, deci este canal de încredere.

Parola — reguli minime

Minim 8 caractere. Fără reguli de complexitate artificiale (majuscule obligatorii, caractere speciale). NIST SP 800-63B (2017, actualizat 2024) recomandă explicit să nu impui aceste reguli — duc la parole mai slabe. Blocăm în schimb parolele din lista top 10.000 compromise. Acceptăm parole de cel puțin 64 de caractere. Minimul de 8 e ales conștient (Bogdan, 15.10.2026), inclusiv acum, când parola e o metodă principală pentru cine o alege: e minimul standardului OWASP ASVS 5.0; NIST și OWASP recomandă 15 când parola e singurul factor. Riscul e redus de limita de încercări, de lista de parole compromise și de faptul că passkey-ul și Google sunt propuse înaintea parolei.

Al doilea pas, doar unde un cont deschide multe firme

Clienții intră cu un singur pas (cod pe email, parolă, Google sau passkey): profilul de risc e contabilitate PFA/SRL, nu banking. Excepțiile sunt conturile și acțiunile la care un cont furat înseamnă datele multor firme sau pierderea contului: (1) angajații Bono și (2) contabilii care văd mai multe firme intră cu passkey sau cu parolă plus un cod dintr-o aplicație de coduri (de tipul Google Authenticator) — niciodată cu un cod pe email ca al doilea pas, pentru că emailul e și calea de recuperare; (3) acțiunile sensibile (schimbarea emailului, exporturile mari) cer o confirmare nouă chiar înainte, cu passkey sau cod pe email, iar la schimbarea emailului confirmarea pleacă pe adresa veche. Decis de Bogdan pe 15.10.2026.

Sesiunile

Sesiunile stau pe server; browserul primește doar un cookie HttpOnly pe care pagina nu-l poate citi, niciodată tokenurile. Sesiunea se închide la logout și se reînnoiește după orice acțiune sensibilă (schimbare email, schimbare parolă). Schimbarea parolei, a emailului sau ștergerea unei metode de autentificare închide sesiunile de pe celelalte dispozitive.

Cerințe de securitate pentru implementare

Ce trebuie să fie adevărat, oricum se construiește. Alegerea uneltelor rămâne la echipa tehnică. Baza: standardul OWASP ASVS 5.0, nivelul 2 (actualizat 15.10.2026).

ComponentăCerința
ParoleSalvate doar ca amprentă, cu un algoritm lent: bcrypt cu cost ≥ 12, Argon2id sau PBKDF2-SHA512 cu ≥ 220.000 de iterații (implicitul din .NET 10 are 100.000, deci se configurează explicit, iar amprentele vechi se refac la următoarea logare). Verificare în lista de parole compromise la setare. Blocarea după încercări greșite pornită efectiv în cod.
Codul pe emailGenerat criptografic (în .NET: RandomNumberGenerator.GetInt32), în bază doar amprenta lui, nu codul.
Linkul de recuperare din emailUnic, imposibil de ghicit, în bază doar amprenta lui, o singură folosire, 10 minute, consumat printr-un buton pe pagină. Deschide aceeași sesiune scurtă ca recuperarea cu cod.
PasskeysLegate de domeniul serviciului de autentificare (de exemplu cont.bono.ro), nu de întreg bono.ro; domeniul nu se mai poate schimba după primele înrolări. Mai multe passkey-uri pe cont; ștergerea ultimei metode de intrare nu e permisă.
GoogleFluxul cu cod de autorizare + PKCE, state și nonce. Contul se recunoaște după identificatorul Google (sub), nu după email. Un cont existent cu același email se leagă de Google doar dacă Google confirmă emailul ca verificat. Scope: email + profile.
SesiuniSesiunea stă pe server; tokenurile nu ajung în browser, care are doar un cookie HttpOnly și Secure. Setarea SameSite se alege după rolul fiecărui cookie (Strict ar rupe întoarcerea de la Google). Fiecare aplicație Bono are cookie-ul ei, legat doar de subdomeniul ei; logarea unică între aplicații se face prin redirecționare la serviciul de autentificare, nu printr-un cookie pe tot .bono.ro. Revocare la logout și la „ieși de pe toate dispozitivele".
Cheile de semnarePăstrate în seiful de secrete al mediului (Key Vault), niciodată în cod sau în fișiere de configurare; cu procedură de schimbare scrisă.
Baza de dateAplicația se conectează cu un cont al ei, fără drept de a schimba structura tabelelor; conexiune criptată, cu certificatul verificat.
Limitarea încercărilorPe server, per email și per IP, pentru coduri și pentru parole, plus limita de 10 coduri greșite pe 24 de ore. Peste limite, verificarea anti-robot automată.
Email tranzacționalTemplate separat per tip de email; niciun cod sau link în jurnale.

Endpoint-uri API

Le stabilește echipa tehnică, în arhitectura propusă de Prodi (serviciu propriu de autentificare pe .NET 10, cu protocolul standard OpenID Connect). PRD-ul cere doar comportamentul de mai jos.

Important pentru backend

Pasul de după email dă același răspuns, în același timp, pentru orice adresă: cont cu parolă, cont cu passkey, cont doar cu cod sau email inexistent. Nu expunem niciodată dacă emailul există sau ce metode are contul. Conținutul emailului poate diferi, răspunsul API nu. Passkey-ul se oferă prin completarea automată a browserului (WebAuthn conditional mediation), nu printr-un răspuns al serverului.

Ce nu facem (și de ce)


Decizii 15 octombrie 2026

Întrebări frecvente de la echipă

Răspunsuri la întrebările pe care le vor pune Product, Engineering și QA.

Cum află ecranul de login dacă un cont are passkey activ?
Nu află. La încărcarea paginii, frontul cere un challenge pentru passkey (fără email) și pornește navigator.credentials.get() în modul de completare automată (mediation: "conditional"). Dacă dispozitivul are un passkey pentru Bono, browserul îl propune în câmpul de email; dacă nu, nu apare nimic și userul merge pe parolă sau Google. Serverul nu spune niciodată ce metode are un cont.
Ce se întâmplă dacă un cod pe email expiră exact când userul apasă butonul?
Serverul validează momentul creării codului la verificare. Dacă a expirat (chiar cu o secundă), returnează eroare. Frontul afișează banner-ul de expirare cu butonul „Trimite din nou" activ, pe același ecran, cu câmpurile resetate.
Rate limiting-ul e per email, per IP sau combinat?
Combinat: per email (3 coduri greșite blochează 5 minute, indiferent de IP; maxim 10 coduri greșite în 24 de ore; la fel pentru parole) și per IP (peste 10 cereri de înregistrare sau logare în 10 minute → blocare temporară). Peste limite pornește verificarea anti-robot automată. Contoarele stau într-un loc comun tuturor serverelor.
Dacă userul are cont Google și încearcă să se logheze cu email, ce se întâmplă?
Vede același ecran ca toți ceilalți (parola). Dacă nu are parolă, intră cu butonul Google; dacă vrea și altă metodă, își adaugă passkey sau parolă din Setări → Securitate. Dacă nu mai are acces la contul Google, folosește recuperarea cu cod pe email.
Cum poate userul să activeze passkey-ul mai târziu, dacă a ales parola sau Google?
Cine a ales parola sau Google la înregistrare vede propunerea de passkey la primele 3 logări. După 3 refuzuri consecutive nu o mai afișăm. Oricând poate reveni din Setări → Securitate, secțiunea „Metode de autentificare". Userul poate înregistra passkey-uri de pe mai multe dispozitive.
Ce se întâmplă cu passkey-ul dacă userul schimbă telefonul?
Pe ecosistemul Apple: passkey-ul se sincronizează automat prin iCloud Keychain pe toate dispozitivele cu același cont Apple. Pe Android: prin Google Password Manager. Dacă userul trece de pe Android pe iPhone sau invers, va trebui să activeze un passkey nou de pe noul dispozitiv — până atunci intră cu parola, cu Google sau prin recuperare.
Cum testăm WebAuthn în mediul de development / staging?
Chrome DevTools are un Virtual Authenticator integrat (F12 → tab-ul „WebAuthn"). Permite simularea Touch ID/Face ID fără hardware real. Safari pe Mac necesită un dispozitiv real sau simulatorul iOS. Pentru testele automate se folosește un autentificator virtual, din biblioteca de passkeys aleasă de echipă. Pe staging, domeniul trebuie să fie HTTPS — WebAuthn nu funcționează pe HTTP (excepție: localhost).
Cum gestionăm cazul în care userul uită parola?
Pe ecranul de parolă, linkul „Nu poți intra?" duce la recuperare: cod pe email, apoi o sesiune scurtă în care își setează o parolă nouă sau un passkey. După asta, celelalte sesiuni se închid și primește un email de notificare.
Unde folosim aplicația de coduri (Google Authenticator)?
Pentru angajații Bono și contabilii cu mai multe firme, aplicația de coduri e una din cele două variante obligatorii (cealaltă e passkey-ul). Pentru clienți rămâne opțională: o pot activa din Setări → Securitate.
Cum funcționează „Rămâi autentificat" tehnic?
Sesiunea stă pe server. Bifat: cookie-ul de sesiune ține 30 de zile și se reînnoiește cât userul e activ. Debifat: cookie-ul nu are dată de expirare, deci browserul îl șterge la închidere. În ambele cazuri, browserul nu vede tokenurile.
Ce se întâmplă dacă același email se înregistrează simultan din două tab-uri?
Emailul e unic în bază, deci primul request creează înregistrarea, iar al doilea o găsește. Ambele primesc același răspuns neutru și un email cu cod. Codul nou îl anulează pe cel vechi — userul folosește ultimul cod primit.
Cum gestionăm logout-ul din toate dispozitivele?
„Ieși de pe toate dispozitivele" închide pe server toate sesiunile userului, cu efect imediat. Același lucru se întâmplă automat după recuperare, schimbarea parolei sau a emailului și ștergerea unei metode de intrare.
Cât timp păstrăm logurile de autentificare și ce conțin?
Logurile de autentificare (logare reușită sau eșuată, cod greșit, limită atinsă, recuperare) se păstrează 90 de zile, pentru investigarea incidentelor de securitate. Conțin: momentul, tipul evenimentului, user_id (nu emailul în clar), IP-ul pseudonimizat, browserul. Nu logăm niciodată coduri, parole, tokenuri sau chei. Scopul, cine le poate citi și ștergerea după 90 de zile se trec în registrul de prelucrări; pseudonimizarea IP-ului nu le face anonime, deci rămân date personale.
De ce nu avem „Login cu Apple"?
Audiența Bono este în principal antreprenori care accesează de pe desktop — unde Google OAuth are penetrare mult mai mare decât Apple ID. În plus, Apple Sign In este obligatoriu doar pentru aplicații iOS care oferă alt provider social (regulă App Store) — pentru web, este opțional. Adăugăm în v2 când lansăm aplicația mobilă nativă.

Bono · PRD Auth Flow v1.1 · 15 octombrie 2026 · Vezi prototipul →