ASP.NET Core Identity: Cookie-Based Authentication

asp.net core identity api

ASP.NET Core Identity kullanıcılar üzerinde Authentication ve Authorization işlemlerini yürüten bir üyelik sistemidir. Cookie-Based Authentication ve Token-Based Authentication kimlik doğrulama işlemlerini gerçekleştirebiliriz. Bu yazıda Cookie-Based Authentication implementasyonu yapılacaktır.

Cookie-Based Authentication

Görsel incelendiğinde kullanıcı yetkiye tabi bir sayfaya erişmeye çalıştığında uygulamaya giriş yapmamışsa genel davranış itibariyle giriş sayfasına yönlendirilir. Giriş sayfasında credential bilgilerini sunarak kimlik doğrulamaya çalışır. Bu işlem authentication olarak adlandırılmaktadır. Kullanıcı uygulamaya başarıyla giriş yapmışsa authentication token içeren bir cookie oluşturulur. Kullanıcı bu cookie eşliğinde yetkiye tabi sayfaya izinleri doğrultusunda erişebilir.

Yazı boyunca kullanıcı ve roller oluşturacak, kullanıcıya roller atayacak ve kullanıcıları yetkilendirmeye tabi tutacağız. Mvc türünde bir proje oluşturalım ve Entity Framework Core implementasyonu olan Microsoft.AspNetCore.Identity.EntityFrameworkCore Identity NuGet paketini kuralım.

dotnet new mvc -n DotNetIdentity
dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore

Kullanıcı, rollerimizi temsil edecek sınıflarımızı oluşturarak başlayalım.

public class AppUser : IdentityUser
{
    public DateTime BirthDay { get; set; }
    public DateTime CreatedOn { get; set; }
}

public class AppRole : IdentityRole
{
    public DateTime CreatedOn { get; set; }
}

Görüldüğü üzere kullanıcılarımızı IdentityUser<TKey> ve rollerimizi IdentityRole<TKey> sınıfı üzerinden türetiyoruz. İhtiyaçlar doğrultusunda ilave kolonlar eklenmiştir.

public class AppDbContext : IdentityDbContext<AppUser, AppRole, string>
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
}

IdentityDbContext<TUser, TRole, TKey> sınıfından türeyen DbContext sınıfımızı oluşturduk. Program.cs içerisinde gerekli service ve middleware ayarlarını gerçekleştirelim.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<AppDbContext>(options =>
{
    options.UseSqlServer(builder.Configuration.GetConnectionString("SqlServer"));
});

builder.Services.AddIdentity<AppUser, AppRole>()
.AddEntityFrameworkStores<AppDbContext>();

builder.Services.ConfigureApplicationCookie(options =>
{
    options.LoginPath = new PathString("/User/Login");
    options.LogoutPath = new PathString("/User/Logout");
    options.AccessDeniedPath = new PathString("/Home/AccessDenied");

    options.Cookie = new()
    {
        Name = "IdentityCookie",
        HttpOnly = true,
        SameSite = SameSiteMode.Lax,
        SecurePolicy = CookieSecurePolicy.Always
    };
    options.SlidingExpiration = true;
    options.ExpireTimeSpan = TimeSpan.FromDays(30);
});

builder.Services.AddAuthentication();

var app = builder.Build();

app.UseAuthentication();

app.Run();

DbContext sınıfımızı AddDbContext methoduyla servis olarak ekliyoruz. AddIdentity methoduyla ilgili kullanıcı ve rolü belirterek yetkilendirme mekanizmasını uygulamaya ekliyoruz. Method içerisinde IdentityOptions parametresiyle kullanıcı ve şifreyle ilgili bir takım ayarlamalar yapılmıştır.

Cookie-Based Authentication kullanacağımız için uygulama cookie bilgisini ConfigureApplicationCookie methoduyla konfigüre ediyoruz. CookieAuthenticationOptions property’lerine göz atalım.

  • LoginPath kullanıcı kimliği doğrulanmadığında yönlendirileceği adrestir.
  • LogoutPath kullanıcı çıkış yaptığında yönlendirileceği adrestir.
  • AccessDeniedPath yetkilendirme başarısız olduğunda yönlendirileceği adrestir.
  • ExpireTimeSpan authentication ticket bilgisinin cookie üzerinde hangi zamana kadar saklanacağını belirler ve varsayılan olarak 14 gündür.
  • SlidingExpiration belirtilen sürenin yarısına gelindiğinde sürenin yenilenip yenilenmeyeceğini belirler.
  • Cookie
    • Name cookie ismidir, varsayılan olarak AspNetCore.Cookies değerini kullanır.
    • HttpOnly cookie bilgisinin client-side script tarafından erişilebilirliğini ayarlar.
    • SameSite cookie bilgisinin farklı siteler tarafından erişilebilirliğini ayarlar. Strict ile cookie farklı site isteklerinde taşınmaz. Lax ile bu bilgi taşınır ve None ile bir kısıtlamaya tabi olmaz.
    • SecurePolicy cookie bilgisinin secure bilgisi ayarlanır.

