C# ile ASP.NET Core Identity: Güvenli Kimlik Doğrulama Sistemi Rehberi

Modern web uygulamalarında güvenli kimlik doğrulama ve yetkilendirme sistemleri oluşturmak, yazılım geliştirme sürecinin en kritik adımlarından biridir. ASP.NET Core Identity, Microsoft tarafından geliştirilen ve bu ihtiyacı karşılayan güçlü bir framework’tür. Bu kapsamlı rehberde, C# ile ASP.NET Core Identity’nin tüm yönlerini detaylı kod örnekleriyle inceleyeceğiz.

login_identity

Kullanıcı kaydı, giriş işlemleri, rol yönetimi, claims tabanlı yetkilendirme ve token işlemlerinden clean architecture entegrasyonuna kadar her konuyu ele alacağız.

ASP.NET Core Identity Nedir?

ASP.NET Core Identity, kullanıcı hesapları, şifreler, roller ve yetkilendirme işlemlerini yönetmek için tasarlanmış bir üyelik sistemidir. Eski ASP.NET Membership sisteminin modern ve genişletilebilir halidir. Entity Framework Core ile entegre çalışır ve veritabanı işlemlerini otomatik olarak yönetir.

Identity framework’ü, kullanıcı kaydı, giriş-çıkış işlemleri, şifre sıfırlama, email doğrulama, iki faktörlü kimlik doğrulama (2FA) ve dış kimlik sağlayıcı entegrasyonları gibi temel özellikleri hazır olarak sunar. Bu sayede sıfırdan güvenlik altyapısı geliştirmek yerine, doğrudan iş mantığınıza odaklanabilirsiniz.

Neden ASP.NET Core Identity Kullanmalıyız?

ASP.NET Core Identity kullanmanın birçok avantajı vardır. Framework, endüstri standartlarına uygun şifre hashleme algoritmaları kullanır ve güvenlik en iyi pratiklerini varsayılan olarak uygular. Claims-based kimlik doğrulama modeli sayesinde esnek yetkilendirme senaryoları oluşturabilirsiniz. Ayrıca, Entity Framework Core ile tam entegrasyon sayesinde özelleştirilebilir veri modellerine sahip olursunuz.

identity

Identity Kurulumu ve Yapılandırması

ASP.NET Core Identity’yi projenize entegre etmek için öncelikle gerekli NuGet paketlerini yüklemeniz gerekir. Visual Studio’da Package Manager Console üzerinden veya .NET CLI kullanarak bu paketleri projenize ekleyebilirsiniz.

Gerekli NuGet Paketleri

İlk adım olarak aşağıdaki NuGet paketlerini yükleyin:

// Package Manager Console'da
Install-Package Microsoft.AspNetCore.Identity.EntityFrameworkCore
Install-Package Microsoft.EntityFrameworkCore.SqlServer
Install-Package Microsoft.EntityFrameworkCore.Tools

Bu paketler, Identity framework’ünün Entity Framework Core ile çalışması için gerekli tüm bileşenleri içerir. EntityFrameworkCore.SqlServer paketi SQL Server veritabanı desteği sağlarken, EntityFrameworkCore.Tools migration işlemleri için gereklidir.

DbContext Yapılandırması

Identity kullanmak için öncelikle IdentityDbContext‘ten türeyen bir veritabanı context sınıfı oluşturmalısınız:

using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
        
        // Tablo isimlerini özelleştirme (opsiyonel)
        builder.Entity<ApplicationUser>().ToTable("Users");
        builder.Entity<IdentityRole>().ToTable("Roles");
        builder.Entity<IdentityUserRole<string>>().ToTable("UserRoles");
        builder.Entity<IdentityUserClaim<string>>().ToTable("UserClaims");
        builder.Entity<IdentityUserLogin<string>>().ToTable("UserLogins");
        builder.Entity<IdentityRoleClaim<string>>().ToTable("RoleClaims");
        builder.Entity<IdentityUserToken<string>>().ToTable("UserTokens");
    }
}

Bu yapılandırma sayesinde Identity’nin varsayılan tablo isimlerini Türkçe veya kendi isimlendirme standartlarınıza göre özelleştirebilirsiniz. OnModelCreating metodu içinde Fluent API kullanarak daha detaylı veritabanı yapılandırmaları yapabilirsiniz.

Program.cs Yapılandırması

ASP.NET Core 6.0 ve sonrası için Program.cs dosyasında Identity servislerini aşağıdaki şekilde yapılandırın:

using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

