.Net Core MVC Web Uygulamalarında Yetkilendirme – 1 (Scaffold Identity)

Merhaba arkadaşlar.

Son zamanlarda, vaktimin çoğunu .Net Core tarafında kendimi geliştirmek için harcıyorum. Bu süreç içerisinde; öğrenmekte zorluk çektiğim veya Türkçe kaynak eksikliği hissettiğim konularda ayrıntılı yazılar hazırlayarak; yazılım dünyasına katkıda bulunmaya çalışacağım.

Bu yazıda; Microsoft’un bize hazır olarak sunduğu Scaffold Identity (Kimlik İskele) yapısından bahsedeceğim. Böylelikle .Net Core Identity ara katmanına bir giriş yapmış olacağız, ve yapı hakkında bilgi sahibi olacağız. Devamında ise;

  • .Net Core Web Mvc projesinde kendi Identity katmanımızı oluşturma,

  • .Net Core Identity katmanında Kullanıcılar ve Roller,

  • .Net Core Identity katmanını farklı veri kaynaklarından besleme (UserStore kavramı),

  • .Net Core Web Api projesine kendi Identity katmanımızı implemente etme ve JWT Token ile Authentication

başlıklı 4 yazı daha yayınlayacağım.

Bu 5 yazıdan oluşan seriyi toplamda 6 haftada bitirmeyi hedefliyorum. Umarım başarırım 🙂

Yeni bir .Net Core Web Mvc projesi oluşturarak yazıya start verelim.

dotnet new mvc -n identityScaffoldMvc

komutunu terminalden çalıştırarak identityScaffoldMvc adında bir Mvc projesi oluşturalım. Proje oluştukdan sonra uygulamamızı çalıştıralım.

dotnet run

Örnek projenin ilk görüntüsüne hemen bakalım;

Proje gayet güzel çalışıyor 🙂 Şimdi HomeController içinde Private adında bir Action oluşturacağım.

    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }

        public IActionResult Private() {
            return Ok("Bu sayfayı sadece üye olanlar görebilecek");
        }

    }

Şimdi projeyi tekrar çalıştırıp, yeni oluşturduğumuz Private sayfasına bir bakalım

Şuan Private sayfası herkese açık bir şekilde görüntülenebilmektedir.

Buraya kadar herşey bildiğimiz gibi.. Peki burdan sonra neler olacak;

  1. Private sayfasına erişimi, sadece üye olanların görüntüleyebileceği şekilde kısıtlayacağız.

  2. Code Generation Tool‘u kullanarak Scaffold Identity yapısını projemize ekleyeceğiz. Böylece projemiz; ziyaretçilerin üye olabileceği, ve tüm üyelik işlemlerini gerçekleştirebileceği bir altyapıya sahip olacak.

  3. Üyelik işlemlerinin yapılabilmesi için ihtiyacımız olan arayüzleri (login, register, logout v.b. sayfaları) Code Generation Tool‘u kullanarak projemize dahil edeceğiz.

  4. Son olarak; kullanacağımız arayüzleri nasıl özelleştirebileceğimizden biraz bahsedip yazıyı sonlandıracağız.

Private sayfasına erişimi kısıtlamak için [Authorize] etiketiniz Action‘ımızın üstüne ekliyoruz.

        [Authorize]
        public IActionResult Private()
        {
            return Ok("Bu sayfayı sadece üye olanlar görebilecek");
        }

Erişim kısıtlandıktan sonra, Private sayfasının son durumuna bir bakalım

InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found.

hatası almaktayız..

Bu hata, bize Private sayfasının erişime kısıtlandığının bir göstergesidir. Fakat projede, gelen ziyaretçinin kimliğini doğrulayacak bir mekanizma yok. Dolayısıyla; Private sayfasına erişmeye çalıştığımızda hata alıyoruz.

Scaffold Identity yapısını Code Generation Tool kullanarak projemize ekleyeceğimizden yukarıda söz etmiştik.

Code Generation Tool; içerisinde bazı hazır kod yapıları barındırır. Scaffold Identity‘de bunlardan biridir. Code Generation Tool

