Derinlemesine yazılım eğitimleri için kanalımı takip edebilirsiniz...

Asp.NET Core 3.1 ile Token Bazlı Kimlik Doğrulaması ve Refresh Token Kullanımı(JWT)

Merhaba,

Günümüz web uygulamalarında veri güvenliğini sağlayabilmek için kullanıcı doğrulama yöntemleri arasından en etkili ve çağdaş olanlarından kabul edebileceğimiz Token Bazlı Kimlik Doğrulama yöntemi ile sistemsel ve kullanıcı açısından en az maliyette en yüksek verimi elde edebilmekte ve güvenli bir şekilde gerekli authorization işlemlerini gerçekleştirebilmekteyiz.

Bu makalemizde Asp.NET Core uygulamalarında Token Bazlı Kimlik Doğrulama yapılanmasını a’dan z’ye inceleyeceğiz. Esasında daha önceden bu konu üzerine kâh Asp.NET mimarisi için olsun kâh Node.JS için olsun birçok makale karalamış bulunmaktayım. İlgili makaleleri aşağıda listelersek eğer;

Görüldüğü üzere konuya dair bloğumuzda önceden birçok kaynak mevcuttur. Peki hoca, madem konuya dair önceden karaladıkların var. Ne diye tekrar aynı konuda içerik oluşturuyorsun? sorunuzu duyar gibiyim. Biliyorsunuz ki son zamanlarda Asp.NET Core Identity üzerine detaylı yazı dizisi kaleme almaktayım. İşte bu yazı dizisinde authentication yöntemlerinden biri olarak yer alacak ve konuyu sıfırdan ele alıp ve adım adım kodlama standartlarını belirleyecek bir içerik ortaya koyma niyetindeyim. Ayriyetten diğer makalelerde bulamayacağınız terminolojilerle birlikte farklı tanımlamalar ve yöntemlerde ele alınmış olacaktır.

İşte bu niyetle yeni bir minvalde yol alacağız… O halde haydi gelin başlayalım.

Başlarken

Makalemize başlarken bir adet Asp.NET Core Web API projesinin hali hazırda bulunmasıyla birlikte gerekli authentication kontrolünde bizlere eşlik edecek olan aşağıdaki “Users” tablosunu tasarlamamız gerekmektedir.

    public class User
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Surname { get; set; }
        public string Email { get; set; }
        public string Password { get; set; }
        public string RefreshToken { get; set; }
        public DateTime? RefreshTokenEndDate { get; set; }
    }

Users tablosunu Code First yahut Database First yaklaşımlarından herhangi biriyle tasarlayıp, oluşturabilirsiniz.

Oluşturulan “Users” tablosuna tekrar göz atarsak eğer standart kolonların dışında “RefreshToken” ve “RefreshTokenEndDate” kolonları mevcuttur. Bu kolonların ne amaçla oluşturulduğuna değinebilmek için öncelikle üretilecek token yapılanmasıyla ilintili “Access Token” ve “Refresh Token” terimlerini açıklamamız gerekmektedir.

  1. Access Token
    OAuth 2.0 protokolüne göre RFC 7519 standartında olan ve authorization neticesinde belirli bir expire süresine bağlı bir şekilde üretilip kullanıcıya sunulan güvenli bir anahtar değeridir. Anlayacağınız token’ın ta kendisidir 🙂
  2. Refresh Token
    Access token’ın expire süresi sona ermeye yaklaştığında veya sona erdiğinde yeni bir access token üretebilmek için kullanılan token değeridir. Kullanıcıya verilen access token değeri yanında refresh token değeri verilerek, access token süresi dolduğu taktirde bu refresh token ile yeni token talebinde bulunabilecektir. Böylece kullanıcı token süresi dolduğu taktirde oturumdan düşürülmeden yeni token elde edebilecek ve yoluna devam edecektir.

Dikkat!!! Her zaman Refresh Token Expiration Access Token Expiration’dan fazla olmalıdır!

İşte “Users” tablosundaki “RefreshToken” kullanıcı için üretilmiş olan refresh token değerini tutacak olan kolondur. “RefreshTokenEndDate” kolonu ise üretilen refresh token değerinin işlev/kullanım süresini belirleyecek olan zaman bilgisini tutan alandır.

Token Servisinin Uygulamaya Eklenmesi

Asp.NET Core uygulamasında token ile authentication işlemlerini gerçekleştirebilmek için JWT Token servisini uygulamaya eklememiz gerekmektedir. Bunun için “Startup.cs” dosyasındaki “ConfigureServices” metodu içerisinde “AddAuthentication” middleware’i aracılığıyla bir şema oluşturarak işe başlamalıyız. İlgili metodu aşağıdaki görselde olduğu gibi çağırarak ikinci overloadına göz atarsak eğer;
Asp.NET Core - Token Bazlı Kimlik Doğrulama(JWT)
görüldüğü üzere bir default scheme değeri istemektedir.
Peki bizden istenen bu default scheme değeri nedir?
Bir authentication işlemini farklı pozisyonlar için farklı şekilde tanımlamak isteyebiliriz.
Örneğin; ‘Bayi Authentication‘ – ‘Kullanıcı Authentication‘ gibi…
İşte bu mantıkta bir isimlendirmeyle farklı authentication şeması tanımlanabilir. Bizler ister benzer şekilde opsiyonel değerler tanımlayarak şema belirleyebilir yahut default şema ayarı vererek buradaki ihtiyaca daha spesifik çözüm getirebiliriz. Eğer ki tercihimiz default şema ayarı ise bunun için “Microsoft.AspNetCore.Authentication.JwtBearer” kütüphanesindeki “JwtBearerDefaults” sınıfını kullanmamız gerekecektir.