Kullanıcı İşlemleri

Kullanıcı sorumluluklarından sorumlu olacak Controller sınıfını oluşturup kullanıcı kayıt işlemini gerçekleştirelim.

public class SignUpViewModel
{
    public string UserName { get; set; } = default!;
    public string Email { get; set; } = default!;
    public DateTime BirthDay { get; set; }
    public string Password { get; set; } = default!;
}

public class UserController : Controller
{
    private readonly UserManager<AppUser> _userManager;

    public UserController(UserManager<AppUser> userManager)
    {
        _userManager = userManager;
    }

    public IActionResult Register() => View();
}

Kayıt işlemini yapacağımız view’ımızı oluşturalım.

@model SignUpViewModel

<form asp-action="Register">
    <div asp-validation-summary="ModelOnly"></div>
    <div>
        <label asp-for="UserName"></label>
        <input asp-for="UserName" />
        <span asp-validation-for="UserName"></span>
    </div>
    <div>
        <label asp-for="Email"></label>
        <input asp-for="Email" />
        <span asp-validation-for="Email"></span>
    </div>
    <div>
        <label asp-for="Password"></label>
        <input asp-for="Password" />
        <span asp-validation-for="Password"></span>
    </div>
    <div>
        <label asp-for="BirthDay"></label>
        <input asp-for="BirthDay" />
        <span asp-validation-for="BirthDay"></span>
    </div>
    <button type="submit">Sign up</button>
</form>

Sayfanın post edileceği ilgili Action methodumuzu yazalım.

[HttpPost]
public async Task<IActionResult> Register(SignUpViewModel viewModel)
{
    if (ModelState.IsValid)
    {
        var user = new AppUser
        {
            UserName = viewModel.UserName,
            Email = viewModel.Email,
            BirthDay = viewModel.BirthDay,
            CreatedOn = DateTime.UtcNow
        };

        var result = await _userManager.CreateAsync(user, viewModel.Password);
        if (result.Succeeded)
        {
            return RedirectToAction("Login");
        }
        result.Errors.ToList().ForEach(f => ModelState.AddModelError(string.Empty, f.Description));
    }
    return View(viewModel);
}

ViewModel ile gelen bilgiler doğrultusunda CreateAsync methoduyla bir kullanıcı oluşturulup giriş sayfasına yönlendiriliyor. Bu method bize IdentityResult tipinde bir nesne dönmektedir. Bir hata alınmışsa ilgili hatalar ModelState‘e eklenerek ViewModel ile geri gönderiliyor. İlgili hatalara Errors property’sinden erişilmektedir.

Kullanıcı oluşturabildiğimize göre artık kullanıcımızla giriş yapabiliriz.

public class SignInViewModel
{
    public string Email { get; set; } = default!;
    public string Password { get; set; } = default!;
    public bool RememberMe { get; set; } = true;
}

public IActionResult Login(string? returnUrl)
{
    if (returnUrl != null)
    {
        TempData["ReturnUrl"] = returnUrl;
    }
    return View();
}

Action methoduna karşılık düşen view’ı oluşturalım. Action methodumuza bakarsak ReturnUrl adında bir parametre almaktadır. Kullanıcı kimliğini doğrulamadan yetkilendirmeye tabi bir sayfaya erişmeye çalıştığında sayfa adresi query string‘e returnUrl key’iyle eklenerek belirtilen giriş sayfasına yönlendirilmektedir.

@model SignInViewModel

<form asp-action="Login">
    <div asp-validation-summary="ModelOnly"></div>
    <div>
        <label asp-for="Email"></label>
        <input asp-for="Email" />
        <span asp-validation-for="Email"></span>
    </div>
    <div>
        <label asp-for="Password"></label>
        <input asp-for="Password" />
        <span asp-validation-for="Password"></span>
    </div>
    <div>
        <input asp-for="RememberMe" />
        <label asp-for="RememberMe"></label>
    </div>
    <button type="submit">Sign in</button>
