Asp.NET Core/Angular 7 – Web Api Token Authentication Kullanımı

Merhaba,

Önceki yazılarımdan olan Asp.NET MVC – Web Api Token Authentication başlıklı içeriğimde standart Asp.NET MVC’de Web API kullanırken nasıl Token Authentication uygulandığını incelemiştik. Bu içeriğimizde ise aynı güvenlik mekanizmasını Asp.NET Core çekirdeği üzerinde gerçekleştirmeyi ele alacak, .NET Core mimarisinin yapısına uygun bir şekilde durumu değerlendirecek ve nihai olarak Angular uygulamasıyla yaptığımız çalışmayı test edeceğiz.

İlk olarak uygulamamıza talepte bulunan kullanıcıya özel uygulamamızın başlangıç noktası olan “Startup.cs” dosyasında gerekli ayarlamaları yaparak sistemde öncelik olarak authentication kontrolü yapılacağını ve oturum işlemine tabi tutulmaksızın herhangi bir request’in başarıya erişemeyeceğini belirtmemiz gerekmektedir. Bunun için ilgili dosya içerisinde tanımlanmış olan “ConfigureServices” metoduna aşağıdaki gibi tanımlama yapmamız gerekiyor.

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

            var appSettingsSection = Configuration.GetSection("AppSettings");
            services.Configure<AppSettings>(appSettingsSection);
            var appSettings = appSettingsSection.Get<AppSettings>();
            var key = Encoding.ASCII.GetBytes(appSettings.Secret);
            services.AddAuthentication(scheme =>
            {
                scheme.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                scheme.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
            .AddJwtBearer(options =>
            {
                options.RequireHttpsMetadata = false;
                options.SaveToken = true;
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(key),
                    ValidateIssuer = false,
                    ValidateAudience = false
                };

                options.Events = new JwtBearerEvents
                {
                    OnTokenValidated = ctx =>
                    {
                        return Task.CompletedTask;
                    },
                    OnAuthenticationFailed = ctx =>
                    {
                        Console.WriteLine($"Exception : {ctx.Exception.Message}");
                        return Task.CompletedTask;
                    },
                    OnChallenge = ctx =>
                    {
                        return Task.CompletedTask;
                    },
                    OnMessageReceived = ctx =>
                    {
                        return Task.CompletedTask;
                    }
                };
            });
        }

        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