Velhasıl, JWT Token servisini uygulamaya entegre edersek;

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

        public IConfiguration Configuration { get; }
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(option =>
            {
                option.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateAudience = true,
                    ValidateIssuer = true,
                    ValidateLifetime = true,
                    ValidateIssuerSigningKey = true,
                    ValidIssuer = Configuration["Token:Issuer"],
                    ValidAudience = Configuration["Token:Audience"],
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Token:SecurityKey"])),
                    ClockSkew = TimeSpan.Zero
                };
            });
            .
            .
            .
            services.AddControllers();
        }
    }

şeklinde çalışma yapmamız yeterli olacaktır. Burada dikkat ederseniz “Audience”, “Issuer”, “LifeTime”, “SigningKey” ve “ClockSkew” kavramları mevcuttur. Bu kavramların ne olduğuna dair bir açıklama yapmamız gerekirse eğer;

  • Audience
    Oluşturulacak token değerini kimlerin/hangi originlerin/sitelerin kullanacağını belirlediğimiz alandır. Örneğin; “www.bilmemne.com”
  • Issuer
    Oluşturulacak token değerini kimin dağıttığını ifade edeceğimiz alandır. Örneğin; “www.myapi.com”
  • LifeTime
    Oluşturulan token değerinin süresini kontrol edecek olan doğrulamadır.
  • SigningKey
    Üretilecek token değerinin uygulamamıza ait bir değer olduğunu ifade eden security key verisinin doğrulamasıdır.
  • ClockSkew
    Üretilecek token değerinin expire süresinin belirtildiği değer kadar uzatılmasını sağlayan özelliktir. Örneğin; kullanılabilirlik süresi 5 dakika olarak ayarlanan token değerinin ClockSkew değerine 3 dakika verilirse eğer ilgili token 5 + 3 = 8 dakika kullanılabilir olacaktır. Bunun nedeni, aralarında zaman farkı olan farklı lokasyonlardaki sunucularda yayın yapan bir uygulama üzerinde elde edilen ortak token değerinin saati ileride olan sunucuda geçerliliğini daha erken yitirmemesi için ClockSkew propertysi sayesinde aradaki fark kadar zamanı tokena eklememiz gerekmektedir. Böylece kullanım süresi uzatılmış ve tüm sunucularda token değeri adil kullanılabilir hale getirilmiş olunacaktır.

Şimdi yönümüzü tekrar yukarıdaki kod bloğuna çevirerek 15 ile 22. satır arasındaki yapılanları inceleyelim;

  • 15. satır; ‘ValidateAudience’ ile token üzerinde Audience doğrulamasını aktifleştirdik.
  • 16. satır; ‘ValidateIssuer’ ile token üzerinde Issuer doğrulamasını aktifleştirdik.
  • 17. satır; ‘ValidateLifetime’ ile token değerinin kullanım süresi doğrulamasını aktifleştirdik.
  • 18. satır; ‘ValidateIssuerSigningKey’ ile token değerinin bu uygulamaya ait olup olmadığını anlamamızı sağlayan Security Key doğrulamasını aktifleştirdik.
  • 19. satır; ‘ValidIssuer’ ile uygulamadaki tokenın Issuer değerini belirledik.
  • 20. satır; ‘ValidAudience’ ile uygulamadaki tokenın Audience değerini belirledik.
  • 21. satır; ‘IssuerSigningKey’ ile Security Key doğrulaması için SymmetricSecurityKey nesnesi aracılığıyla mevcut keyi belirtiyoruz.
  • 22. satır; ‘ClockSkew’ ile TimeSpan.Zero değeri ile token süresinin üzerine ekstra bir zaman eklemeksizin sıfır değerini belirtiyoruz.

Ayriyetten yukarıdaki kod bloğunda kullanılan değerler için Configuration propertysi kullanılmaktadır. Yani bu ilgili verileri “appsettings.json” dosyasından okuyoruz anlamına gelmektedir. Haliyle örnek uygulamamızda verileri ilgili dosyada aşağıdaki gibi tuttuğumuzu ifade etmekte fayda var;

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "Token": {
    "Issuer": "www.myapi.com",
    "Audience": "www.bilmemne.com",
    "SecurityKey":  "doldur be meyhaneci, boş kalmasın kadehim ..."
  }
}

Token Handler Sınıfını Oluşturma

JWT Token servisini uygulamaya entegre ettikten sonra sıra Token değerini oluşturmaktan sorumlu olan Token Handler sınıfını inşa etmeye geldi.

İlk olarak üretilecek token ve refresh token değerlerini taşıyacak olan “Token” modelimizi tasarlıyoruz.

    public class Token
    {
        public string AccessToken { get; set; }
        public DateTime Expiration { get; set; }
        public string RefreshToken { get; set; }
    }

Ardından Token Handler sınıfımızı inşa edebiliriz;

    public class TokenHandler
    {
        public IConfiguration Configuration { get; set; }
        public TokenHandler(IConfiguration configuration)
        {
            Configuration = configuration;
        }
        //Token üretecek metot.
        public TokenAuthentication.Models.Token CreateAccessToken(User user)
        {
            Models.Token tokenInstance = new Models.Token();

            //Security  Key'in simetriğini alıyoruz.
            SymmetricSecurityKey securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Token:SecurityKey"]));

            //Şifrelenmiş kimliği oluşturuyoruz.
            SigningCredentials signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

            //Oluşturulacak token ayarlarını veriyoruz.
            tokenInstance.Expiration = DateTime.Now.AddMinutes(5);
            JwtSecurityToken securityToken = new JwtSecurityToken(
                issuer: Configuration["Token:Issuer"],
                audience: Configuration["Token:Audience"],
                expires: tokenInstance.Expiration,//Token süresini 5 dk olarak belirliyorum
                notBefore: DateTime.Now,//Token üretildikten ne kadar süre sonra devreye girsin ayarlıyouz.
                signingCredentials: signingCredentials
                );

            //Token oluşturucu sınıfında bir örnek alıyoruz.
            JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();

            //Token üretiyoruz.
            tokenInstance.AccessToken = tokenHandler.WriteToken(securityToken);

            //Refresh Token üretiyoruz.
            tokenInstance.RefreshToken = CreateRefreshToken();
            return tokenInstance;
        }

        //Refresh Token üretecek metot.
        public string CreateRefreshToken()
        {
            byte[] number = new byte[32];
            using (RandomNumberGenerator random = RandomNumberGenerator.Create())
            {
                random.GetBytes(number);
                return Convert.ToBase64String(number);
            }
        }
    }