// DbContext yapılandırması
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

// Identity servisleri
builder.Services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
    // Şifre gereksinimleri
    options.Password.RequireDigit = true;
    options.Password.RequireLowercase = true;
    options.Password.RequireUppercase = true;
    options.Password.RequireNonAlphanumeric = true;
    options.Password.RequiredLength = 8;
    
    // Kullanıcı gereksinimleri
    options.User.RequireUniqueEmail = true;
    
    // Lockout ayarları
    options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
    options.Lockout.MaxFailedAccessAttempts = 5;
    options.Lockout.AllowedForNewUsers = true;
    
    // SignIn ayarları
    options.SignIn.RequireConfirmedEmail = true;
    options.SignIn.RequireConfirmedPhoneNumber = false;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();

// Cookie ayarları
builder.Services.ConfigureApplicationCookie(options =>
{
    options.LoginPath = "/Account/Login";
    options.LogoutPath = "/Account/Logout";
    options.AccessDeniedPath = "/Account/AccessDenied";
    options.ExpireTimeSpan = TimeSpan.FromDays(7);
    options.SlidingExpiration = true;
    options.Cookie.HttpOnly = true;
    options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
    options.Cookie.SameSite = SameSiteMode.Strict;
});

builder.Services.AddControllersWithViews();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

Bu yapılandırma, güvenli bir kimlik doğrulama sistemi için gerekli tüm ayarları içerir. Şifre politikaları, hesap kilitleme kuralları ve cookie güvenlik seçenekleri detaylı şekilde yapılandırılmıştır.

Özel Kullanıcı Modeli Oluşturma

Varsayılan IdentityUser sınıfı temel özellikleri içerirken, çoğu projede ek kullanıcı bilgilerine ihtiyaç duyarsınız. Özel kullanıcı modeli oluşturmak için IdentityUser‘dan türetilmiş bir sınıf tanımlayın:

using Microsoft.AspNetCore.Identity;

public class ApplicationUser : IdentityUser
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime? BirthDate { get; set; }
    public string ProfilePicture { get; set; }
    public DateTime CreatedAt { get; set; }
    public DateTime? UpdatedAt { get; set; }
    public bool IsActive { get; set; } = true;
    
    // Navigation properties
    public virtual ICollection<UserAddress> Addresses { get; set; }
    public virtual ICollection<UserOrder> Orders { get; set; }
    
    // Computed property
    public string FullName => $"{FirstName} {LastName}";
    
    public int Age
    {
        get
        {
            if (!BirthDate.HasValue)
                return 0;
                
            var today = DateTime.Today;
            var age = today.Year - BirthDate.Value.Year;
            
            if (BirthDate.Value.Date > today.AddYears(-age))
                age--;
                
            return age;
        }
    }
}

Bu model, kullanıcılarınız hakkında ek bilgiler saklamanızı sağlar. Navigation property’ler sayesinde Entity Framework Core ile ilişkili verileri de yönetebilirsiniz. Computed property’ler ise veritabanında saklanmayan ancak kullanışlı olan bilgileri sunar.

Kullanıcı Yönetimi (UserManager)

ASP.NET Core Identity’de kullanıcı işlemleri UserManager<TUser> servisi üzerinden gerçekleştirilir. Bu servis, dependency injection ile controller veya servis sınıflarınıza enjekte edilir.

Kullanıcı Kaydı İşlemi

Yeni kullanıcı oluşturmak için önce bir ViewModel tanımlayın:

using System.ComponentModel.DataAnnotations;

public class RegisterViewModel
{
    [Required(ErrorMessage = "Ad alanı zorunludur")]
    [StringLength(50, ErrorMessage = "Ad en fazla 50 karakter olabilir")]
    public string FirstName { get; set; }
    
    [Required(ErrorMessage = "Soyad alanı zorunludur")]
    [StringLength(50, ErrorMessage = "Soyad en fazla 50 karakter olabilir")]
    public string LastName { get; set; }
    
    [Required(ErrorMessage = "Email adresi zorunludur")]
    [EmailAddress(ErrorMessage = "Geçerli bir email adresi giriniz")]
    public string Email { get; set; }
    
    [Required(ErrorMessage = "Kullanıcı adı zorunludur")]
    [StringLength(50, MinimumLength = 3, ErrorMessage = "Kullanıcı adı 3-50 karakter arasında olmalıdır")]
    public string UserName { get; set; }
    
