ASP.NET Core 9 MVC — Bölüm 4: Identity, Authentication ve Role Tabanlı Authorization
ASP.NET Core Identity'yi MVC projesine entegre ediyoruz: custom IdentityUser, cookie ve JWT authentication, kayıt/giriş/çıkış, rol tabanlı [Authorize(Roles=...)], policy-based authorization, claims, password ve lockout ayarları — kullanıcı yönetimi ve güvenliğin tüm katmanları.
Merhaba arkadaşlar, bu makalemizde ASP.NET Core 9 MVC serisinin dördüncü bölümünde gelen konu kimlik doğrulama ve yetkilendirme. Bir önceki bölümde veritabanı katmanını kurduk; şimdi üstüne kullanıcı yönetimi, rol sistemi ve policy tabanlı yetkilendirme ekliyoruz. ASP.NET Core Identity çerçevesini tüm ayrıntılarıyla — kurulumundan özelleştirilmesine, cookie vs JWT seçiminden claims'e kadar — göreceksiniz.
Identity Nedir?
ASP.NET Core Identity, kullanıcı hesapları, şifre yönetimi, role'ler ve claim'ler için hazır bir altyapı. EF Core ile entegre gelir, şemasını migration ile oluşturur, UI template'leri bile vardır. Temel sorumlulukları:
- Kullanıcı CRUD (kayıt, giriş, şifre sıfırlama, email onayı, 2FA).
- Şifre politikaları (uzunluk, karmaşıklık).
- Role ve claim yönetimi.
- Cookie ve token tabanlı authentication scheme'leri.
- Hesap kilitleme, brute-force koruması.
- External login (Google, Microsoft, GitHub).
Her şeyi sıfırdan yazmak yerine Identity'yi kullanmak 9/10 projede doğru karar.
Identity'yi Projeye Ekleyelim
dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore --project src/SemGoksuMvc.Infrastructure
dotnet add package Microsoft.AspNetCore.Identity.UI --project src/SemGoksuMvc.Web
Özel IdentityUser'ı Türetelim
Hazır IdentityUser temel alanları içerir (UserName, Email, PasswordHash, vs.). Ek alan koymak için türetiyoruz:
// Core/Entities/Kullanici.cs
public class Kullanici : IdentityUser<int>
{
public string? AdSoyad { get; set; }
public string? AvatarUrl { get; set; }
public DateTime KayitTarihi { get; set; } = DateTime.UtcNow;
public DateTime? SonGirisTarihi { get; set; }
public string? Hakkimda { get; set; }
}
public class Rol : IdentityRole<int>
{
public string? Aciklama { get; set; }
}
Visual Studio'dan: Visual Studio tarafinda projeye sag tiklayip Add > New Scaffolded Item > Identity diyerek bu sihirbazi aciyoruz. Override etmek istediginiz sayfalari isaretleyin.
Burada int tipini primary key olarak kullandım — Identity default olarak string (GUID) kullanır ama ben integer ID'leri tercih ediyorum, hem daha az yer kaplar hem foreign key tablolarında daha rahat.
DbContext'i Güncelleyelim
// Infrastructure/Persistence/AppDbContext.cs
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
public class AppDbContext(DbContextOptions<AppDbContext> options)
: IdentityDbContext<Kullanici, Rol, int>(options)
{
public DbSet<Kategori> Kategoriler => Set<Kategori>();
public DbSet<Urun> Urunler => Set<Urun>();
protected override void OnModelCreating(ModelBuilder b)
{
base.OnModelCreating(b); // ONEMLI - Identity sema
b.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly);
// Identity tablolarinin isimlerini ozellestirelim
b.Entity<Kullanici>().ToTable("Kullanicilar");
b.Entity<Rol>().ToTable("Roller");
b.Entity<IdentityUserRole<int>>().ToTable("KullaniciRolleri");
b.Entity<IdentityUserClaim<int>>().ToTable("KullaniciClaims");
b.Entity<IdentityUserLogin<int>>().ToTable("KullaniciGirisleri");
b.Entity<IdentityUserToken<int>>().ToTable("KullaniciTokenleri");
b.Entity<IdentityRoleClaim<int>>().ToTable("RolClaims");
}
}
Identity Servislerini Kaydet
// Program.cs
builder.Services.AddIdentity<Kullanici, Rol>(options =>
{
// Sifre politikasi
options.Password.RequiredLength = 8;
options.Password.RequireDigit = true;
options.Password.RequireUppercase = true;
options.Password.RequireLowercase = true;
options.Password.RequireNonAlphanumeric = false;
// Lockout
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15);
options.Lockout.AllowedForNewUsers = true;
// Kullanici
options.User.RequireUniqueEmail = true;
// Dogrulama
options.SignIn.RequireConfirmedEmail = false; // prod'da true yapin
})
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultTokenProviders();
AddDefaultTokenProviders, email confirmation ve password reset için token üreten mekanizmayı ekler.
Cookie Authentication Konfigürasyonu
Identity default olarak cookie auth kullanır, bunu ince ayarlayalım:
builder.Services.ConfigureApplicationCookie(options =>
{
options.LoginPath = "/Hesap/Giris";
options.LogoutPath = "/Hesap/Cikis";
options.AccessDeniedPath = "/Hesap/ErisimEngellendi";
options.ExpireTimeSpan = TimeSpan.FromDays(7);
options.SlidingExpiration = true; // aktif kullanici su takdirde yeniler
options.Cookie.HttpOnly = true;
options.Cookie.SameSite = SameSiteMode.Lax;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.Cookie.Name = "SemGoksu.Auth";
});
Middleware Sırası
UseAuthentication mutlaka UseAuthorization'dan önce gelmeli:
app.UseRouting();
app.UseAuthentication(); // ONCE
app.UseAuthorization(); // SONRA
app.MapControllerRoute(...);
Migration Ekle ve Güncelle
dotnet ef migrations add IdentityEkle -p ../SemGoksuMvc.Infrastructure -s .
dotnet ef database update -p ../SemGoksuMvc.Infrastructure -s .
Şimdi DB'de Kullanicilar, Roller, KullaniciRolleri gibi tablolar oluştu.
Kayıt, Giriş, Çıkış — HesapController
public class HesapController(
UserManager<Kullanici> userManager,
SignInManager<Kullanici> signInManager) : Controller
{
[HttpGet]
public IActionResult Kayit() => View();
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Kayit(KayitViewModel vm)
{
if (!ModelState.IsValid) return View(vm);
var kullanici = new Kullanici
{
UserName = vm.KullaniciAdi,
Email = vm.Email,
AdSoyad = vm.AdSoyad
};
var sonuc = await userManager.CreateAsync(kullanici, vm.Sifre);
if (!sonuc.Succeeded)
{
foreach (var hata in sonuc.Errors)
ModelState.AddModelError(string.Empty, hata.Description);
return View(vm);
}
// Default rol
await userManager.AddToRoleAsync(kullanici, "Uye");
// Otomatik giris yap
await signInManager.SignInAsync(kullanici, isPersistent: false);
return RedirectToAction("Index", "Home");
}
[HttpGet]
public IActionResult Giris(string? returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Giris(GirisViewModel vm, string? returnUrl = null)
{
if (!ModelState.IsValid) return View(vm);
var sonuc = await signInManager.PasswordSignInAsync(
vm.KullaniciAdi,
vm.Sifre,
vm.BeniHatirla,
lockoutOnFailure: true);
if (sonuc.Succeeded)
{
// Son giris tarihini guncelle
var kullanici = await userManager.FindByNameAsync(vm.KullaniciAdi);
if (kullanici is not null)
{
kullanici.SonGirisTarihi = DateTime.UtcNow;
await userManager.UpdateAsync(kullanici);
}
return !string.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl)
? Redirect(returnUrl)
: RedirectToAction("Index", "Home");
}
if (sonuc.IsLockedOut)
ModelState.AddModelError("", "Hesabiniz gecici olarak kilitlendi");
else
ModelState.AddModelError("", "Kullanici adi veya sifre hatali");
return View(vm);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Cikis()
{
await signInManager.SignOutAsync();
return RedirectToAction("Index", "Home");
}
}
Rol Tabanlı Authorization
[Authorize] attribute'u endpoint'leri korur. En basit kullanım:
[Authorize] // giris yapmis herkes
[Authorize(Roles = "Admin")] // sadece Admin
[Authorize(Roles = "Admin,Editor")] // Admin VEYA Editor
[Authorize(Roles = "Admin")]
[Authorize(Roles = "Editor")] // Admin VE Editor (stack)
[AllowAnonymous] // public erisim
Controller seviyesinde veya action seviyesinde kullanabilirsiniz:
[Authorize(Roles = "Admin,Editor")]
public class UrunYonetimController : Controller
{
public IActionResult Index() => View(); // Admin veya Editor
[Authorize(Roles = "Admin")]
public IActionResult Sil(int id) => View(); // Sadece Admin
[AllowAnonymous]
public IActionResult PublicOrnek() => View(); // Herkes
}
Rolleri Seed Edelim
public static class IdentitySeed
{
public static async Task SeedAsync(IServiceProvider sp)
{
var roleManager = sp.GetRequiredService<RoleManager<Rol>>();
var userManager = sp.GetRequiredService<UserManager<Kullanici>>();
string[] roller = ["Admin", "Editor", "Uye"];
foreach (var r in roller)
{
if (!await roleManager.RoleExistsAsync(r))
await roleManager.CreateAsync(new Rol { Name = r, Aciklama = $"{r} rolu" });
}
// Ilk admin
var admin = await userManager.FindByEmailAsync("admin@semgoksu.com");
if (admin is null)
{
admin = new Kullanici
{
UserName = "admin",
Email = "admin@semgoksu.com",
AdSoyad = "Sistem Yoneticisi",
EmailConfirmed = true
};
await userManager.CreateAsync(admin, "Admin@2026");
await userManager.AddToRoleAsync(admin, "Admin");
}
}
}
// Program.cs'in sonunda, Run'dan once:
using (var scope = app.Services.CreateScope())
{
await IdentitySeed.SeedAsync(scope.ServiceProvider);
}
app.Run();
Policy-Based Authorization — Daha Esnek
Role yeterli gelmediği zaman policy kullanıyoruz. Örneğin "en az 18 yaşında" veya "belirli bir şirkete bağlı":
// Program.cs
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("Yetiskin", policy =>
policy.RequireClaim("DogumTarihi")
.Requirements.Add(new YetiskinGereksinimi(18)));
options.AddPolicy("YalnızAdmin", policy =>
policy.RequireRole("Admin"));
options.AddPolicy("DepartmaniIK", policy =>
policy.RequireClaim("Departman", "IK", "Insan Kaynaklari"));
});
// Requirement
public class YetiskinGereksinimi(int yasSiniri) : IAuthorizationRequirement
{
public int YasSiniri { get; } = yasSiniri;
}
// Handler
public class YetiskinHandler : AuthorizationHandler<YetiskinGereksinimi>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context, YetiskinGereksinimi gerek)
{
var claim = context.User.FindFirst("DogumTarihi");
if (claim is null) return Task.CompletedTask;
if (!DateTime.TryParse(claim.Value, out var dogum))
return Task.CompletedTask;
var yas = DateTime.Today.Year - dogum.Year;
if (dogum.Date > DateTime.Today.AddYears(-yas)) yas--;
if (yas >= gerek.YasSiniri)
context.Succeed(gerek);
return Task.CompletedTask;
}
}
// Program.cs
builder.Services.AddScoped<IAuthorizationHandler, YetiskinHandler>();
// Kullanim
[Authorize(Policy = "Yetiskin")]
public IActionResult YetiskinIcerik() => View();
Claims ve ClaimsPrincipal
Kullanıcı giriş yaptığında bir ClaimsPrincipal oluşur ve request boyunca HttpContext.User üzerinden erişilir. Claim eklemek:
// Giris sirasinda claim ekle
public async Task<IActionResult> Giris(...)
{
var kullanici = await userManager.FindByNameAsync(vm.KullaniciAdi);
if (kullanici is null) { /* ... */ }
var ekClaims = new List<Claim>
{
new("AdSoyad", kullanici.AdSoyad ?? ""),
new("Departman", "BilgiIslem"),
new(ClaimTypes.DateOfBirth, "1990-01-15")
};
await signInManager.SignInWithClaimsAsync(kullanici, isPersistent: false, ekClaims);
// ...
}
// View veya Controller'da kullanim
var adSoyad = User.FindFirstValue("AdSoyad");
var roller = User.FindAll(ClaimTypes.Role).Select(c => c.Value);
var girisYapti = User.Identity?.IsAuthenticated ?? false;
JWT Alternative — API için Daha Uygun
MVC'de cookie auth doğal seçim ama aynı app hem MVC hem API serving ediyorsa, API kısmı için JWT ekleyebilirsiniz:
builder.Services
.AddAuthentication(options =>
{
// Default MVC icin cookie kalsin
options.DefaultScheme = IdentityConstants.ApplicationScheme;
})
.AddJwtBearer("Jwt", options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]!))
};
});
// API endpoint'lerine JWT scheme belirt
[Authorize(AuthenticationSchemes = "Jwt")]
[ApiController]
[Route("api/[controller]")]
public class UrunlerApiController : ControllerBase
{
// ...
}
View Tarafında Auth Durumu
@* Layout.cshtml *@
@if (User.Identity?.IsAuthenticated == true)
{
<span>Hosgeldin @User.Identity.Name</span>
@if (User.IsInRole("Admin"))
{
<a asp-controller="Admin" asp-action="Dashboard">Admin Paneli</a>
}
<form asp-controller="Hesap" asp-action="Cikis" method="post">
<button type="submit">Cikis</button>
</form>
}
else
{
<a asp-controller="Hesap" asp-action="Giris">Giris</a>
<a asp-controller="Hesap" asp-action="Kayit">Kayit</a>
}
İpuçları ve Tuzaklar
- Prod'da email confirmation zorunlu tutun: Fake hesap korsanlığını engeller.
- 2FA'yı kolaylaştırın: Microsoft.AspNetCore.Identity.UI hazır Razor Pages ile geliyor.
- External login: Google, Microsoft, GitHub ile tek tıkla giriş — AddGoogle, AddMicrosoftAccount paketleri.
- Secret key'leri güvende tutun: JWT key'i, cookie data protection key'leri — Azure Key Vault veya environment variable'da.
- HTTPS zorunlu: Cookie'lerde SecurePolicy.Always + HSTS + HttpsRedirection.
- Lockout: Brute force koruması için açık tutun.
- Password reset: Identity default token provider'ı var, sadece email gönderme kısmını yazmalısınız.
Bu Bölümde Ne Yaptık?
ASP.NET Core Identity'yi projeye entegre ettik, custom IdentityUser + Rol ile özelleştirdik, cookie authentication'ı yapılandırdık, kayıt/giriş/çıkış akışını yazdık, rol tabanlı ve policy tabanlı authorization'ı gördük, claims'i tanıdık ve JWT'yi API için alternatif olarak kurduk.
Bir sonraki ve son bölümde performans, caching, health check'ler ve production deploy konularını işliyoruz. Response caching, output cache, memory cache, distributed cache, Serilog, health endpoint'leri, Docker, Azure App Service ve GitHub Actions ile CI/CD — seriyi güçlü bitireceğiz. Bol kolay gelsin, bir sonraki bölümde görüşürüz!