ASP.NET Core Identity este sistemul de autentificare și autorizare inclus nativ în ecosistemul .NET. Dacă ai lucrat vreodată cu o aplicație web în ASP.NET Core, probabil l-ai întâlnit — dar puțini dezvoltatori îi explorează întreaga profunzime. Acest articol face exact asta.
Arhitectura de bază
Identity se bazează pe două servicii centrale: UserManager<TUser> și SignInManager<TUser>. Acestea sunt injectate prin DI și orchestrează aproape orice operație legată de utilizatori.
UserManager se ocupă de operații CRUD pe utilizatori: creare, ștergere, modificare parolă, gestiunea rolurilor, claim-uri, tokeni de confirmare email. SignInManager gestionează sesiunea: login cu parolă, login extern, 2FA, logout.
Înregistrarea în Program.cs arată astfel:
builder.Services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.Password.RequiredLength = 8;
options.User.RequireUniqueEmail = true;
options.SignIn.RequireConfirmedEmail = true;
})
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultTokenProviders();
Custom User — extinderea IdentityUser
Modelul implicit IdentityUser acoperă câmpurile de bază: email, username, hash parolă, număr de telefon. Dar în aplicații reale ai nevoie de mai mult — nume complet, avatar, dată înregistrare, plan de abonament.
Soluția este să creezi o clasă care moștenește IdentityUser:
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; } = default!;
public string LastName { get; set; } = default!;
public string? AvatarUrl { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public string PlanId { get; set; } = "basic";
}
Odată definit, înlocuiești IdentityUser cu ApplicationUser peste tot — în AddIdentity<ApplicationUser, ...>(), în UserManager<ApplicationUser> și în contextul EF.
Custom Password Validators
Validatorii impliciți de parolă sunt funcționali dar rigizi. Dacă vrei reguli personalizate — de exemplu, să interzici parolele care conțin username-ul, sau să ceri cel puțin un caracter special dintr-o listă specifică — implementezi IPasswordValidator<TUser>.
public class NoUsernameInPasswordValidator : IPasswordValidator<ApplicationUser>
{
public Task<IdentityResult> ValidateAsync(
UserManager<ApplicationUser> manager,
ApplicationUser user,
string? password)
{
if (password != null &&
user.UserName != null &&
password.Contains(user.UserName, StringComparison.OrdinalIgnoreCase))
{
return Task.FromResult(IdentityResult.Failed(
new IdentityError
{
Code = "PasswordContainsUsername",
Description = "Parola nu poate conține username-ul."
}));
}
return Task.FromResult(IdentityResult.Success);
}
}
Înregistrarea în DI:
builder.Services.AddScoped<IPasswordValidator<ApplicationUser>,
NoUsernameInPasswordValidator>();
Poți înregistra oricâți validatori — Identity îi rulează pe toți și agregă erorile.
Two-Factor Authentication (2FA)
Identity suportă 2FA nativ prin TOTP (Time-based One-Time Password) — compatibil cu Google Authenticator, Microsoft Authenticator și orice app TOTP standard.
Fluxul complet implică trei pași: activarea 2FA de către utilizator, generarea unui QR code cu secretul, și verificarea codului la fiecare login.
Generarea cheii și URI-ului pentru QR code:
var userId = await _userManager.GetUserIdAsync(user);
var key = await _userManager.GetAuthenticatorKeyAsync(user);
if (string.IsNullOrEmpty(key))
{
await _userManager.ResetAuthenticatorKeyAsync(user);
key = await _userManager.GetAuthenticatorKeyAsync(user);
}
var uri = _urlEncoder.Encode(
$"otpauth://totp/MyApp:{user.Email}?secret={key}&issuer=MyApp");
Verificarea codului introdus de utilizator:
var isValid = await _userManager.VerifyTwoFactorTokenAsync(
user,
_userManager.Options.Tokens.AuthenticatorTokenProvider,
code.Replace(" ", "").Replace("-", ""));
if (isValid)
await _userManager.SetTwoFactorEnabledAsync(user, true);
La login, după validarea parolei, SignInManager detectează automat că utilizatorul are 2FA activ și returnează SignInResult.TwoFactorRequired, redirecționând fluxul către pasul de verificare cod.
Account Lockout
Lockout-ul protejează împotriva atacurilor brute-force. Identity îl gestionează automat dacă îl configurezi corect:
builder.Services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15);
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.AllowedForNewUsers = true;
})
Când apelezi SignInManager.PasswordSignInAsync(..., lockoutOnFailure: true), Identity incrementează automat contorul de încercări eșuate. La depășirea pragului, contul este blocat pentru intervalul configurat.
Poți verifica starea lockout-ului și o poți gestiona manual:
// Verificare
bool isLockedOut = await _userManager.IsLockedOutAsync(user);
// Reset manual (de ex. din panoul admin)
await _userManager.ResetAccessFailedCountAsync(user);
await _userManager.SetLockoutEndDateAsync(user, null);
Un detaliu important: GetLockoutEndDateAsync returnează un DateTimeOffset? — dacă e în viitor, contul e blocat; dacă e null sau în trecut, contul e activ.
External Providers (OAuth 2.0)
Identity se integrează nativ cu provideri externi prin middleware-ul de autentificare OAuth/OIDC. Cei mai comuni sunt Google, Microsoft, GitHub și Facebook.
Configurarea pentru Google și Microsoft în Program.cs:
builder.Services.AddAuthentication()
.AddGoogle(options =>
{
options.ClientId = configuration["Auth:Google:ClientId"]!;
options.ClientSecret = configuration["Auth:Google:ClientSecret"]!;
})
.AddMicrosoftAccount(options =>
{
options.ClientId = configuration["Auth:Microsoft:ClientId"]!;
options.ClientSecret = configuration["Auth:Microsoft:ClientSecret"]!;
});
Fluxul de login extern are două etape. Prima: redirect la providerul extern.
var redirectUrl = Url.Action("ExternalLoginCallback", "Account");
var properties = _signInManager.ConfigureExternalAuthenticationProperties(
provider, redirectUrl);
return Challenge(properties, provider);
A doua: procesarea callback-ului după autentificarea la provider.
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null) return RedirectToAction("Login");
var result = await _signInManager.ExternalLoginSignInAsync(
info.LoginProvider, info.ProviderKey, isPersistent: false);
if (!result.Succeeded)
{
// Utilizator nou — creare cont local
var email = info.Principal.FindFirstValue(ClaimTypes.Email)!;
var user = new ApplicationUser { UserName = email, Email = email };
await _userManager.CreateAsync(user);
await _userManager.AddLoginAsync(user, info);
await _signInManager.SignInAsync(user, isPersistent: false);
}
Identity stochează automat legătura dintre contul local și cel extern în tabela AspNetUserLogins, permițând unui utilizator să aibă mai mulți provideri asociați aceluiași cont.
Concluzie
ASP.NET Core Identity este un sistem matur și extensibil — acoperă 90% din nevoile de autentificare ale unei aplicații web standard. Înțelegerea mecanismelor interne (cum funcționează lockout-ul, cum sunt procesate claim-urile externe, cum se înlănțuiesc validatorii) face diferența între o implementare fragilă și una solidă.
Dacă proiectul tău crește și ai nevoie de autentificare federată la nivel enterprise — multi-tenant, SSO, RBAC avansat — pasul următor natural este Azure Entra ID sau Entra CIAM, pe care le vom explora într-un articol viitor.