    [Required(ErrorMessage = "Şifre zorunludur")]
    [StringLength(100, MinimumLength = 8, ErrorMessage = "Şifre en az 8 karakter olmalıdır")]
    [DataType(DataType.Password)]
    public string Password { get; set; }
    
    [Required(ErrorMessage = "Şifre tekrarı zorunludur")]
    [DataType(DataType.Password)]
    [Compare("Password", ErrorMessage = "Şifreler eşleşmiyor")]
    public string ConfirmPassword { get; set; }
    
    [Required(ErrorMessage = "Doğum tarihi zorunludur")]
    [DataType(DataType.Date)]
    public DateTime BirthDate { get; set; }
}

Ardından AccountController içinde kayıt işlemini gerçekleştirin:

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;

public class AccountController : Controller
{
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly SignInManager<ApplicationUser> _signInManager;
    private readonly IEmailSender _emailSender;
    
    public AccountController(
        UserManager<ApplicationUser> userManager,
        SignInManager<ApplicationUser> signInManager,
        IEmailSender emailSender)
    {
        _userManager = userManager;
        _signInManager = signInManager;
        _emailSender = emailSender;
    }
    
    [HttpGet]
    public IActionResult Register()
    {
        return View();
    }
    
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Register(RegisterViewModel model)
    {
        if (!ModelState.IsValid)
            return View(model);
        
        var user = new ApplicationUser
        {
            UserName = model.UserName,
            Email = model.Email,
            FirstName = model.FirstName,
            LastName = model.LastName,
            BirthDate = model.BirthDate,
            CreatedAt = DateTime.UtcNow,
            IsActive = true
        };
        
        var result = await _userManager.CreateAsync(user, model.Password);
        
        if (result.Succeeded)
        {
            // Email doğrulama token'ı oluştur
            var token = await _userManager.GenerateEmailConfirmationTokenAsync(user);
            var callbackUrl = Url.Action(
                "ConfirmEmail", 
                "Account",
                new { userId = user.Id, token = token },
                protocol: Request.Scheme);
            
            // Email gönder
            await _emailSender.SendEmailAsync(
                user.Email,
                "Email Adresinizi Doğrulayın",
                $"Lütfen email adresinizi doğrulamak için <a href='{callbackUrl}'>buraya tıklayın</a>.");
            
            // Varsayılan rol ata
            await _userManager.AddToRoleAsync(user, "User");
            
            TempData["Success"] = "Kayıt başarılı! Lütfen email adresinizi doğrulayın.";
            return RedirectToAction("Login");
        }
        
        foreach (var error in result.Errors)
        {
            ModelState.AddModelError(string.Empty, error.Description);
        }
        
        return View(model);
    }
}

Bu kod, güvenli kullanıcı kaydı için gerekli tüm adımları içerir. Email doğrulama, hata yönetimi ve rol ataması otomatik olarak gerçekleştirilir.

Kullanıcı Girişi

Kullanıcı giriş işlemi için LoginViewModel oluşturun:

public class LoginViewModel
{
    [Required(ErrorMessage = "Email veya kullanıcı adı zorunludur")]
    public string UserNameOrEmail { get; set; }
    
    [Required(ErrorMessage = "Şifre zorunludur")]
    [DataType(DataType.Password)]
    public string Password { get; set; }
    
    [Display(Name = "Beni Hatırla")]
    public bool RememberMe { get; set; }
}

Giriş action metodunu implement edin:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
    if (!ModelState.IsValid)
        return View(model);
    
    // Email veya kullanıcı adı ile kullanıcıyı bul
    var user = await _userManager.FindByEmailAsync(model.UserNameOrEmail) 
               ?? await _userManager.FindByNameAsync(model.UserNameOrEmail);
    
    if (user == null)
    {
        ModelState.AddModelError(string.Empty, "Geçersiz giriş bilgileri");
        return View(model);
    }
    
    // Email doğrulama kontrolü
    if (!await _userManager.IsEmailConfirmedAsync(user))
    {
        ModelState.AddModelError(string.Empty, "Lütfen önce email adresinizi doğrulayın");
        return View(model);
    }
    
    // Hesap aktiflik kontrolü
    if (!user.IsActive)
    {
        ModelState.AddModelError(string.Empty, "Hesabınız aktif değil. Lütfen destek ile iletişime geçin");
        return View(model);
    }
    
    var result = await _signInManager.PasswordSignInAsync(
        user.UserName,
        model.Password,
        model.RememberMe,
        lockoutOnFailure: true);
    
    if (result.Succeeded)
    {
        // Giriş logunu kaydet
        user.UpdatedAt = DateTime.UtcNow;
        await _userManager.UpdateAsync(user);
        
        return RedirectToLocal(returnUrl);
    }
    
    if (result.RequiresTwoFactor)
    {
        return RedirectToAction("LoginWith2fa", new { returnUrl, model.RememberMe });
    }
    
    if (result.IsLockedOut)
    {
        return RedirectToAction("Lockout");
    }
    
    ModelState.AddModelError(string.Empty, "Geçersiz giriş bilgileri");
    return View(model);
}

