Asp.net Core 3.1. Token Based Authentication with Jwt (Oauth 2.0) – A&A part 5

Merhaba ;

Geliştirdiğimiz uygulamaların çoğunda bir kullanıcı doğrulama mekanizmasına ihtiyaç duyarız.Geçmişte çeşitli basic yöntemlerde (Cookie Based vs) bu mekanizmaları geliştirdik.

Şimdi ise cookie based kullanıcı doğrulama yöntemleri yerine token based sistemler kullanılmaktadır. Bunun nedeni artık uygulamaların dağıtık tabanlı ( Mivroservisler, Mobil Uygulamalar vs ) olmasıdır.

Bu iki sistemi karşılaştıracak olur isek ;

Cookie Based Authentication

  • Authenticate için verilen servise kullanıcı adı ve şifre gönderilir
  • Server Veriyi validate edip kullanıcıya cookie gönderir ve client browserına kaydedilir
  • Client –> servera bir request de bulunduğunda bu cookie headera eklenip gönderilir
  • Server cookie validasyonu yapıp ilgili servisi çalıştırır.

Token Based Authentication

  • Authenticate için verilen servise kullanıcı adı ve şifre gönderilir
  • Server(Api) validasyon işlemi yapar ve JWT(Json Web Token) oluşturur ve clienta döner.(http 200 OK)
  • Client – servera bir request de bulunduğunda requestin headerına “Authorization: Bearer JWT(serverın oluşturup bize döndüğü token)” şeklinde bir veri gönderilir
  • Server JWT validasyonu yapar ve ilgili apiyi çalıştırır.

Not: Biz JWT kullanacağız fakat siz çeşitli yapılar veya custom tokenda kullanabilirsiniz.


JWT (Json Web Token) Nedir ?

JWT (JSON Web Tokens), IETF kuruluşu tarafından tasarlanan standart bir token biçimidir (Bkz. RFC 7519). Haberleşen iki veya daha fazla sistem (Web, Mobile, IOT, Cloud vb.) arasında kullanıcı doğrulama, kullanıcı tanıma, veri bütünlüğünü ve bilgi güvenliğini koruma gibi noktalarda kullanılmaktadır.

https://jwt.io -> Buradan Jwt validasyonu yapabilirsiniz.

JWT, Base64 biçiminde kodlanmış 3 ayrı JSON parçasından oluşmaktadır. Parçalar nokta (.) sembolüyle ayrılmakta ve bir bütün olarak JWT’yi temsil etmektedir.

Bunlar ;

  • Header   ( Token Tipi ve Algoritma bilgileri )
  • Payload  ( Veri taşınan kısım )
  • Signature  ( Doğrulama kodu )

Token Based Yapıyı Asp.net Core 3.1. Web Api Projesine Dahil Etme.

İlk önce  Appsettings.json da token için gerekli tanımlamaları yapıyoruz.

  • Audience : Token kullanılacak , dinleyecek adresler.
  • Issuer: Tokenı dağıtacak adresler.
  • AccessTokenExpiration: Access Token geçerlilik süresi.
  • RefreshTokenExpiration: Refresh token geçerlilik süresi.
  • SecurityKey: Token imzası için kullanılacak key.

Daha sonra bu tanımladığımız değişkenleri bir class’a cast etmek ve ugulama üzerinden daha rahat kullanmak için TokenOptions isimli classı oluşturuyoruz.

Daha sonra startup.cs de  TokenOptions.cs ‘ e  Appsettings.json da tanımladığımız değişkenlerin set edilmesini sağlıyoruz.

var tokenopts = Configuration.GetSection(“TokenOptions”).Get<TokenOptions>();

Şimdi ise  bir sınıf içerisinde bu imzalama işleri ile ilgili işlemlerimizi yapalım. Burada token oluştururken ve tokenı onaylarken aynı keyi kullanacağımız için SymmetricSecurityKey dönüş tipinde bir metodumuz olacak.

Sırada ki adımda ise startup.cs e dönüp token ile ilgili tanımalamarı yapıyoruz ;

İlk önce Configure metodu altına Authentication kullanacağımızı (Middleware) belirtiyoruz.

app.UseAuthentication();

Aşağıda yazan paketi console’dan projemize dahil ediyoruz.

install-package Microsoft.AspNetCore.Authentication.JwtBearer

ConfigureServices altında Jwt tanımlamalarını yapıyoruz.

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(jwtopt =>
{
jwtopt.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
{
ValidateAudience = true,
ValidateIssuer = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = tokenopts.Issuer,
ValidAudience = tokenopts.Audience,
IssuerSigningKey = SignHandler.GetSecurityKey(tokenopts.SecurityKey),
ClockSkew = TimeSpan.Zero
};
});

Burada “ JwtBearerDefaults.AuthenticationScheme “ yazan kısımda Jwt nin default (Bearer oauth 2.0) temasını kullanacağımızı belirtiyoruz.Token mekanızmamızın Audience ve Issuer doğrulamasını yapmasını istiyoruz. Gerekli parametleri veriyor ve ayarlamaları bitiriyoruz.

Şimdi token için bir class oluşturuyoruz ;

ITokenHandler Interfacesini oluşturuyoruz.

public interface ITokenHandler
{
AccessToken CreateAccessToken(User user);
}

TokenHandler classını oluşturuyoruz.

 using Authentication.Domain.Entity;
 using Microsoft.Extensions.Options;
 using Microsoft.IdentityModel.Tokens;
 using System;
 using System.Collections.Generic;
 using System.IdentityModel.Tokens.Jwt;
 using System.Security.Claims;
 using System.Security.Cryptography;
 using System.Text;