Yukarıdaki kod bloğunu detaylıca incelersek eğer;

  • 5. ve 8. Satır Aralığı
    Token ile ilgili validasyon yapılanmasında kullanılacak key değerini “appsettings.json” dosyasından okuyabilmek ve bunu “AppSettings” isimli bir sınıf üzerinden gerçekleştirebilmek için yapılan klasik konfigürasyon çalışmasıdır.

        public class AppSettings
        {
            public string Secret { get; set; }
        }
    

    İlgili “appsettings.json” dosyasının içeriği;

    {
      "AppSettings": {
        "Secret": "secret key"
      }
    }
    

    Şimdi burada dikkatinizi çekmek istediğim bir husus mevcut. Secret key değerine ’16’ karakterden az sayıda değer girilirse eğer token oluşturulurken aşağıdaki hatayla karşılaşılmaktadır;

    System.ArgumentOutOfRangeException: ‘IDX10603: Decryption failed. Keys tried: ‘[PII is hidden]’.
    Exceptions caught:
    ‘[PII is hidden]’.
    token: ‘[PII is hidden]’

    Hatanın görsel halinide merak edenler için aşağıya alalım;
    Asp.NET Core - Web Api Token Authentication
    Dolayısıyla bu hataya yakalanmamak için “appsettings.json” dosyasının içeriğini aşağıdaki gibi uzun bir secret key ile düzenliyorum.

    {
      "AppSettings": {
        "Secret": "gazla uçabilirsin amma frenle konamazsın..."
      }
    }
    
  • 9. ve 13. Satır Aralığı
    “AddAuthentication” metodu ile uygulamaya kimlik doğrulama servisi eklenmekte ve parametre olarak doğrulama şemaları belirtilmektedir.
  • 14. Satır
    “AddJwtBearer” metodu ile kimlik doğrulama servisine JSON Web Token eklenmektedir.
  • 16. ve 24. Satır Aralığı
    Client tarafından gönderilen request ile birlikte gelen tokenla ilgili belirli validation ayarları, meta data kontrolleri ve kayıt işlemleri vs. ile ilgili ayarlamalar gerçekleştirilmektedir.
  • 26. ve 45. Satır Aralığı
    Request neticesinde gelen tokenın doğrulanma, iptal edilme, değişme yahut kabul edilme durumlarından haberdar olabilmemiz için ilişkili eventlar tanımlanmıştır. İşleyiş sırasına göre incelersek eğer;

    1. OnMessageReceived

                          OnMessageReceived = ctx =>
                          {
                              return Task.CompletedTask;
                          }
      

      Client’tan gelen tüm talepleri, token olsun olmasın ilk karşılayan ve kabul eden eventtır.

    2. OnTokenValidated

                          OnTokenValidated = ctx =>
                          {
                              return Task.CompletedTask;
                          }
      

      Eğer ki, taleple birlikte gönderilen token geçerliyse burası tetiklenmekte ve doğrulama ile ilgili işlemler gerçekleştirilmektedir. Ardından requestin asıl hedefi olan controller tetiklenmektedir.

    3. OnAuthenticationFailed

                          OnAuthenticationFailed = ctx =>
                          {
                              Console.WriteLine($"Exception : {ctx.Exception.Message}");
                              return Task.CompletedTask;
                          }
      

      Yok eğer taleple birlikte gelen token geçersiz, eskimiş yahut bozuk ise bu event tetiklenecektir.

    4. OnChallenge

                          OnChallenge = ctx =>
                          {
                              return Task.CompletedTask;
                          }
      

      Hatalı durumlarda “OnAuthenticationFailed” eventının ardından bu event tetiklenecektir.

Tüm bu authentication konfigürasyonundan sonra sıra, yine aynı “Startup.cs” dosyasında bulunan “Configure” metodunda yukarıda tanımlanan ya da bir başka deyişle konfigüre edilen kimlik doğrulama yapısının uygulama tarafından kullanılmasını belirtmeye gelmiştir. Bu işlem için “UseAuthentication” middleware’ini kullanacağız.

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseAuthentication();
            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller}/{action=Index}/{id?}");
            });
        }

Artık uygulamamızda authentication aktif olacak bir şekilde ayarlandığı için token üretimine geçebiliriz.

İlk olarak sisteme giriş yapacak olan kullanıcıyı karşılayacak nesnemizi belirleyelim. Ben burada örnek olarak “Employee” entitysine karar verdim. Sizler ihtiyacınız doğrultusunda istediğiniz entityi oluşturabilirsiniz.

    public class Employee
    {
        public int Id { get; set; }
        public string UserName { get; set; }
        public string Password { get; set; }
    }

Ardından oluşturacağımız tokenın değerlerini tanımlama görevini üstlenecek olan “TokenDescriptor” sınıfımızı inşa edelim.

    public class TokenDescriptor
    {
        public Claim[] Claims { get; set; }
        public string Secret { get; set; }
        public DateTime ExpiresValue { get; set; }
    }

Yukarıda oluşturduğumuz “TokenDescriptor” sınıfının memberlarına göz atarsak eğer “Claims”, “Secret” ve “ExpiresValue” elemanları mevcuttur. Sırasıyla ne olduklarını incelersek eğer;

  • Claims
    Claim, oluşturulacak token üzerinde belirli bilgileri, verileri, talepleri, yetkileri vs. tutabilmek ve tüm bunları token değerine gömebilmek için tasarlanmış bir nesnedir. Dolayısıyla Claims elemanı bu nesne tipinden bir koleksiyon görevi görmekte ve tokenda tutulacak tüm harici verileri üzerinden generate sürecine dahil etme görevini üstlenmektedir.
  • Secret
    Token oluşturulurken kullanılacak secret key değerini taşımaktadır.
  • ExpiresValue
    Oluşturulacak token değerinin aktiflik süresini tutacak olan elemandır.