</form>

Sayfanın post edileceği ilgili Action methodumuzu yazalım.

[HttpPost]
public async Task<IActionResult> Login(SignInViewModel viewModel)
{
    if (ModelState.IsValid)
    {
        var user = await _userManager.FindByEmailAsync(viewModel.Email);
        if (user != null)
        {
            await _signInManager.SignOutAsync();

            var result = await _signInManager.PasswordSignInAsync(user, viewModel.Password, viewModel.RememberMe, true);

            if (result.Succeeded)
            {
                await _userManager.ResetAccessFailedCountAsync(user);
                await _userManager.SetLockoutEndDateAsync(user, null);

                var returnUrl = TempData["ReturnUrl"];
                if (returnUrl != null)
                {
                    return Redirect(returnUrl.ToString() ?? "/");
                }
                return RedirectToAction("Index", "Admin");
            }
            else if (result.IsLockedOut)
            {
                var lockoutEndUtc = await _userManager.GetLockoutEndDateAsync(user);
                var timeLeft = lockoutEndUtc.Value - DateTime.UtcNow;
                ModelState.AddModelError(string.Empty, $"This account has been locked out, please try again {timeLeft.Minutes} minutes later.");
            }
            else
            {
                ModelState.AddModelError(string.Empty, "Invalid e-mail or password.");
            }
        }
        else
        {
            ModelState.AddModelError(string.Empty, "Invalid e-mail or password.");
        }
    }
    return View(viewModel);
}

Kullanıcılarımızı e-posta adresleriyle giriş yaptıracağımız için FindByEmailAsync methoduyla kullanıcımızı elde ediyoruz. Önceki oturumlardan kalma olası cookie bilgilerini temizlemek için SignInManager<TUser> sınıfının SignOutAsync methodunu kullanıyoruz. Sonrasındaysa PasswordSignInAsync methoduyla kullanıcı ve girilen şifreyle kullanıcı giriş yaptırılıyor. Giriş işlemi başarılıysa ResetAccessFailedCountAsync ve SetLockoutEndDateAsync methodlarıyla kullanıcının ilgili lockout alanları sıfırlanmakta ve ilgili sayfaya yönlendirilmektedir. Hesap kilitlenmişse kalan süreyle ilgili hata mesajı GetLockoutEndDateAsync methoduyla ModelState‘e eklenmektedir. Bunlar dışında bir hata alınmışsa hatalar ModelState‘e eklenerek ViewModel ile kullanıcıya geri dönülmektedir.

Kullanıcılarımız giriş yapabildiğine göre artık kullanıcılarımızı çıkış yaptırabiliriz.

<a asp-controller="User" asp-action="Logout" asp-route-returnUrl="/Home/Index">
    Logout
</a>

Çıkış işlemini sağlayacak Action methodumuzu yazalım.

public async Task Logout()
{
    await _signInManager.SignOutAsync();
}

Kullanıcılar ilgili linke tıkladığında çıkış yaparak asp-route-returnUrl tag helper sayesinde belirtilen adrese yönlendirilecektir.

Yetkilendirme İşlemleri

Kullanıcılarla ilgili işlemleri tamamlamış bulunmaktayız. Artık kullanıcılarımızı bir takım yetkilendirmelere tabi tutabiliriz. Yetkilendirme işlemlerini [Authorize] attribute’ü kullanarak gerçekleştirmekteyiz. Authorize attribute’ü içerisinde Roles ve Policy isimlerinde birer property barındırmaktadır. Roles property’si sayesinde Role-Based Authorization yapabilmekteyiz. Policy property’si sayesindeyse Claim-Based Authorization ve Policy-Based Authorization yapabilmekteyiz.

Henüz bir role sahip olmadığımız için öncelikle kullanıcılarımızı listeleyelim. Sonrasındaysa rol ekleme sayfasını yazarak kullanıcılara rol atama işlemini gerçekleştirelim.

[Authorize]
public class AdminController : Controller
{
    private readonly UserManager<AppUser> _userManager;
    private readonly RoleManager<AppRole> _roleManager;

    public AdminController(UserManager<AppUser> userManager, RoleManager<AppRole> roleManager)
    {
        _userManager = userManager;
        _roleManager = roleManager;
    }