ile bu hazır kod yapılarını kolayca projemize dahil edip düzenleyebiliriz.

İlk olarak Code Generation Tool‘un kurulumunu yapalım:

dotnet tool install -g dotnet-aspnet-codegenerator

Daha sonra, bu aracı projede kullanabilmek için nuget paketini projeye ekleyelim, ve projeyi restore edelim.

dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
dotnet restore

Artık Scaffold Identity yapısını projemize dahil edebiliriz.

dotnet aspnet-codegenerator identity

Şimdi tekrar projemizi çalıştıralım ve Private sayfasına ulaşmaya çalışalım

Eveett.. Artık projemizde; kullanıcı girişi yapabileceğimiz (Login), üye olabileceğimiz (Register) ve daha birçok üyelik işlemini gerçekleştireceğimiz sayfalarımız mevcut.

“Peki Scaffold Identity yapısıyla birlikte gelen dosyalar proje içerisinde nerede yer alıyor?” diyo sorabilirsiniz. Hemen yanıtlayalım

Scaffold Identity yapısı ile gelen hazır kodlar proje dizininde Areas > Identity klasörü içerisinde yer almaktadır.

Identity klasörü içerisinde;

  • IdentityStartupHosting.cs dosyası,

  • Data klasörü,

  • Pages klasörü

yer almaktadır.

IdentityStartupHosting.cs dosyasında; projeye eklediğimiz Identity yapısının configurasyonlarını değiştirebilir, kendi projenize göre yapıyı özelleştirebilirsiniz.

public class IdentityHostingStartup : IHostingStartup
{
    public void Configure(IWebHostBuilder builder)
    {
        builder.ConfigureServices((context, services) => {
            services.AddDbContext<identityScaffoldMvcIdentityDbContext>(options =>
                options.UseSqlServer(
                    context.Configuration.GetConnectionString("identityScaffoldMvcIdentityDbContextConnection")));
            services.AddDefaultIdentity<IdentityUser>()
                .AddEntityFrameworkStores<identityScaffoldMvcIdentityDbContext>();
        });
    }
}

Mevcut IdentityStartupHosting.cs sınıfında;

  • Kullanıcı kimlik bilgilerinin saklanacağı veri kaynağı (identityScaffoldMvcIdentityDbContext)

  • Kullanıcı kimlik bilgilerinin modeli (IdentityUser) tanımlanmıştır.

DbContext demişken, Scaffold Identity yapısını projeye ekledik ama hangi veri tabanını kullanacağı ile ilgili birşey belirtmedik. Fakat .Net Core, bizim için appsettings.json dosyasına bir ConnectionString değeri eklemiş.

{
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "identityScaffoldMvcIdentityDbContextConnection": "Server=(localdb)\\mssqllocaldb;Database=identityScaffoldMvc;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
}

identityScaffoldMvcIdentityDbContextConnection‘ı kullanıcı bilgilerinin saklanacağı kendi veri tabanınıza göre düzenleyebilirsiniz.

Düzenleme işlemini hallettikten sonra bilgilerin saklanacağı tabloları oluşturmamız gerekiyor. Hemen ilk migration’ı oluşturalım.

dotnet ef migrations add CreateIdentitySchema

Eğer bir hata almadıysanız,ilgili CreateIdentitySchema isimli Migration sınıfı Migrations dizininde oluşturulmuş olması gerekiyor. Veri tabanımızı güncelleyelim;

dotnet ef database update

ve oluşan tablolara bir göz atalım

Kullanıcının kimlik bilgilerinden, yaptığı login işlemlerine kadar birçok veriyi saklayabilmemize olanak sağlayacak tablolar, .Net Core tarafından bizim için oluşturuldu.

Tüm bu tablolarda hangi verilerin tutulduğunu ileriki yazılarda ayrıntısıyla anlatacağım. Şimdi Identity dizinindeki Data ve Pages klasörlerine geri dönelim.

Data klasöründe veri kaynağı ile ilgili sınıflar bulunmaktadır.