Ve son olarak tokenı oluşturma operasyonunu gerçekleştirecek olan sınıfımızıda inşa edelim.

    public static class GenerateToken
    {
        public static string Generate(TokenDescriptor descriptor)
        {
            var tokenHandler = new JwtSecurityTokenHandler();
            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(descriptor.Claims),
                Expires = descriptor.ExpiresValue,
                SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.ASCII.GetBytes(descriptor.Secret)), SecurityAlgorithms.HmacSha256)
            };
            var token = tokenHandler.CreateToken(tokenDescriptor);
            return tokenHandler.WriteToken(token);
        }
    }

Yukarıdaki kod bloğuna göz atarsak eğer static “GenerateToken” sınıfı oluşturulmuş ve içerisine “Generate” static metodu eklenmiştir. Dikkat ederseniz “Generate” metodu “TokenDescriptor” tipinden bir parametre almaktadır. İçerisinde “SecurityTokenDescriptor” nesnesi üzerinden “Claims”, “ExpresValue” ve “Secret” değerleri belirtilmekte ve “HmacSha256” algoritması ile şifreleme gerçekleştirilmektedir. Ardından “JwtSecurityTokenHandler” nesnesi üzerinden “CreateToken” metodu ile oluşturduğumuz “SecurityTokenDescriptor” nesnesi handler ediliyor ve sonuç metinsel olarak “WriteToken” metodu ile geriye döndürülüyor.

Artık token altyapısıyla beraber, generate işlemi içinde gerekli olan tüm yapılanma hazır bulunmaktadır. Şimdi token oluşturmak için talep gönderilecek Controller ve Action yapılanmasını sağlayalım.

    [Route("api/[controller]")]
    public class TokenController : ControllerBase
    {
        IConfiguration Configuration;
        public TokenController(IConfiguration configuration)
        {
            this.Configuration = configuration;
        }

        [HttpPost("[action]")]
        public IActionResult GetToken([FromBody]Employee model)
        {
            string secretSection = Configuration.GetSection("AppSettings").GetSection("Secret").Value;

            string token = GenerateToken.Generate(new TokenDescriptor
            {
                Claims = new Claim[]
                    {
                            new Claim("id","1"),
                            new Claim("userName", model.UserName),
                            new Claim("password", model.Password),
                            new Claim("email", "gncy@gencayyildiz.com"),
                            new Claim("country", "Türkiye")
                    },
                ExpiresValue = DateTime.UtcNow.AddMinutes(5),
                Secret = secretSection
            });
            return new JsonResult(new
            {
                Token = token
            });
        }
    }

Görüldüğü üzere “TokenController” isimli controller içerisinde tanımlanan “GetToken” metodu oluşturduğumuz “GenerateToken” sınıfı üzerinden tokenı üretmekte ve response olarak geriye döndürmektedir.

Bu noktadan itibaren tokenı üretecek alt yapıyı tam teferruatlı sağlamış bulunmaktayız. Şimdi herhangi bir Angular uygulaması üzerinden Web API’mize bağlanalım ve oluşturduğumuz yapılanma üzerinden token talep edip gerekli yetkilendirme işlemlerini gerçekleştirelim.

Angular Uygulaması Üzerinden Token Talebi ve Authentication İşlemleri

İlk olarak ayağa kaldırılmış herhangi bir Angular uygulaması üzerinden yukarıda inşa ettiğimiz token yapılanmasına talepte bulunuyoruz. Ardından elde edilen token ile Web API’a istekte bulunuyoruz.