    public async Task<IActionResult> Users() => View(await _userManager.Users.ToListAsync());

    public async Task<IActionResult> Roles() => View(await _roleManager.Roles.ToListAsync());
}

Kullanıcılarımızı UserManager<TUser> sınıfının Users property’si üzerinden, rollemiziyse RoleManager<TRole> sınıfının Roles property’si üzerinden listelemekteyiz. Kullanıcılarımızı listeleyecek view’ımızı oluşturalım.

@model List<AppUser>

<table>
    <thead>
        <tr>
            <th>Id</th>
            <th>Username</th>
            <th>Email</th>
            <th>Ops</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model ?? new())
        {
        <tr>
            <td>@item.Id</td>
            <td>@item.UserName</td>
            <td>@item.Email</td>
            <td>
                <a asp-action="AssignRoles" asp-route-id="@item.Id">Assign Roles</a>
            </td>
        </tr>
        }
    </tbody>
</table>

Henüz AssignRoles sayfamız mevcut değil, rolle ilgili işlemler tamamlandığında yazılacaktır. Rollerimizi listeletecek view’ımızı oluşturalım.

@model List<AppRole>

<a asp-action="CreateRole">Create Role</a>

<table>
    <thead>
        <tr>
            <th>Id</th>
            <th>Name</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model ?? new())
        {
        <tr>
            <td>@item.Id</td>
            <td>@item.Name</td>
        </tr>
        }
    </tbody>
</table>

Rollerimizi listeledik, rol oluşturacak action methodumuzu yazalım.

public class CreateRoleViewModel
{
    public string Name { get; set; } = default!;
}

public async Task<IActionResult> CreateRole() => View();

[HttpPost]
public async Task<IActionResult> CreateRole(CreateRoleViewModel viewModel)
{
    if (ModelState.IsValid)
    {
        var role = new AppRole() { Name = viewModel.Name, CreatedOn = DateTime.Now };

        var result = await _roleManager.CreateAsync(role);
        if (result.Succeeded)
        {
            return RedirectToAction("Roles");
        }
        result.Errors.ToList().ForEach(f => ModelState.AddModelError(string.Empty, f.Description));
    }
    return View(viewModel);
}

Rol oluşturmak için ilgili manager sınıfının CreateAsync methodunu kullanmaktayız.

Artık kullanıcılarımıza oluşturduğumuz rolleri atayabiliriz. AssisnRoles action methodunu yazalım.

public class AssignRoleViewModel
{
    public string RoleId { get; set; } = default!;
    public string RoleName { get; set; } = default!;
    public bool IsAssigned { get; set; }
}

public class UserRolesViewModel
{
    public string Id { get; set; } = default!;
    public List<AssignRoleViewModel> Roles { get; set; } = new();
}

public async Task<IActionResult> AssignRoles(string id)
{
    var user = await _userManager.FindByIdAsync(id);

    var userRoles = await _userManager.GetRolesAsync(user);

    var roles = await _roleManager.Roles.Select(s => new AssignRoleViewModel
    {
        RoleId = s.Id,
        RoleName = s.Name,
        IsAssigned = userRoles.Any(a => a == s.Name)
    }).ToListAsync();

    return View(new { Id = user.Id, Roles = roles });
}

Kullanıcıya rol atayacağımız view’ımızı tasarlayalım.

@model UserRolesViewModel

<form asp-action="AssignRoles">
    <div asp-validation-summary="ModelOnly"></div>

    <input asp-for="@Model.Id" type="hidden" />

    @for (var i = 0; i < Model?.Roles.Count(); i++) 
    { 
        <input asp-for="@Model.Roles[i].IsAssigned" />
        <label asp-for="@Model.Roles[i].IsAssigned">@Model.Roles[i].RoleName</label>
        <input asp-for="@Model.Roles[i].RoleId" type="hidden" />
        <input asp-for="@Model.Roles[i].RoleName" type="hidden" />
    }

    <button type="submit">Assign Roles</button>
</form>

Kullanıcıya atanmış rollerin post edileceği ilgili Action methodumuzu yazalım.

[HttpPost]
public async Task<IActionResult> AssignRoles(UserRolesViewModel viewModel)
{
    if (ModelState.IsValid)
    {
        var user = await _userManager.FindByIdAsync(viewModel.Id);

        foreach (var item in viewModel.Roles)
        {
            if (item.IsAssigned)
            {
                await _userManager.AddToRoleAsync(user, item.RoleName);
            }
            else
            {
                await _userManager.RemoveFromRoleAsync(user, item.RoleName);
            }
        }

        return RedirectToAction("Users");
    }

    return View(viewModel);
}