public class identityScaffoldMvcIdentityDbContext : IdentityDbContext<IdentityUser>
{
    public identityScaffoldMvcIdentityDbContext(DbContextOptions<identityScaffoldMvcIdentityDbContext> options)
        : base(options)
    {
    }
    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
        // Customize the ASP.NET Identity model and override the defaults if needed.
        // For example, you can rename the ASP.NET Identity table names and more.
        // Add your customizations after calling base.OnModelCreating(builder);
    }
}

identityScaffoldMvcIdentityDbContext.cs sınıf içerisinde DbContext sınıfınızı projenize göre düzenleyebilirsiniz.

Pages dizini içersisinde ise Scaffold Identity yapısının kullandığı arayüz dosyaları mevcuttur. Buradaki .cshtml dosyalarını düzenleyerek kullandığınız arayüzleri özelleştirebilirsiniz.

Mesela ben Login sayfasını Türkçeleştirdim.

@page
@model LoginModel

@{
    ViewData["Title"] = "Kullanıcı Girişi";
}

<h1>@ViewData["Title"]</h1>
<div class="row">
    <div class="col-md-4">
        <section>
            <form id="account" method="post">
                <h4>Lütfen giriş yapınız</h4>
                <hr />
                <div asp-validation-summary="All" class="text-danger"></div>
                <div class="form-group">
                    <label asp-for="Input.Email">E-posta </label>
                    <input asp-for="Input.Email" class="form-control" />
                    <span asp-validation-for="Input.Email" class="text-danger"></span>
                </div>
                <div class="form-group">
                    <label asp-for="Input.Password">Şifre</label>
                    <input asp-for="Input.Password" class="form-control" />
                    <span asp-validation-for="Input.Password" class="text-danger"></span>
                </div>
                <div class="form-group">
                    <div class="checkbox">
                        <label asp-for="Input.RememberMe">
                            <input asp-for="Input.RememberMe" />
                            Beni Hatırla
                        </label>
                    </div>
                </div>
                <div class="form-group">
                    <button type="submit" class="btn btn-primary">Giriş</button>
                </div>
                <div class="form-group">
                    <p>
                        <a id="forgot-password" asp-page="./ForgotPassword">Şifremi Unuttum</a>
                    </p>
                    <p>
                        <a asp-page="./Register" asp-route-returnUrl="@Model.ReturnUrl">Yeni Kullanıcı</a>
                    </p>
                </div>
            </form>
        </section>
    </div>
    <div class="col-md-6 col-md-offset-2">
        <section>
            <h4>Kullanıcı giriş sayfasını değiştirdik.</h4>
            <hr />
            @{
                if ((Model.ExternalLogins?.Count ?? 0) == 0)
                {
                    <div>
                        <p>
                            bla bla bla.. 
                        </p>
                    </div>
                }
                else
                {
                    <form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" class="form-horizontal">
                        <div>
                            <p>
                                @foreach (var provider in Model.ExternalLogins)
                                {
                                    <button type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
                                }
                            </p>
                        </div>
                    </form>
                }
            }
        </section>
    </div>
</div>

@section Scripts {
    <partial name="_ValidationScriptsPartial" />
}

ve yeni login sayfası:

Son olarak; proje ana dizinindeki Startup.cs dosyasında UseAuthentication() metodunu çağırarak yetkilendirme ara katmanını projeye dahil etmiş oluyoruz. (UseAuthentication() metodunun UseMvc() metodundan önce çağırmasına etmelisiniz.)

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
        // The default HSTS value is 30 days. You may want to change this for production scenarios, see
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseAuthentication();

    app.UseCookiePolicy();
    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
}

Ve bitirdik dostlar..

Bir sonraki yazıda kendi Identity yapımızı kendimiz oluşturacağız, ve bu yazıda yüzeysel olarak bahsettiğim bir çok konunun ayrıntılarına gireceğiz.

Projenin kaynak kodlarına buradan ulaşabilirsiniz.

Tekrar görüşmek üzere..

Last updated