Cum funcționează Register și Login în Bono.
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:
- Email-first. Primul câmp este adresa de email, alături de butonul Google.
- Fiecare client își alege o metodă proprie de intrare. La înregistrare: passkey (Face ID / Touch ID), Google sau parolă — passkey-ul primul și cel mai vizibil, parola ultima. Codul pe email confirmă adresa la înregistrare și recuperează contul; nu e mod obișnuit de intrare (decizia din 15.10.2026, după propunerea lui Prodi și după practica pieței).
- Niciodată nu confirmăm în UI că un email există. Asta protejează confidențialitatea clienților și previne account enumeration.
- Un singur flux, nu două. Register și Login pornesc de pe același ecran, iar ecranul de după email e același pentru orice adresă.
- Passkeys (WebAuthn) ca metodă principală, recomandată insistent, niciodată singura.
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.
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.
„Bine ai venit! Confirmă-ți adresa cu codul de mai jos: [123456]. Valabil 10 minute."
„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."
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).
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ă
- Nu poate fi phishat. Cheia e legată de domeniul bono.ro. Pe un site fals, nu funcționează.
- Nu poate fi furat dintr-un breach. Serverul stochează doar cheia publică, nu secretul.
- Nu necesită 2FA separat. Biometria este al doilea factor, incorporat.
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.
Apple sincronizează prin iCloud Keychain. Google prin Google Password Manager. Dacă userul schimbă telefonul, passkey-ul este disponibil automat pe dispozitivul nou.
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
| Parametru | Valoare și motivație |
|---|---|
| Folosire | Doar confirmarea adresei la înregistrare și recuperarea contului. Nu e mod obișnuit de intrare. |
| Format | 6 cifre numerice, generat criptografic (CSPRNG). Nu Math.random(). |
| Valabilitate | 10 minute. Countdown vizibil în UI, devine galben la <60s, roșu la expirare. |
| Încercări maxime | 3 greșeli consecutive → blocare 5 minute cu countdown vizibil. |
| Cooldown resend | 30 secunde între două cereri de cod nou. Previne spam-ul pe email. |
| Invalidare | Codul devine invalid după utilizare, expirare sau când se cere un cod nou. |
| Canal | Email exclusiv. Nu SMS — cost ridicat + vulnerabil SIM swap. |
| Rate limiting | Implementat server-side, nu doar în UI. Contoare per email și per IP, într-un loc comun tuturor serverelor. |
| Limită pe 24 de ore | Maxim 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 |
|---|---|
| Parole | Salvate 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 email | Generat criptografic (în .NET: RandomNumberGenerator.GetInt32), în bază doar amprenta lui, nu codul. |
| Linkul de recuperare din email | Unic, 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. |
| Passkeys | Legate 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ă. |
Fluxul 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. | |
| Sesiuni | Sesiunea 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 semnare | Pă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 date | Aplicația se conectează cu un cont al ei, fără drept de a schimba structura tabelelor; conexiune criptată, cu certificatul verificat. |
| Limitarea încercărilor | Pe 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țional | Template 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.
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)
- SMS OTP — cost + SIM swap vulnerability.
- CAPTCHA în fluxul normal — limitarea pe server rezolvă fără fricțiune; verificarea automată pornește doar peste limite.
- Al doilea pas obligatoriu pentru clienți — profilul de risc Bono (contabilitate PFA/SRL, nu banking). Pentru angajații Bono și contabilii cu mai multe firme e obligatoriu.
- Cod pe email ca mod obișnuit de intrare — emailul rămâne doar pentru confirmare și recuperare.
- Login cu Apple — audiența Bono e web-first. Se adaugă în v2 pentru aplicația mobilă.
- Reguli de complexitate parolă — NIST 2024 recomandă explicit să nu le impui.
Decizii 15 octombrie 2026
- Construim sistemul nostru de autentificare, nu cumpărăm Auth0: arhitectura propusă de Prodi (.NET 10, ASP.NET Core Identity, OpenIddict, Postgres). Motive: Auth0 cere oricum plan plătit pentru deciziile de mai jos (circa 54.000 USD pe 4 ani la creșterea estimată), iar plecarea de la el e grea pentru parole și passkey-uri; din 15 firme de contabilitate și facturare verificate, 11 își fac singure logarea, inclusiv Keez, pe .NET.
- Codul pe email confirmă adresa și recuperează contul; nu e mod obișnuit de intrare. La înregistrare, fiecare client își alege passkey, Google sau parolă.
- Ecranul de după email e același pentru orice adresă.
- Parola: minim 8 caractere.
- Limita de 10 coduri greșite în 24 de ore, cu link de intrare de rezervă în emailul de avertizare.
- Al doilea pas obligatoriu pentru angajații Bono și contabilii cu mai multe firme: passkey sau aplicație de coduri.
- Verificare anti-robot automată doar peste limite.
- Adresa serviciului de autentificare (cont.bono.ro sau accounts.bono.ro) o decide Prodi; se alege o singură dată, pentru că passkey-urile nu se mută.
Întrebări frecvente de la echipă
Răspunsuri la întrebările pe care le vor pune Product, Engineering și QA.
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.
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.
Bono · PRD Auth Flow v1.1 · 15 octombrie 2026 · Vezi prototipul →