Rollerimizi liste şeklinde alarak içerisinde dönüyoruz. İlgili rol seçilmişse AddToRoleAsync methoduyla kullanıcıya ekliyor aksi durumda RemoveFromRoleAsync methoduyla siliyoruz.

Role-Based Authorization

Kullanıcı ve rollerle ilgili işlemleri bitirdiğimize göre sayfalarımıza rol bazlı yetkilendirme uygulayarak kaynaklarımızı güven altına alabiliriz.

Bir controller sınıfı oluşturarak action methodlarımızı Authorize attribute’üyle işaretleyip ilgili rolleri Roles property’si üzerinden case-sensitive olarak verelim. Birden fazla rol verebiliriz.

public class RoleBasedController : Controller
{
    [Authorize(Roles = "Admin")]
    public IActionResult Admin() => Content("Admin content");

    [Authorize(Roles = "Moderator, Admin")]
    public IActionResult Moderator() => Content("Moderator content");

    [Authorize(Roles = "User, Moderator, Admin")]
    public IActionResult User() => Content("User content");
}

İlgili role sahip olmayan kullanıcılar artık ilgili action methodlara erişemeyeceklerdir. Action methodları yerine controller sınıfı da doğrudan Authorize attribute’üyle işaretlenebilir. Bu durumda tüm action methodları yetkilendirmeye tabi olacaktır. Bu senaryoda dışarıya açmak isteyeceğimiz bir action methodumuz olursa [AllowAnonymous] attribute’ünü kullanmalıyız.

Claim-Based Authorization

Claim‘ler kullanıcılar hakkında key-value pair şeklinde bilgi tutan parçacıklardır. Bir içerik yönetim sistemimiz olduğunu düşünelim. Yeni kullanıcılara bir süreliğine yorum izni vermemek istiyorsunuz. Bunu rol bazlı yetkilendirme uygulayarak çözemeyiz. Kullanıcının üye olduğu tarihin claim olarak eklenerek claim bazlı yetkilendirme uygulanmalıdır. Rol bazlı yetkilendirme mekanizmasına esneklik katmaktadır.

Konuyu pekiştirmek adına kullanıcıya bağlı olduğu departmanı claim olarak ekleyelim ve sayfalara departman bazlı erişim sağlayalım.

var user = await _userManager.FindByNameAsync(User.Identity?.Name);
var userClaims = await _userManager.GetClaimsAsync(user);
var departmentClaim = userClaims.FirstOrDefault(a => a.Type == "Department");
var claimsToAdd = new Claim("Department", "HR");
if (departmentClaim != null)
{
    await _userManager.ReplaceClaimAsync(user, departmentClaim, claimsToAdd);
}
else
{
    await _userManager.AddClaimAsync(user, claimsToAdd);
}

Kullanıcıya atanmış claim’ler GetClaimsAsync methoduyla elde ediliyor. Claim’ler arasında departman claim’i varsa claim ReplaceClaimAsync methoduyla değiştiriliyor, yoksa AddClaimAsync methoduyla ekleniyor.

Claim bazlı yetkilendirme yapabilmek için Policy‘ler oluşturmalıyız. Bu işlem için AddAuthorization methodunun AuthorizationOptions property’sindeki AddPolicy methodundan faydalanacağız.

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("HrDepartmentPolicy", policy =>
    {
        policy.RequireClaim("Department", "HR");
    });

    options.AddPolicy("AccountingDepartmentPolicy", policy =>
    {
        policy.RequireClaim("Department", "Accounting");
    });
});

Claim bazlı yetkilendirme yapacağımız controller sınıfımızı oluşturarak action methodlarımızı Authorize attribute’üyle işaretleyip oluşturduğumuz policy’leri Policy property’si üzerinden belirtelim.

public class ClaimBasedController : Controller
{
    [Authorize(Policy = "HrDepartmentPolicy")]
    public IActionResult HR() => Content("HR content");

    [Authorize(Policy = "AccountingDepartmentPolicy")]
    public IActionResult Accounting() => Content("Accounting content");
}

İlgili claim’e sahip olmayan kullanıcılar artık ilgili action methodlara erişemeyeceklerdir. Authorize attribute’ünü Policy değeriyle birlikte doğrudan controller sınıfına da verebiliriz.