private IActionResult RedirectToLocal(string returnUrl)
{
    if (Url.IsLocalUrl(returnUrl))
        return Redirect(returnUrl);
    
    return RedirectToAction("Index", "Home");
}

Bu implementasyon, güvenli giriş için tüm kontrolleri içerir. Email doğrulama, hesap kilitleme ve iki faktörlü kimlik doğrulama desteği mevcuttur.

Rol Tabanlı Yetkilendirme

Rol yönetimi, kullanıcıların erişim yetkilerini kontrol etmek için kullanılır. ASP.NET Core Identity’de roller RoleManager<TRole> servisi ile yönetilir.

Rol Oluşturma ve Yönetimi

Roller için bir service sınıfı oluşturun:

public interface IRoleService
{
    Task<IdentityResult> CreateRoleAsync(string roleName);
    Task<IdentityResult> DeleteRoleAsync(string roleName);
    Task<List<IdentityRole>> GetAllRolesAsync();
    Task<bool> RoleExistsAsync(string roleName);
}

public class RoleService : IRoleService
{
    private readonly RoleManager<IdentityRole> _roleManager;
    
    public RoleService(RoleManager<IdentityRole> roleManager)
    {
        _roleManager = roleManager;
    }
    
    public async Task<IdentityResult> CreateRoleAsync(string roleName)
    {
        if (string.IsNullOrWhiteSpace(roleName))
            throw new ArgumentException("Rol adı boş olamaz", nameof(roleName));
        
        if (await RoleExistsAsync(roleName))
            return IdentityResult.Failed(new IdentityError 
            { 
                Description = "Bu rol zaten mevcut" 
            });
        
        var role = new IdentityRole(roleName);
        return await _roleManager.CreateAsync(role);
    }
    
    public async Task<IdentityResult> DeleteRoleAsync(string roleName)
    {
        var role = await _roleManager.FindByNameAsync(roleName);
        
        if (role == null)
            return IdentityResult.Failed(new IdentityError 
            { 
                Description = "Rol bulunamadı" 
            });
        
        return await _roleManager.DeleteAsync(role);
    }
    
    public async Task<List<IdentityRole>> GetAllRolesAsync()
    {
        return await _roleManager.Roles.ToListAsync();
    }
    
    public async Task<bool> RoleExistsAsync(string roleName)
    {
        return await _roleManager.RoleExistsAsync(roleName);
    }
}

Kullanıcıya Rol Atama

Kullanıcılara rol atamak için UserManager kullanın:

public class UserService
{
    private readonly UserManager<ApplicationUser> _userManager;
    
    public UserService(UserManager<ApplicationUser> userManager)
    {
        _userManager = userManager;
    }
    
    public async Task<IdentityResult> AssignRoleToUserAsync(string userId, string roleName)
    {
        var user = await _userManager.FindByIdAsync(userId);
        
        if (user == null)
            return IdentityResult.Failed(new IdentityError 
            { 
                Description = "Kullanıcı bulunamadı" 
            });
        
        if (await _userManager.IsInRoleAsync(user, roleName))
            return IdentityResult.Failed(new IdentityError 
            { 
                Description = "Kullanıcı zaten bu role sahip" 
            });
        
        return await _userManager.AddToRoleAsync(user, roleName);
    }
    
    public async Task<IdentityResult> RemoveRoleFromUserAsync(string userId, string roleName)
    {
        var user = await _userManager.FindByIdAsync(userId);
        
        if (user == null)
            return IdentityResult.Failed(new IdentityError 
            { 
                Description = "Kullanıcı bulunamadı" 
            });
        
        return await _userManager.RemoveFromRoleAsync(user, roleName);
    }
    
    public async Task<List<string>> GetUserRolesAsync(string userId)
    {
        var user = await _userManager.FindByIdAsync(userId);
        
        if (user == null)
            return new List<string>();
        
        var roles = await _userManager.GetRolesAsync(user);
        return roles.ToList();
    }
}

Controller’da Rol Kontrolü