Yukarıdaki kod bloğunu incelerseniz eğer access token ile birlikte refresh token değerlerini üretecek bir sınıf tasarlamış bulunmaktayız. Burada “CreateRefreshToken” metodu içerisinde refresh token değeri için “RandomNumberGenerator” nesnesinin kullanıldığına dikkatinizi çekerim. Alternatif olarak burada isterseniz Guid tipinde bir değer dönebilir yahut farklı bir Token değeri oluşturup refresh token olarak kullanabilirsiniz.

Controller’ların Oluşturulması

İlk olarak kullanıcılardan gelecek olan login istekleri için “UserLogin” view model’ını oluşturalım.

    public class UserLogin
    {
        public string Email { get; set; }
        public string Password { get; set; }
    }

Ardından kullanıcılar tarafından yapılacak olan kayıt yahut login isteklerini karşılayacak olan “Login(Controller).cs” dosyasını oluşturalım.

    [ApiController]
    [Route("api/[controller]")]
    public class LoginController : ControllerBase
    {
        readonly TokenExampleContext _context;
        readonly IConfiguration _configuration;
        public LoginController(TokenExampleContext content, IConfiguration configuration)
        {
            _context = content;
            _configuration = configuration;
        }
        [HttpPost("[action]")]
        public async Task<bool> Create([FromForm]User user)
        {
            _context.Users.Add(user);
            await _context.SaveChangesAsync();
            return true;
        }
        [HttpPost("action")]
        public async Task<TokenAuthentication.Models.Token> Login([FromForm]UserLogin userLogin)
        {
            User user = await _context.Users.FirstOrDefaultAsync(x => x.Email == userLogin.Email && x.Password == userLogin.Password);
            if (user != null)
            {
                //Token üretiliyor.
                TokenHandler tokenHandler = new TokenHandler(_configuration);
                TokenAuthentication.Models.Token token = tokenHandler.CreateAccessToken(user);

                //Refresh token Users tablosuna işleniyor.
                user.RefreshToken = token.RefreshToken;
                user.RefreshTokenEndDate = token.Expiration.AddMinutes(3);
                await _context.SaveChangesAsync();

                return token;
            }
            return null;
        }
    }

Authorization işlemlerini test edebilmek için kullanıcı isteklerini karşılayacak “Test(Controller).cs” dosyasını oluşturalım.

    [Authorize]
    [ApiController]
    [Route("api/[controller]")]
    public class TestController : ControllerBase
    {
        public string Index()
        {
            return "Yetkilendirme başarılı...";
        }
    }

Yukarıdaki “Test(Controller).cs” sınıfını incelerseniz eğer “Authorize” attribute’u ile işaretlenerek yetkisiz erişim engellenir bir hale getirilmiştir.

Test Edelim

Sıra oluşturduğumuz yapılanmayı test etmeye gelmiştir. Tüm yapılanmayı Postman vasıtasıyla test edeceğiz.
İlk olarak kullanıcı oluşturalım.
Asp.NET Core - Token Bazlı Kimlik Doğrulama(JWT)
Ardından “Test(Controller)” controller’ına bir istekte bulunalım.
Asp.NET Core - Token Bazlı Kimlik Doğrulama(JWT)
Görüldüğü üzere ilgili istek neticesinde yetkisiz erişim yapmaya çalışıldığına dair “401 Unauthorized” durum kodunu döndürmektedir.

O halde token talebinde bulunalım.
Asp.NET Core - Token Bazlı Kimlik Doğrulama(JWT)
Görüldüğü üzere token talebi neticesinde bizlere access token, expiration ve refresh token verilerini barındıran bir JSON verisi döndürülmüştür. Artık yapmamız gereken ilgili access token değerini kullanıp bir önceki “Test(Controller)” sınıfına istekte bulunmaktır.
Asp.NET Core - Token Bazlı Kimlik Doğrulama(JWT)

Yukarıdaki ekran görüntüsünü incelerseniz eğer “Headers” kategorisinde “Authorization” keyine karşılık verilen “Bearer {token}” değeri ile yapılan istek neticesinde token değerimizin başarılı bir şekilde doğrulandığını görmekteyiz.

Unutmayın!!! Token bazlı kimlik doğrulama senaryosunda sistemin çalışması için “Startup.cs” dosyasındaki “Configure” metodu içerisinde ‘UseAuthorization’ middleware’ından önce ‘UseAuthentication’ middleware’inin çağrılması gerekmektedir. Aksi taktirde token ile yapılan istekler yine -401 Unauthorized- durum kodu olarak geri dönecektir.

Refresh Token Kullanımı

Üretilen token değerinin süresi bitmeden yahut bittiğinde kullanıcıyı yeniden login işlemleriyle meşgul etmeden gerekli yetkilendirmeyi tekrar sağlayabilmek için refresh token mekanizmasını kullanacağız. Bu mekanizma temelde aşağıdaki gibi bir stratejiden ibarettir.
Asp.NET Core - Token Bazlı Kimlik Doğrulama(JWT)
Yukarıdaki görüntüde olduğu gibi client öncelikle gerekli authorization için Authorization Server’a bir istekte bulunuyor ve bu istek neticesinde “Access Token” ile birlikte “Refresh Token” değerlerini elde ediyor. Süreçte Resource Server’a yapılan tüm istekler access token aracılığıyla gerçekleştiriliyor… Taa ki access token’ın süresi bitip geçersiz oluncaya kadar… İşte bu durumda kullanıcıyı tekrardan login sayfasına yönlendirerek kullanıcı adı ve şifre kontrolü yapmak yerine kullanıcıya önceden gönderilmiş olan refresh token değeri ile Authorization Server’a bir istekte daha bulunuluyor ve tekrar yeni bir access token üretilerek hiç vakit kaybetmeksizin kullanıcıya bu yeni token değeri ile kaldığı yerden devam etme olanağı tanınıyor. Tabi bu arada refresh token kullanıldıktan sonra geçerliliğini yitirerek üretilen access token değerine eşlik edebilecek yenisi üretilmiş oluyor.