namespace Authentication.Domain.Token
 {
     public class TokenHandler : ITokenHandler
     {
         private readonly TokenOptions tokenOpt;    
public TokenHandler(IOptions<TokenOptions> _tokenOpt)
    {
        this.tokenOpt = _tokenOpt.Value;
    }
    private IEnumerable<Claim> GetClaims(User user)
    {
        var claims = new List<Claim>
       {
          new Claim(ClaimTypes.NameIdentifier,user.Id.ToString()),
           new Claim(ClaimTypes.Name,$"{user.Name}{user.SurName}"),
            new Claim(JwtRegisteredClaimNames.Email,user.Email.ToString()),
             new Claim(JwtRegisteredClaimNames.Jti,Guid.NewGuid().ToString()),
       };

        return claims;
    }
    public AccessToken CreateAccessToken(User user)
    {
        var accessTokenExp = DateTime.Now.AddMinutes(tokenOpt.AccessTokenExpiration);

        var securityKey = SignHandler.GetSecurityKey(tokenOpt.SecurityKey);

        SigningCredentials signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256Signature);

        JwtSecurityToken jwtSecurityToken = new JwtSecurityToken(
            issuer: tokenOpt.Issuer,
            audience: tokenOpt.Audience,
            expires: accessTokenExp,
            notBefore: DateTime.Now,
            claims: GetClaims(user),
            signingCredentials: signingCredentials
            );

        var handler = new JwtSecurityTokenHandler();
        var token = handler.WriteToken(jwtSecurityToken);


        AccessToken accessToken = new AccessToken();
        accessToken.Token = token;
        accessToken.Expiration = accessTokenExp;
        accessToken.RefreshToken = CreateRefreshToken();
        return accessToken;
    }

    private string CreateRefreshToken()
    {
        var numByte = new Byte[32];
        using (var rng = RandomNumberGenerator.Create())
        {
            rng.GetBytes(numByte);
            return Convert.ToBase64String(numByte);
        }

    }
}
}

Oluşturduğumuz tokenı db ye yazmak için gerekli entityi tanımlıyoruz.

    public class UserToken : BaseEntity
     {
         public virtual long UserId { get; protected set; }
         public User User { get; protected set; }
         public string RefreshToken { get; protected set; }
         public DateTime ExpirationDate { get; protected set; }
         public UserToken() { }   
         public UserToken(long _UserId, string _RefreshToken, DateTime _ExpirationDate)
    {
        this.UserId = _UserId;
        this.RefreshToken = _RefreshToken;
        this.ExpirationDate = _ExpirationDate;
    }

       }

Tanımladığımız entityi de contexte eklemeyi unutmuyoruz.

public DbSet<UserToken> UserToken { get; set; }

UserToken için gerekli repo ve UnitOfWork ekleme işlemlerini yapıyoruz.(bu kısmın önceki postlardan nasıl yapıldığını öğrenebilirsiniz)

AuthenticationService de Access token için bir create metodu yazıyoruz.

private async Task<AccessToken> CreateAccessToken(User user){        
var accessToken = tokenHandler.CreateAccessToken(user);

        var usertkn = uow.UserToken.GetUserTokenByUserId(user.Id);

        if (usertkn != null)
        {
            uow.UserToken.DeleteUserToken(usertkn);
        }

        UserToken userToken = new UserToken(user.Id, accessToken.RefreshToken, accessToken.Expiration.AddMinutes(tokenOpt.RefreshTokenExpiration));
        await uow.UserToken.InsertAsync(userToken);
        uow.Complete();

        return accessToken;

Aynı serviste bulunan ValidateUserAsync metodunu ise şu şekilde değiştiriyoruz.


public async Task<ValidateUserResponseDTO> ValidateUserAsync(ValidateUserRequestDTO request)
        {
 
            if (String.IsNullOrEmpty(request.UserName) || String.IsNullOrEmpty(request.Password))
            {
                throw new BusinessException(ResponseCode.UserNameOrUserPasswordNotNull);
            }
           
            var user = await uow.User.ValidateUser(request.UserName, request.Password);
 
            var accessToken = await this.CreateAccessToken(user);
 
            var result = Imapper.Map<ValidateUserResponseDTO>(user);
 
            result.Token = accessToken.Token;
            result.RefreshToken = accessToken.RefreshToken;
            result.TokenExpireDate = accessToken.Expiration;
 
            return result;
        }

Son olarak Controllerda token ile validasyon yapmak istediğimiz metodları [Authorize] attribute ile etiketliyoruz.

Not:  ITokenHandler i DI için hazırlamayı ve migration yapmayı ve TokenOptions ı istediğimiz yerde kullanmak için startup.cs e aşağıdaki kodu eklemeyi unutmayalım.

services.Configure<TokenOptions>Configuration.GetSection(“TokenOptions”));

Şimdi gelelim test etmeye ;

Swagger üzerinden validate metodumuza attığımız istek sonucu aşağıdaki gibi bir sonuç dönüyor.

Daha sonra burada yazan token value sunu alıp sap üst köşede yer alan authorize butonuna basıyor ve açılan pencerede boş kutucuğa “Bearer ” keyini ve bize dönülen tokenı yapıştırıp Authorize butonuna tıklıyoruz.

En son olarak Authorize attribute ile etiketlediğimiz metoda istekte bulunuyoruz ve 200 OK döndüğünü görüyoruz.

Proje Repo Adresi : https://github.com/aburakbasaran/Authentication.git

Bir cevap yazın

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

Post comment