Rol tabanlı yetkilendirme için [Authorize] attribute’unu kullanın:

[Authorize(Roles = "Admin")]
public class AdminController : Controller
{
    [HttpGet]
    public IActionResult Dashboard()
    {
        return View();
    }
    
    [HttpGet]
    [Authorize(Roles = "Admin,SuperAdmin")]
    public IActionResult UserManagement()
    {
        return View();
    }
}

[Authorize(Roles = "User")]
public class ProfileController : Controller
{
    private readonly UserManager<ApplicationUser> _userManager;
    
    public ProfileController(UserManager<ApplicationUser> userManager)
    {
        _userManager = userManager;
    }
    
    [HttpGet]
    public async Task<IActionResult> Index()
    {
        var user = await _userManager.GetUserAsync(User);
        return View(user);
    }
}

Claims ve Policy Yönetimi

Claims-based yetkilendirme, rol tabanlı yetkilendirmeden daha esnek bir yapı sunar. Her kullanıcı için özel izinler tanımlayabilir ve bunları policy’ler ile kontrol edebilirsiniz.

Claim Ekleme ve Yönetme

Kullanıcılara claim eklemek için:

public class ClaimService
{
    private readonly UserManager<ApplicationUser> _userManager;
    
    public ClaimService(UserManager<ApplicationUser> userManager)
    {
        _userManager = userManager;
    }
    
    public async Task<IdentityResult> AddClaimToUserAsync(
        string userId, 
        string claimType, 
        string claimValue)
    {
        var user = await _userManager.FindByIdAsync(userId);
        
        if (user == null)
            return IdentityResult.Failed(new IdentityError 
            { 
                Description = "Kullanıcı bulunamadı" 
            });
        
        var claim = new Claim(claimType, claimValue);
        return await _userManager.AddClaimAsync(user, claim);
    }
    
    public async Task<IdentityResult> RemoveClaimFromUserAsync(
        string userId,
        string claimType,
        string claimValue)
    {
        var user = await _userManager.FindByIdAsync(userId);
        
        if (user == null)
            return IdentityResult.Failed(new IdentityError 
            { 
                Description = "Kullanıcı bulunamadı" 
            });
        
        var claim = new Claim(claimType, claimValue);
        return await _userManager.RemoveClaimAsync(user, claim);
    }
    
    public async Task<IList<Claim>> GetUserClaimsAsync(string userId)
    {
        var user = await _userManager.FindByIdAsync(userId);
        
        if (user == null)
            return new List<Claim>();
        
        return await _userManager.GetClaimsAsync(user);
    }
}

Policy Tanımlama

Program.cs içinde custom policy’ler tanımlayın:

builder.Services.AddAuthorization(options =>
{
    // Basit policy
    options.AddPolicy("RequireAdminRole", policy =>
        policy.RequireRole("Admin"));
    
    // Claim tabanlı policy
    options.AddPolicy("CanEditPosts", policy =>
        policy.RequireClaim("Permission", "EditPost"));
    
    // Birden fazla claim gerektiren policy
    options.AddPolicy("CanManageUsers", policy =>
        policy.RequireClaim("Permission", "ViewUser", "EditUser", "DeleteUser"));
    
    // Kombinasyon policy
    options.AddPolicy("AdminOnly", policy =>
    {
        policy.RequireRole("Admin");
        policy.RequireClaim("Department", "IT");
        policy.RequireAssertion(context =>
            context.User.HasClaim(c => c.Type == "EmployeeNumber"));
    });
    
    // Özel requirement ile policy
    options.AddPolicy("MinimumAge18", policy =>
        policy.Requirements.Add(new MinimumAgeRequirement(18)));
});

Custom Authorization Requirement

Özel yetkilendirme kuralları için requirement ve handler oluşturun:

using Microsoft.AspNetCore.Authorization;

public class MinimumAgeRequirement : IAuthorizationRequirement
{
    public int MinimumAge { get; }
    
    public MinimumAgeRequirement(int minimumAge)
    {
        MinimumAge = minimumAge;
    }
}

public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
    private readonly UserManager<ApplicationUser> _userManager;
    
    public MinimumAgeHandler(UserManager<ApplicationUser> userManager)
    {
        _userManager = userManager;
    }
    
    protected override async Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        MinimumAgeRequirement requirement)
    {
        if (!context.User.Identity.IsAuthenticated)
        {
            context.Fail();
            return;
        }
        
        var user = await _userManager.GetUserAsync(context.User);
        
        if (user?.BirthDate == null)
        {
            context.Fail();
            return;
        }
        
        var age = DateTime.Today.Year - user.BirthDate.Value.Year;
        
        if (user.BirthDate.Value.Date > DateTime.Today.AddYears(-age))
            age--;
        
        if (age >= requirement.MinimumAge)
        {
            context.Succeed(requirement);
        }
        else
        {
            context.Fail();
        }
    }
}