Bu mantıkta refresh token değerini kullanarak yeni bir access token üretebilmek için gerekli istekleri karşılayacak endpointi aşağıdaki gibi tasarlayabiliriz.

    [ApiController]
    [Route("api/[controller]")]
    public class LoginController : ControllerBase
    {
        readonly TokenExampleContext _context;
        readonly IConfiguration _configuration;
        public LoginController(TokenExampleContext content, IConfiguration configuration)
        {
            _context = content;
            _configuration = configuration;
        }
        .
        .
        .
        [HttpGet("[action]")]
        public async Task<TokenAuthentication.Models.Token> RefreshTokenLogin([FromForm] string refreshToken)
        {
            User user = await _context.Users.FirstOrDefaultAsync(x => x.RefreshToken == refreshToken);
            if (user != null && user?.RefreshTokenEndDate > DateTime.Now)
            {
                TokenHandler tokenHandler = new TokenHandler(_configuration);
                TokenAuthentication.Models.Token token = tokenHandler.CreateAccessToken(user);

                user.RefreshToken = token.RefreshToken;
                user.RefreshTokenEndDate = token.Expiration.AddMinutes(3);
                await _context.SaveChangesAsync();

                return token;
            }
            return null;
        }
    }

Asp.NET Core - Token Bazlı Kimlik Doğrulama(JWT)
Görüldüğü üzere refresh token değeri ile yapılan istek neticesinde yeni bir access token değeri elde etmekte ve bu değeri aşağıdaki görselde olduğu gibi devam faaliyetlerimizde kullanabilmekteyiz.
Asp.NET Core - Token Bazlı Kimlik Doğrulama(JWT)

Evet… Görüldüğü üzere Refresh Token ile birlikte Token Bazlı Yetkilendirme işlemi bu çabadan ibarettir diyebiliriz 🙂 Sabredipte son noktasına kadar okuduğunuz için teşekkür ederim.

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

Not : Örnek projeyi indirmek için buraya tıklayınız.

Bunlar da hoşunuza gidebilir...