<input type="text" #txtKullaniciAdi placeholder="Kullanıcı Adı" /><br />
<input type="text" #txtSifre placeholder="Şifre" /><br />
<button (click)="Login(txtKullaniciAdi, txtSifre)">Giriş Yap</button>
<br />
<div style="width:1000px;border-width:4px;border-color:brown; border-style:ridge;">
  <p>
    Token : <input type="text" [(ngModel)]="tokenKey" style="width:1000px;" #txtInputToken />
  </p>
</div>
<br />
<button (click)="CheckAuthorize(txtInputToken)">Oturum Kontrolü</button>
import { Component, OnInit } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {

  constructor(private httpClient: HttpClient) { }

  ngOnInit() {
  }

  tokenKey: string;
  Login(txtKullaniciAdi: HTMLInputElement, txtSifre: HTMLInputElement) {
    this.httpClient.post("https://localhost:5001/api/token/gettoken", { userName: txtKullaniciAdi.value, password: txtSifre.value }).subscribe((data: { token: string }) => this.tokenKey = data.token);
  }

  CheckAuthorize(inputToken: HTMLInputElement) {
    let _header = new HttpHeaders({
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${inputToken.value}`
    });
    this.httpClient.get("https://localhost:5001/api/home/index", { headers: _header }).subscribe(data => { });
  }
}

Yukarıdaki kod bloklarını incelersek eğer “Giriş Yap” butonuna tıklandığında “Login” fonksiyonu tetiklenecek ve bu fonksiyon aracılığıyla Web API’a girilen kullanıcı adı ve şifre değerlerine karşılık token isteğinde bulunulacaktır. Gelen token, “tokenKey” isimli değişkene atanacak ve bu şekilde ilgili değişkeni model olarak gören “txtInputToken” isimli input içerisine değer getirilecektir.

“Oturum Kontrolü” butonuna tıklandığı zaman “CheckAuthorize” fonksiyonu tetiklenecektir. Bu fonksiyon, içerisinde authentication kontrolü yapılan “HomeController” içerisindeki “Index” actionına ilgili token değeri ile talepte bulunacaktır. Eğer token doğruysa ilgili istek sonucunda “home/index” tetiklenecek, aksi taktirde tetiklenmeyecektir.

Yapılan tüm işlemleri test ettiğimizde aşağıdaki ekran görüntüsünde olduğu gibi bir süreçle karşılaşmaktayız;

Asp.NET CoreAngular 7 – Web Api Token Authentication Kullanımı


Görsele dikkat ederseniz; kullanıcı adı ve şifre girildikten sonra “Giriş Yap” butonuna talep yapıldığında requesti ilk olarak “OnMessageReceived” eventı karşılamakta ve ardından gerekli token üretimi gerçekleştirilmektedir. Üretilen token ile “Oturum Kontrolü” butonuna tıklandığında tekrardan ilk olarak “OnMessageReceived” eventı tetiklenmekte, ardından “OnTokenValidated” eventının tetiklenmesi ile tokenın doğrulandığı anlaşılmaktadır. Dolayısıyla hedef başarıyla tetiklenmekte ve işlem son bulmaktadır.

Son hamlede ise, üretilen tokenın değiştirilmesi, bozulması, yaşam süresinin sona ermesi durumlarına istinaden yapılan istekte her zamanki gibi ilk olarak “OnMessageReceived” eventı karşılamakta lakin ilgili token doğrulanamadığından dolayı “OnAuthenticationFailed” eventı ile başarısızlık durumu devreye girmekte ve en son “OnChallenge” eventını tetikleyerek süreci sonlandırmaktadır.

Görüldüğü üzere Asp.NET Core ile Web Api Token Authentication kullanımı bu şekilde ceyran etmektedir.

İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…

Bunlar da hoşunuza gidebilir...

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

*

Copy Protected by Chetan's WP-Copyprotect.