Handler’ı Program.cs’de kaydedin:

builder.Services.AddScoped<IAuthorizationHandler, MinimumAgeHandler>();

Policy Kullanımı

Controller’da policy kullanımı:

[Authorize(Policy = "CanEditPosts")]
public class PostController : Controller
{
    [HttpGet]
    public IActionResult Edit(int id)
    {
        return View();
    }
    
    [HttpPost]
    [Authorize(Policy = "MinimumAge18")]
    public async Task<IActionResult> PublishPost(PostViewModel model)
    {
        // Post yayınlama işlemleri
        return RedirectToAction("Index");
    }
}

Token İşlemleri ve Güvenlik

ASP.NET Core Identity, email doğrulama, şifre sıfırlama ve iki faktörlü kimlik doğrulama için token mekanizmaları sunar.

Email Doğrulama

Email doğrulama token’ı oluşturma ve doğrulama:

public class EmailConfirmationService
{
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly IEmailSender _emailSender;
    
    public EmailConfirmationService(
        UserManager<ApplicationUser> userManager,
        IEmailSender emailSender)
    {
        _userManager = userManager;
        _emailSender = emailSender;
    }
    
    public async Task<string> GenerateEmailConfirmationTokenAsync(string userId)
    {
        var user = await _userManager.FindByIdAsync(userId);
        
        if (user == null)
            throw new ArgumentException("Kullanıcı bulunamadı", nameof(userId));
        
        return await _userManager.GenerateEmailConfirmationTokenAsync(user);
    }
    
    public async Task<IdentityResult> ConfirmEmailAsync(string userId, string token)
    {
        var user = await _userManager.FindByIdAsync(userId);
        
        if (user == null)
            return IdentityResult.Failed(new IdentityError 
            { 
                Description = "Kullanıcı bulunamadı" 
            });
        
        return await _userManager.ConfirmEmailAsync(user, token);
    }
    
    public async Task SendConfirmationEmailAsync(ApplicationUser user, string callbackUrl)
    {
        var emailBody = $@"
            <h2>Email Adresinizi Doğrulayın</h2>
            <p>Merhaba {user.FirstName},</p>
            <p>Hesabınızı aktifleştirmek için lütfen aşağıdaki linke tıklayın:</p>
            <p><a href='{callbackUrl}'>Email Adresimi Doğrula</a></p>
            <p>Bu linkin geçerlilik süresi 24 saattir.</p>
        ";
        
        await _emailSender.SendEmailAsync(
            user.Email,
            "Email Adresinizi Doğrulayın",
            emailBody);
    }
}

Controller’da kullanımı:

[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> ConfirmEmail(string userId, string token)
{
    if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(token))
    {
        return BadRequest("Geçersiz email doğrulama bağlantısı");
    }
    
    var user = await _userManager.FindByIdAsync(userId);
    
    if (user == null)
    {
        return NotFound("Kullanıcı bulunamadı");
    }
    
    var result = await _userManager.ConfirmEmailAsync(user, token);
    
    if (result.Succeeded)
    {
        TempData["Success"] = "Email adresiniz başarıyla doğrulandı. Şimdi giriş yapabilirsiniz.";
        return RedirectToAction("Login");
    }
    
    return View("Error");
}

Şifre Sıfırlama

Şifre sıfırlama token işlemleri:

public class PasswordResetService
{
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly IEmailSender _emailSender;
    
    public PasswordResetService(
        UserManager<ApplicationUser> userManager,
        IEmailSender emailSender)
    {
        _userManager = userManager;
        _emailSender = emailSender;
    }
    
    public async Task<string> GeneratePasswordResetTokenAsync(string email)
    {
        var user = await _userManager.FindByEmailAsync(email);
        
        if (user == null || !await _userManager.IsEmailConfirmedAsync(user))
            throw new InvalidOperationException("Kullanıcı bulunamadı veya email doğrulanmamış");
        
        return await _userManager.GeneratePasswordResetTokenAsync(user);
    }
    