Policy-Based Authorization

Policy oluştururken bir claim kontrolünden fazlasına ihtiyaç duyabilir ve bir takım koşulların gerçekleşmesini isteyebiliriz. Bu durumda ilke bazlı yetkilendirme kullanılarak gereksinimler karşılanmalıdır.

Öncelikle kullanıcı kayıt tarihini çalışma zamanında claim listemize eklememiz gerekmektedir. Bu işlem için IClaimsTransformation arayüzünü implemente eden bir sınıfa ihtiyaç duymaktayız.

public class ClaimsTransformation : IClaimsTransformation
{
    private readonly UserManager<AppUser> _userManager;

    public ClaimsTransformation(UserManager<AppUser> userManager)
    {
        _userManager = userManager;
    }

    public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
    {
        var identity = principal.Identity as ClaimsIdentity;
        var user = await _userManager.FindByNameAsync(identity?.Name);
        if (user != null)
        {
            if (!principal.HasClaim(c => c.Type == "CreatedOn"))
            {
                var creationClaim = new Claim("CreatedOn", user.CreatedOn.ToShortDateString());
                identity?.AddClaim(creationClaim);
            }
        }

        return principal;
    }
}

İhtiyaç duyduğumuz claim’i eklediğimize göre karşılanması gereken koşulu belirtebiliriz.

public class NewMemberRestraintRequirement : IAuthorizationRequirement { }

public class NewMemberRestraintHandler : AuthorizationHandler<NewMemberRestraintRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, NewMemberRestraintRequirement requirement)
    {
        var creationClaim = context.User.FindFirst(f => f.Type == "CreatedOn");
        if (creationClaim != null)
        {
            var restraintExpires = Convert.ToDateTime(creationClaim.Value).AddDays(7);
            if (DateTime.Now > restraintExpires)
            {
                context.Succeed(requirement);
            }
            else
            {
                context.Fail();
            }
        }

        return Task.CompletedTask;
    }
}

Yukarıda öncelikle claim listemize eklediğimiz kayıt tarihi elde ediliyor. Sonrasındaysa üyeliğinden bu yana bir hafta geçip geçmediği kontrol ediliyor. Bir haftayı aşması durumunda koşul başarılı aksi durumda başarısız sayılacaktır.

Program.cs dosyası içerisinde gerekli düzenlemeleri yapalım.

builder.Services.AddScoped<IClaimsTransformation, ClaimsTransformation>();
builder.Services.AddScoped<IAuthorizationHandler, NewMemberRestraintHandler>();

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("NewMemberPolicy", policy =>
    {
        policy.Requirements.Add(new NewMemberRestraintRequirement());
    });
});

Controller sınıfımıza ilgili action methodu ekleyerek Authorize attribute’ünün Policy property’sini ayarlayalım.

public class ClaimBasedController : Controller
{
    [Authorize(Policy = "NewMemberPolicy")]
    public IActionResult Comment() => Content("Comment content");
}

Kullanıcı üyeliği 7 günden fazla olmayan kullanıcılar artık ilgili action methodlara erişemeyeceklerdir.

Geliştirilen proje dosyalarına buradan ulaşabilirsiniz.

You may also like...

2 Responses

  1. Adil dedi ki:

    Cok guzel yazi Identity konusunda bildiklerinizi tazeleme maksadi ile geldiyseniz, Eger Identity hakkinda birsey bilmiyorsaniz bu yaziyi onermek biraz zor: sondaki “Claim-Based Authorization” ve “Policy-Based Authorization” basliklarindan dolayi, cunki, bu basliklar hic bir detala/inceliklerine inmeden anlatilmis. Bence “Claim-Based Authorization”-in temel taslarindan 2-si olan: “ClaimsIdentity” ve “ClaimsPrincipal” :hakkinda bir-iki bir sey anlatilmaliydi, ben internetde anlasilir bir aciklama ile karsilasmadim bu 2 sey hakkinda, rica etsem siz aydinlatirsaniz beni cok memnun olurum.

    Bence, soyledigim bu 2 baslik disinda, yazi cok akici, anlasilir dilde ve kismen detalli yazilmis, yani Identity konusunu yeni giris etmek ve tabiiki bildiklerini tazelemek maksatli gelenler icin guzel bir kaynak olmus.

Bir yanıt yazın

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