60 Cevaplar

  1. Recep Demir dedi ki:

    Merhabalar hocam
    Jwt te header da token göndermemiz ile bir güvenlik sorunu olmuyormu ?.
    Yani biri profiler dan bakım bizim api endpoint ve token degerimizi bildigi zaman api izinsiz kullanmış olmazmı. Bu konu hakkında nasıl bir çalışma ile jwt daha güvenli yapabiliriz ?.

  2. Ebubekir dedi ki:

    Merhabalar, Refresh Token Login yaparken Get methodunda body göndermişsiniz fakat Get işleminde body alınmıyor, Post olucaktı sanırım.

    • Gençay dedi ki:

      Merhaba,

          [HttpGet("[action]")]
          public async Task<TokenAuthentication.Models.Token> RefreshTokenLogin([FromForm] string refreshToken)
          {
              .
              .
              .
          }
      

      ‘refreshToken’ parametresi [FromForm] attribute’u ile işaretlenmiştir. Sanırım gözünüzden kaçmış 🙂

  3. oğuzhan dedi ki:

    Hocam bu token bazlı kimlik doğrulama yaparken İdentity de olan AppUser :IdentityUser modelini kullanarak yapmaya çalışıyorum yapamadım bi türlü acaba yapılamıormu yapılıorsada bildiğiniz bi kaynak var

  4. Gençay dedi ki:

    Sevgili okurların dikkatine;

    İlgili makalede aşağıdaki middlewareleri eklemeyi unutmuşum. Bilginize 🙂

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

    Sevgiler.

  5. selim dedi ki:

    Hocam refresh token çürüme süresi olamsı doğrum mu ? refresh token da çürürse tekrar login ekranına dönmesi gerekmez mi uygulamanın ?

  6. F.Ahmet dedi ki:

    Merhaba benim anlamadığım bir yer var. Token süresi dolduğunda otomatik olarak RefreshToken kullanılarak yeni Token alınması gerekiyor. Bunu nerede yaptınız. RefreshTokenLogin var ama bu otomatik değil. Yada JWT bunu kendisimi yapıyor.

    • Gençay dedi ki:

      Merhaba,

      İçerikte refresh token’ın mantığı ve teknik tasarımı ele alınmıştır lakin access token’ın süresi dolduğu vakit refresh token ile talepte bulunarak token’ı yenileme senaryosu artık kullanılan UI’da yapılan çalışmadaki stratejiye bırakılmıştır. Misal; jQuery ile yaptığınız istek neticesinde 401 hata koduyla error alırsanız, refresh token kullanılarak ilgili adrese istekte bulunabilir ve geçerli süre içerisindeyseniz access token’ı böylece yenileyebilirsiniz.

      Kolay gelsin.

  7. ibrahim dedi ki:

    Merhaba Recep,

    Genelde Login için gerekli kullanıcı adı ve şifre de açık bir şekilde istenmez. Belli bir algoritma ve key’e göre hashlenip eklenmesi istenir. Client hashleyerek gönderir ve karşı taraf bu hashi çözüp token verir.

    • Emre Aka dedi ki:

      Konu sadece token ile ilgili olduğundan olabildiğince basit tutulup sadece bu konu üzerine odaklanıldığını düşünüyorum.

  8. nasr dedi ki:

    merhaba,
    mesela bir haftalık bir access token oluşturduk, bir hafta geçti ve süresi doldu. mobil uygulamada da hala kullanıcı giriş yapılmış şekilde karşılanmalı. refresh token süresi ise access token’dan yalnızca 3 dakika fazla. access token bittiği andan itibaren kullanıcı sunucuya herhangi bir istek atmazsa 3 dakika sonra refresh token’ın da süresi bitecek demek oluyor. bu durumda nasıl kullanıcı girişi yapılmış halde kalabilir? (refresh token süresi +3 dakikadan fazla da olabilir yine de o süre içinde kullanıcının istek atmama ihtimali var)

    • Gençay dedi ki:

      Merhaba,

      Ben ne şekilde bir ihtiyaca binaen probleminiz olduğunu ne yazık ki tam olarak anlayabilmiş değilim. Access token süresi bittiğinde kullanıcıyı yormaksızın token değerini yenileyebilmek ve oturumdan düşürmemek için refresh token yapılanması kullanılmaktadır. Ee doğal olarak verilen süreler çerçevesinde kullanıcı tarafında herhangi bir hayat belirtisi yoksa ilgili tokenların etkinliğini yitirmesi en doğalı olacaktır. Refresh token süresinden daha fazla kullanıcıyı oturumda tutmayı istemek birnevi refresh token yapılanmasını gölgeleme zarureti doğuracağından dolayı, sualinize anladığım kadarıyla önerim, refresh token kullanmaksızın 60 günlük, 6 aylık vs. gibi süresi uzun bir access token üretmeniz ve sadece onu kullanmanızdır.

      Sevgiler.

      • nasr dedi ki:

        cevabınız için teşekkür ederim, aslında şunu söylemek istiyorum
        eğer ürün bir mobil uygulama ise ve bir kullanıcı haftada bir uygulamaya bakıyorsa (tüm kullanıcılar bu şekilde olmak zorunda değil bazısı daha sık bakıyordur) sunucuya o kullanıcıdan haftada bir istek geliyor

        token 3 günlük olduğunu varsayalım 3 gün sonra token expire oldu
        3gün+3 dakika sonra refresh token da expire oldu

        kullanıcı bir hafta sonra girdiğinde tekrar oturum açması mı gerekiyor?
        bu bir mobil uygulama için pek doğru bir senaryo olarak gelmedi bana refresh token’ın bu kadar kısa süreli olmasını pek anlayamadım belki örneğin bir banka uygulaması için doğru olabilir

        dediğiniz gibi uzun süre kullanıcıyı oturumda tutmak isteyen bir uygulamanın herhangi bir koşulda refresh token’a ihtiyacı olmaz olarak anlayabilir miyim?

        • Gençay dedi ki:

          Tekrar merhaba,

          Esasında JWT ile refresh token’ı mobil veya PC senaryosu diye ayırmak yanlış bir yorum olacaktır. JWT ile refresh token kullanıcıya kolaylık sağlaması için uygulanan mantıksal bir manevradır, algoritmadır. Kullanılacağı yer vardır, kullanılsa dahi pek yakışmayacağı yerler de vardır. Lakin bir kullanıcının herhangi bir platformdan 1 hafta sonra girdiğinde tekrar oturum açması sizin için problemse bunu JWT ile refresh token’dan bağımsız bir şekilde ele alıp ona göre geliştirme yapmanız sizce de daha doğru olmayacak mıdır?

          Nasıl ki, 10 tane 3’ün toplamı direkt olarak 3 + 3 + 3 + … + 3 şeklinde değilde 3 x 10 olarak hesaplamak işin doğasındansa benzer mantıkla refresh token’ında access token’dan biraz uzun olması bu stratejinin/mantığın/algoritmanın doğasındandır. Ve nasıl ki, ilk örneği anlayabilmek için toplamayı ve toplamanın nedenini anlamak gerekiyorsa, aynı şekilde ikinci vurgudaki refresh token için refresh token’ı ve nedenini anlamak gerekmektedir.

          Sözgelimi, dillendirdiğiniz senaryoda refresh token’ın mantığına aykırı bir problem görmemekteyim. Sanırım siz, çözüm getiremediğiniz noktalar çerçevesinde refresh token’ın kısalığına takılmışsınız 🙂 Bu durumda platformu ayırt edebilir bir kod geliştirerek ona göre access token ve refresh token üretebilmeniz yahut refresh token’ı istediğiniz kadar uzatma hüvviyetine sahip olmanız sizin için yeterli değil mi?

          Uzun lafın kısası demek istediğim ‘kuralları siz koyun, yazılım aracınız olsun‘ 😉

          Çok elzem olmasada yinede basit bir öneride bulunmamız gerekirse;

          Kullanıcıdan gelen isteğin hangi platformdan geldiğini(mobil, pc vs.) algılayıp mobil’den gelen isteklerde token’ın zamansal değerleri ile oynayarak, 1 hafta 10 günlük adil bir süre belirleyebilirsiniz.

          Sevgiler.

          • nasr dedi ki:

            teşekkür ederim
            selamlarımla

          • Uğur dedi ki:

            30 dakika accesstoken süresi olsun ,
            3 dk da refreshtoken süresi olsun. kullanıcı 30 ve 33. dakikalar arasında tekrar uygulamayı kullanmaya başladığında yeni accesstoken oluşsun ve bir 30 dk daha login yapmadan işlemlerini yapabilsin anlamına geliyor. Eğer kullanıcı 33 dakikadan daha fazla uygulamayı kullanmamışsa da artık yeniden giriş yapmak zorunda.
            Gencay’ın dediği gibi senaryoya göre de bu süreler değişir.

  9. Selahattin dedi ki:

    Hocam Merhaba
    JwtTokeni urettikten sonra WebUI kısmında nasıl kullanabiliriz ?

    Yani sessiona hangi adımda eklememiz gerekiyor ?

    Örnek bir config gösterebilir misiniz ?

  10. Selahattin dedi ki:

    Hocam Selamlar

    Jwt web token WebUI tarafında sessiona nasıl kaydedebiliriz. Bende bir takım hatalar oluşuyor runtime kısmında.

    Şöyle ki:

    WebUI tarafında gelen token’i Login actionunda sessiona setobjeyle atıyorum. Sonra startupda şu komuta

                app.Use(async(context, next) =>
                {
                    var JWToken = context.Session.GetString("JWToken");
                    if (!string.IsNullOrEmpty(JWToken))
                    {
                        context.Request.Headers.Add("Authorization", "Bearer " + JWToken);
                    }
                    await next();
                });
    

    sessiiondan okuyorum.

    Şimdi sormak istediğim tam olarak şunlar:
    1- Tokeni sessionda tutmalı mıyım ?
    2- app.use komutu her http isteğine sorgulanıyor. tokenin süresi bitse bile sessionda token duruyor. Bundan dolayı site bir hayli yavaşlıyor ve en sonunda beni sistemden logoff ediyor. app.use komutunu doğru mu kullanıyorum ?
    3- Web site canlıda https üzerinden çıkıyor. Httponly gibi bir durum https istekleri içinde geçerli midir ?

    • Gençay dedi ki:

      Merhaba,

      Cevaplar aşağıdadır;
      1 – Token değerine ihtiyacınız yoksa silebilirsiniz.
      2 – Kanımca doğru bir middleware seçilimi yapmışsınız. Kendinizde yazıp değerlendirebilirsiniz.
      3 – Evet, geçerlidir.

      Kolay gelsin.

  11. Hasan Mert dedi ki:

    Merhaba hocam asp.net core projemde apiyi güvenli hale getirmek için jwt yapıcaktım ancak oauth 2.0 varmış sanırım onu da kullanabiliyoruz .Müsait bir zamanınızda o konuyuda anlatırsanız çok sevinirim. İyi çalışmalar hocam

  12. Kadir dedi ki:

    Merhaba hocam,
    .Net Core Api projem var. 2 sorum olacak.
    1-Multiple login’i nasıl engelleyebiliriz?
    2-localhost’ta çalışırken token alıyorum, debug’ı durdurup bir daha çalıştırdığımda aynı token geçerli oluyor. Bunu nasıl engelleyebilirim?

    • Gençay dedi ki:

      Merhaba,

      1- Multiple’dan kastınız bir token ile birden fazla client’tan giriş yapmayı soruyorsanız eğer bunun için özel bir alan tanımlamasıyla oturumu check edebilirsiniz.
      2- Token yapısı gereği expire değeri kadar yaşam sürecine devam edecektir.

      • Kadir dedi ki:

        Aynı user ile iki farklı client’tan giriş yapılmasını engellemek istiyorum. Bir kullanıcı hesabıyla token aldım diyelim. Farklı client’tan aynı user ile başka bir token aldığımda ilk token iptal olmasını nasıl sağlayabilirim. Cevap için teşekkürler hocam.

        • Gençay dedi ki:

          Tekrar merhaba,

          Kullanıcıya dair üretilen token değerlerini Redis ya da SQL tarafında tutabilir ve iptal olmasını istediklerinizi BlackList mantığında bir tabloya atabilirsiniz. Gelen request’te token bilgisini blacklist üzerinden check edersiniz, eğer varsa request’i geri yönlendirirsiniz.

          Sevgiler.

  13. Erdi dedi ki:

    Çalışmalarınız için çok teşekkürler.

    1- Oluşturulan tokenlar’ı, cookie içerisinde standart bir şekilde saklamanızın güvenlik açısından bir sakıncası var mı?
    2- Cookie yönetiminde bilmemiz gereken başka bir durum var mı?
    3- User.Identity.Name gibi erişimler için ek kullanıcı bilgilerini bu yapılar içinde saklayabiliyormuyuz? Yani cookie’de token’ı saklayıp, yeniden geldiğinde süresi bitmediği zamanda, token sorgusu yapıp session ile mi tutulmalı, yoksa token içeriği bu verileri saklayacak bir yapı sağlıyor mu?

    • Gençay dedi ki:

      Merhaba,

      1- Yoktur.
      2- İhtiyaca göre bilinmesi gerekenler bir nebze olsun detaylarda olabilir amma velakin JWT için daha ötesine gerek bulunmadığı kanaatindeyim.
      3- Anlayamadım 🙂 Sorunuzu biraz daha açar mısınız?

      Kolay gelsin.

  14. İsmail dedi ki:

    Çalışmalarınız için çok teşekkürler.

    Bir web api ve bir mvc client olan bir projede, Jwt ile authentication ve authorization yapmak istiyorum. Api tarafını oluşturdum ancak consume ederken cookie bazlı yetkilendirme kullanıyorum.

    Sorularım biraz bunun üzerinden olacak.
    1. Bu şekilde yaklaşım doğru mudur? Veya bu konuda tavsiyeleriniz nelerdir?
    2. Permissionları tutmak için claimler güvenli olacak mıdır?
    3. Hem api hem clientta permission kontrolü için ne yapmak lazım?
    4. Son olarak da 2FA, bu yapıda mümkün olur mu?

    • Gençay dedi ki:

      Merhaba,

      Öncelikle nezaketiniz için ben teşekkür ederim.

      Suallere gelirsek eğer;
      1- API’lar ile çalışırken JWT, MVC ile çalışırken Cookie tavsiye edilir. Yaklaşımınız yanlış değildir, ihtiyaca nazaran doğrudur.
      2- Evet, permissionları tutmak için claim güvenli olacaktır.
      3- Her iki taraftada Authorize attribute’u aynı işlevi görmektedir. Bu makalede anlatılan yöntemi her ikisinde de kullanabilirsiniz.
      4- Evet, mümkündür.

      Sevgiler.

  15. mehmed emre dedi ki:

    Merhaba içerik için çok teşekkürler. Benim bir sorum olacak Token Authentication aynı zamanda Bearer Authentication mı oluyor? Http Authenticaiton Metodları içerisinde basic,bearer,digest ve oauth geçiyor ondan sordum.Teşekkürler.

    • Gençay dedi ki:

      Mehraba,

      Evet, token authentication bearer token olarak geçmektedir. Misal, basic auth ise username ve password temelli doğrulamadır vs.

      Kolay gelsin.

  16. Salim dedi ki:

    Audience kısmı için birşey sormak istiyorum. Örneğin ben Angular ile bir istek atacak isem angularda kullandığım hostu Audience kısmına mı yazacağım. Birde linkini bıraktığım yazıda:
    token-authentication kulanımını anlatmışsınız bu yazıdakiyle farkı tam olarak nedir? Son olarak CORS ayarlarını yaparken jwt için dikkat etmemiz gereken birşey var mıdır? Teşekkürler.

    https://www.gencayyildiz.com/blog/asp-net-core-angular-7-web-api-token-authentication-kullanimi/

    • Gençay dedi ki:

      Merhaba,

      İki yazıda mahiyet olarak birebir aynıdır. Sadece bu yazı, diğer referans ettiğiniz yazıya nazaran konuyu daha geniş ele almakta ve refresh token gibi ekstra teknikleri kapsamaktadır.

      ‘Audience’ kısmına genellikle bu JWT’yi kullanacak olan host yazılır. Yani evet, Angular uygulamasının adresini yazmanız yeterli olacaktır.

      CORS konusuna dair Asp.NET Core Uygulamalarında CORS(Cross-Origin-Resource-Sharing) Politikası Ayarlama başlıklı makalede tüm detayları izah etmiş bulunmaktayım. İncelemenizi tavsiye ederim.

      Kolay gelsin.

  17. BATUHAN SEVGİL dedi ki:

    En güveli kod olmuş hocam ” doldur be meyhaneci boş kalmasın kadehim” :))))

  18. necil dedi ki:

    Gençay bey merhaba,
    Peki oluşturduğumuz token ile backend de örneğin bir post atıyoruz token içinde ıd mevcut, bu ıd ye nasıl ulaşıyoruz yani bu postu oluşturan kullanıcının ıd sini çekmem lazım buda token da gömülü buna nasıl ulaşabiliriz ilgili yerlerde ?

    • Gençay dedi ki:

      Bunun için NameClaimType‘ı kullanabilirsiniz. Gerekli konfigürasyon için ‘Startup.cs’ dosyasında aşağıdakine benzer çalışma yapmanız gerekmektedir.

                  services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                      .AddJwtBearer(options =>
                      {
                          options.TokenValidationParameters = new TokenValidationParameters
                          {
                              .
                              .
                              .
                              NameClaimType = "id" //Token içerisindeki claim'in key'i birebir aynı yazılmalı!
                          };
                      });
      

      Burada NameClaimType property’si ilgili token’da ki herhangi bir claim’e karşılık gelebilir. Verilen claim değerine controller’lar dan aşağıdaki gibi erişebilirsiniz.

      User.Identity.Name
      

      Tabi burada ilgili kod her ne kadar Name yani isim karşılında gözükse de siz ihtiyaç doğrultusunda ‘NameClaimType’a id vererek bu kod üzerinden id’yi elde edebilirsiniz.

      Kolay gelsin…
      Sevgiler…

      • necil dedi ki:

        Yanıtınız için teşekkür ederim, verdiğiniz bilgi doğrultusunda ıd yi yine yakalayamadım null geldi fakat biraz daha araştırdığımda şöyle bir yöntem buldum:

        var claimsIdentity = this.User.Identity as ClaimsIdentity;
        var userId = claimsIdentity.FindFirst(ClaimTypes.Name)?.Value;
        

        controller da bu şekilde token da ki ıd yi yakalıyorum startupa da claimtype eklemeden, ne kadar kullanışlı veya daha kısa bir yöntem varmı bilmiyorum paylaşmak istedim.

        • Gençay dedi ki:

          Öncelikle dönüşünüz için teşekkür ederim.

          Gösterdiğiniz yöntemde ClaimTypes.Name‘e karşılık claim’in değerini elde edebilirsiniz. Name’e karşılık id değeri mi geliyor?

          Ayrıca gösterdiğim yöntemde neden olmadığına dair bakabiliriz. İsterseniz bana token’da ki claimleri gönderin. Ayrıca bahsettiğim konfigürasyon kodlarınızı paylaşın. İnceleyip, geri dönebilirim.

  19. necil dedi ki:

    sizin yönteminiz ile yaptığımda startupım:

                    x.RequireHttpsMetadata = false;
                    x.SaveToken = true;
                    x.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateIssuerSigningKey = true,
                        IssuerSigningKey = new SymmetricSecurityKey(key),
                        ValidateIssuer = false,
                        ValidateAudience = false,
                        NameClaimType = "Id"
                    };
    

    servis katmanımda login fonksiyonumda oluşturduğum token claimler:

                        Subject = new ClaimsIdentity(new Claim[]
                        {
                            new Claim(ClaimTypes.Name,loginUser.Id.ToString()),
                            new Claim(ClaimTypes.Role,loginUser.Role)
                        }),
    

    fakat dediğiniz yöntemle controllerımda ıd yi çekemiyorum null çekiyor.

    • Gençay dedi ki:

      Bir önceki mesajta ilgili satırda vurguladığım gibi NameClaimType Claim’in key’i ile birebir aynı olmalıdır. Siz claim’e ‘ClaimTypes.Name’i koymuşsunuz. Halbuki ‘NameClaimType’dan ‘Id’yi bekliyorsunuz. İkisinden biri değişmeli… Misal olarak sizin ‘Startup.cs’ dosyasında aşağıdaki değişikliği yapmanız User.Identity.Name‘a ilgili değerin gelmesini sağlayacaktır.

      x.RequireHttpsMetadata = false;
      x.SaveToken = true;
      x.TokenValidationParameters = new TokenValidationParameters
      {
          ValidateIssuerSigningKey = true,
          IssuerSigningKey = new SymmetricSecurityKey(key),
          ValidateIssuer = false,
          ValidateAudience = false,
          NameClaimType = ClaimTypes.Name
      };
      

      Hazır bu kadar değinmişken hatanıza değinmeden geçemeyeceğim. ‘Name’e karşılık id değerini tutmak pekte mantıklı değildir 🙂 Name’de gerçek kullanıcı adını tutup, controller’da elde ettikten sonra ufak bir sorgulamayla id’sini elde etmeniz daha makuldur. Ya da ‘id’ isminde bir claim üretip onunla çalışmak daha da mantıklı olacaktır kanaatindeyim.

      Sevgiler…

      • necil dedi ki:

        tamam şimdi anladım ve çalıştı 🙂 evet id yi deneme amaçlı name e set ediyorum dediğiniz gibi bir get işlemi ile de idyi alabilirim fakat bir de şu var, ben katmanlı mimari kullanıyorum yani servis katmanımda User.Identity.Name’e erişemiyorum sizce bunu controllerda yakalayıp örneğin admin bir ürün ekle işlemi yapıyor bunun içerisinde oluşturan kullanıcı id si var bu ilk aşamada null geliyor bende bunu controllerda bu şekilde online olan kullanıcıyı tokendan çekip id sini orada set edip sonra mı servis katmanıma yollayayım sizce uygun bir yöntem mi ? kusura bakmayın çok uzattım lakin temelden oturuyor şuan mantık 🙂

  20. Abdullatif dedi ki:

    Hocam postman olmadan test edebilmenin bir yolu var mı?
    ya da ben direk mvc prjesi üzerinden geliştirsem sayfası ile test edilebilinir değil mi?

  21. ali dedi ki:

    Ben işin içinden çıkamadım vallahi billahi. Hocamız keşke videolu anlatım yapsa bunla ilgili. Günlerdir boğuşuyorum.

    Şimdi hocam Token sınıfımız var. Bu veritabanında bir tablo olacak öyle değil mi? AccessToken bilgisi ve onla ilişkili RefreshToken bilgisi de bu tabloda mı yer alacak? User tablomuzda da bir adet RefreshToken var. Bu RefreshToken ile Token tablosundaki RefreshToken aynı veriyi mi tutuyor. Valla kafam karıştı.

  22. Cihan dedi ki:

    Ellerine sağlık Gencay Bey. Harika bir makale olmuş.

  23. Emre AKA dedi ki:

    Teşekkür ederim. Yazınız benim tarafımdan oldukça anlaşılır idi.

  24. ilhan yeşiloğlu dedi ki:

    Gerçekten güzel anlatım. Teşekkürler. Saygılar

  25. Serkan dedi ki:

    bilgi için teşekkürler, şöyle bir ihityaç hasıl oldu, sadece 1 browserda kullanıcı aktif olması gerekiyor, aynı anda aynı hesap ile farklı bağlantı sağlanmasını engellemek istesek nasıl bir yol izlemek gerek acaba.

  26. cenk dedi ki:

    gencay kardeşimizin gözünden kaçmış olabilir.
    Refreshtoken süresi ,accesstoken dan daha uzun olmamalı.
    Çok basit anlatımla ;

    Kullanıcı login oldu -> 25 günlük accesstoken verildi , 30 günlük refreshtoken verildi.
    25 günlük süre doldu, Refreshtoken ile yeni accesstoken alman gerekiyor. Fakat oda ne method içinde şöyle bir sorgu var.
    if (user != null && user?.RefreshTokenEndDate > DateTime.Now)

    Yani daha 5 günün var , öyleyse ben sana accesstoken veremem.O halde kullanıcı ne yapacak ? Tabi ki 5 gün boşta bekleyecek yada 2 sürecin yerini değiştirin ve her şey daha iyi hale gelsin.

    Aslında gencay kardeşimizin neden böyle yaptığını anladım , refreshtoken süresinden niye daha fazla süreyle accesstoken verip boşuna belleğimi yorayım diyor ama yöntem yanlış. Ayrıca konuların güzel tebrik ederim.

    • Gençay dedi ki:

      Tam anlayamadım ama yine de teşekkür ederim 🙂

    • tufan kaşkavalcı dedi ki:

      Selam, zaman birimi konuştuğumuz için ( expiration date) FAZLA olmalı tanımı kafa karıştırmış.FAZLA kelimesi yerine büyük olmalı yani refresh token expiration date büyük olmalı accss token expiration date demeye çalışmış. access token 1 mart 2023 te bitiyorsa kullanımı refresh tokenın expiration date i büyük olmalı 1 mart 2023 ten demeye çalıştığını düşünüyorum.19 şubat 2023 denmez yani refresh token expiration date ine.Dil sürçmesi 🙂 sevgiler saygılar

  1. 13 Şubat 2020

    […] Asp.NET Core 3.1 ile Token Bazlı Kimlik Doğrulaması ve Refresh Token Kullanımı(JWT) Not : Bu makale direkt olarak Identity mekanizmasıyla ilgili olmasada farklı bir kimlik doğrulama mekanizması konusunu kapsadığı için ilgili yazı dizisine dahil edilmiştir. […]

  2. 02 Ekim 2020

    […] öncelikle JWT üretebilecek mekanizmayı inşa etmemiz gerekmektedir. Normalde bu mekanizmayı Asp.NET Core 3.1 ile Token Bazlı Kimlik Doğrulaması ve Refresh Token Kullanımı(JWT) başlıklı makaleyi referans ederek, inşa edildiğini varsayıp geçebilirdik. Lakin sizlerin […]

  3. 25 Haziran 2022

    […] oluşturunuz ve özellikle Asp.NET Core uygulamasında Identity konfigürasyonlarını ve hatta JWT gibi authentication yapılanmalarını […]

Recep Demir için bir yanıt yazın Yanıtı iptal et

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