    public async Task<IdentityResult> ResetPasswordAsync(
        string email,
        string token,
        string newPassword)
    {
        var user = await _userManager.FindByEmailAsync(email);
        
        if (user == null)
            return IdentityResult.Failed(new IdentityError 
            { 
                Description = "Kullanıcı bulunamadı" 
            });
        
        return await _userManager.ResetPasswordAsync(user, token, newPassword);
    }
    
    public async Task SendPasswordResetEmailAsync(ApplicationUser user, string callbackUrl)
    {
        var emailBody = $@"
            <h2>Şifre Sıfırlama Talebi</h2>
            <p>Merhaba {user.FirstName},</p>
            <p>Şifrenizi sıfırlamak için lütfen aşağıdaki linke tıklayın:</p>
            <p><a href='{callbackUrl}'>Şifremi Sıfırla</a></p>
            <p>Bu isteği siz yapmadıysanız, bu emaili görmezden gelebilirsiniz.</p>
            <p>Bu linkin geçerlilik süresi 1 saattir.</p>
        ";
        
        await _emailSender.SendEmailAsync(
            user.Email,
            "Şifre Sıfırlama",
            emailBody);
    }
}

İki Faktörlü Kimlik Doğrulama (2FA)

2FA için gerekli yapılandırma ve kullanım:

public class TwoFactorService
{
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly SignInManager<ApplicationUser> _signInManager;
    
    public TwoFactorService(
        UserManager<ApplicationUser> userManager,
        SignInManager<ApplicationUser> signInManager)
    {
        _userManager = userManager;
        _signInManager = signInManager;
    }
    
    public async Task<IdentityResult> EnableTwoFactorAsync(string userId)
    {
        var user = await _userManager.FindByIdAsync(userId);
        
        if (user == null)
            return IdentityResult.Failed(new IdentityError 
            { 
                Description = "Kullanıcı bulunamadı" 
            });
        
        await _userManager.SetTwoFactorEnabledAsync(user, true);
        return IdentityResult.Success;
    }
    
    public async Task<string> GenerateTwoFactorTokenAsync(string userId)
    {
        var user = await _userManager.FindByIdAsync(userId);
        
        if (user == null)
            throw new ArgumentException("Kullanıcı bulunamadı", nameof(userId));
        
        return await _userManager.GenerateTwoFactorTokenAsync(user, "Email");
    }
    
    public async Task<SignInResult> TwoFactorSignInAsync(
        string code,
        bool rememberMe,
        bool rememberMachine)
    {
        return await _signInManager.TwoFactorSignInAsync(
            "Email",
            code,
            rememberMe,
            rememberMachine);
    }
}

Clean Architecture Entegrasyonu

ASP.NET Core Identity’yi katmanlı mimari içinde doğru şekilde konumlandırmak, sürdürülebilir ve test edilebilir kod yazmak için önemlidir.

Katman Yapısı

Domain Layer (Core):

// Domain/Entities/ApplicationUser.cs
public class ApplicationUser : IdentityUser
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime? BirthDate { get; set; }
}

// Domain/Interfaces/IAuthenticationService.cs
public interface IAuthenticationService
{
    Task<AuthResult> RegisterAsync(RegisterRequest request);
    Task<AuthResult> LoginAsync(LoginRequest request);
    Task<bool> LogoutAsync();
}

Infrastructure Layer:

// Infrastructure/Persistence/ApplicationDbContext.cs
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }
    
    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
        
        // Custom configurations
        builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
    }
}

// Infrastructure/Services/AuthenticationService.cs
public class AuthenticationService : IAuthenticationService
{
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly SignInManager<ApplicationUser> _signInManager;
    
    public AuthenticationService(
        UserManager<ApplicationUser> userManager,
        SignInManager<ApplicationUser> signInManager)
    {
        _userManager = userManager;
        _signInManager = signInManager;
    }
    
    public async Task<AuthResult> RegisterAsync(RegisterRequest request)
    {
        var user = new ApplicationUser
        {
            UserName = request.UserName,
            Email = request.Email,
            FirstName = request.FirstName,
            LastName = request.LastName
        };
        
        var result = await _userManager.CreateAsync(user, request.Password);
        
        return new AuthResult
        {
            Succeeded = result.Succeeded,
            Errors = result.Errors.Select(e => e.Description).ToList()
        };
    }
    
    public async Task<AuthResult> LoginAsync(LoginRequest request)
    {
        var result = await _signInManager.PasswordSignInAsync(
            request.UserName,
            request.Password,
            request.RememberMe,
            lockoutOnFailure: true);
        
        return new AuthResult
        {
            Succeeded = result.Succeeded,
            RequiresTwoFactor = result.RequiresTwoFactor,
            IsLockedOut = result.IsLockedOut
        };
    }
    
    public async Task<bool> LogoutAsync()
    {
        await _signInManager.SignOutAsync();
        return true;
    }
}

Application Layer:

// Application/DTOs/RegisterRequest.cs
public class RegisterRequest
{
    public string UserName { get; set; }
    public string Email { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Password { get; set; }
}

// Application/DTOs/AuthResult.cs
public class AuthResult
{
    public bool Succeeded { get; set; }
    public bool RequiresTwoFactor { get; set; }
    public bool IsLockedOut { get; set; }
    public List<string> Errors { get; set; } = new();
}

Presentation Layer:

// WebAPI/Controllers/AuthController.cs
[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase
{
    private readonly IAuthenticationService _authService;
    
    public AuthController(IAuthenticationService authService)
    {
        _authService = authService;
    }
    
    [HttpPost("register")]
    public async Task<IActionResult> Register([FromBody] RegisterRequest request)
    {
        var result = await _authService.RegisterAsync(request);
        
        if (!result.Succeeded)
            return BadRequest(result.Errors);
        
        return Ok(new { message = "Kayıt başarılı" });
    }
    
    [HttpPost("login")]
    public async Task<IActionResult> Login([FromBody] LoginRequest request)
    {
        var result = await _authService.LoginAsync(request);
        
        if (!result.Succeeded)
            return Unauthorized(new { message = "Giriş başarısız" });
        
        return Ok(new { message = "Giriş başarılı" });
    }
}

Best Practices ve İpuçları

ASP.NET Core Identity kullanırken dikkat edilmesi gereken önemli noktalar:

Güvenlik En İyi Uygulamaları

Şifre Politikaları: Güçlü şifre gereksinimleri belirleyin. Minimum uzunluk, büyük-küçük harf, rakam ve özel karakter zorunluluğu ekleyin.

Hesap Kilitleme: Brute force saldırılarına karşı hesap kilitleme mekanizmasını aktif edin. Başarısız giriş denemelerini sınırlayın.

Email Doğrulama: Kullanıcıların gerçek email adresleri ile kayıt olmalarını sağlayın. Email doğrulamasını zorunlu hale getirin.

HTTPS Kullanımı: Tüm kimlik doğrulama işlemlerinde HTTPS kullanın. Cookie’leri yalnızca HTTPS üzerinden iletin.

Token Güvenliği: Token’ların geçerlilik sürelerini makul seviyelerde tutun. Hassas işlemler için kısa ömürlü token’lar kullanın.

Performans Optimizasyonu

Asenkron Metodlar: Tüm Identity işlemlerinde async/await kullanın. Veritabanı işlemlerinin blocking olmamasını sağlayın.

Caching: Sık erişilen kullanıcı bilgilerini cache’leyin. Rol ve claim bilgilerini memory cache’de tutun.

Eager Loading: İlişkili verileri çekerken eager loading kullanın. N+1 sorgu probleminden kaçının.

Kod Organizasyonu

Separation of Concerns: Identity işlemlerini servis katmanına taşıyın. Controller’ları mümkün olduğunca ince tutun.

Dependency Injection: Tüm servisleri DI container üzerinden kullanın. Constructor injection tercih edin.

Error Handling: Merkezi hata yönetimi implementasyonu yapın. Kullanıcıya anlamlı hata mesajları gösterin.

Sonuç

ASP.NET Core Identity, modern web uygulamaları için kapsamlı ve güvenli bir kimlik doğrulama altyapısı sunar. Bu rehberde ele aldığımız konular ile kullanıcı yönetimi, rol tabanlı ve claims tabanlı yetkilendirme, token işlemleri ve clean architecture entegrasyonunu başarıyla gerçekleştirebilirsiniz.

Identity framework’ü esnek yapısı sayesinde her türlü iş gereksinimini karşılayabilir. Varsayılan yapılandırmaların yanı sıra özelleştirilebilir modeller, custom authorization handler’lar ve policy’ler ile ihtiyacınıza özel çözümler geliştirebilirsiniz.

Unutmayın ki güvenlik, sürekli güncel tutulması gereken bir konudur. Microsoft’un resmi dokümantasyonunu takip edin, güvenlik güncellemelerini düzenli olarak uygulayın ve best practice’lere sadık kalın.

By tanju.bozok

Software Architect, Developer, and Entrepreneur

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir