Categories: Asp.Net Web API

Asp.Net Web API – Token Based Authentication Refresh Token Kullanımı

Merhaba arkadaşlar.

Biraz aradan sonra tekrar bir Asp.Net Web API makalesi ile karşınızdayım. Daha önceki makalemde Asp.Net Web API’da Token Based Authentication nasıl gerçekleştirilir ve implemente edilir konusunu ele almıştım. Bu makale kapsamında ise sizlerden gelen feedback’ler doğrultusunda ilerleyerek, Token Based Authentication kullanırken Refresh Token nasıl implemente edilir ve nasıl çalışır konusunu da ele almaya karar verdim.

Refresh Token Nedir?

Refresh Token authorization server tarafından mevcutta olan Access Token’ın expire süresi sona ermeye yaklaştığında veya sona erdiğinde, yeni bir Access Token elde edebilmek için client’a verilir. Refresh Token’ın kullanımı opsiyonel olup, authorization server tarafından Access Token alınacağı zaman, Refresh Token da beraberinde verilir.

Not: Unutulmamalıdır ki Refresh Token, Access Token’ın aksine sadece authorization server ile kullanılmak için tasarlanmış olup, resource server’a gönderilmemektedir.

Refresh Token Nasıl Çalışır?

Dilerseniz Refresh Token’ın çalışma mantığına, aşağıda çizmeye çalıştığım sequence diyagramı üzerinden bir bakalım.

 

Yukarıdaki akışa baktığımızda, client önce authorization yetkilerini alabilmek için authorization server’a gidiyor ve buradan Access Token’ı ve Refresh Token’ı elde ediyor. Daha sonra Access Token ile birlikte resource server’a erişebiliyor ve istediği response’u alabiliyor. Daha sonra tekrardan aynı Access Token bilgisi ile resource server’a geri geliyor ve bu sefer “invalid token error” hatası ile karşılaşıyor. Burada client’ın Access Token bilgisinin expire olduğunu görüyoruz. Client ilgili hatanın ardından daha önce elde etmiş olduğu Refresh Token’ı kullanarak resource server yerine, direkt olarak authorization server’a gidiyor ve yeni bir access token ve opsiyonel olarak varsa bir refresh token da elde ediyor. Akış bu şekilde devam etmektedir.

Refresh Token Implementasyonu

Implementasyon işlemi tıpkı Token Based Authentication’da olduğu gibi gayet basittir. Bu işlemi “Microsoft.Owin.Security.Infrastructure” namespace’i altında bulunan IAuthenticationTokenProvider interface’ini implemente ederek gerçekleştireceğiz. Implementasyon işlemine başlamadan önce Asp.Net Web API’da Token Based Authentication makalesine geri dönüp, makale sonundaki projeyi indirmemiz gerekmektedir. Örnek projeyi elde ettiğimize göre, “OAuth>Providers” klasörü altında SimpleRefreshTokenProvider isminde yeni bir class ekleyelim. IAuthenticationTokenProvider interface’ini implemente edelim ve Refresh Token’ın oluşturulabilmesini sağlayacak olan “Create” method’unun implementasyonundan aşağıdaki gibi başlayalım.

using System.Linq;
using System.Threading.Tasks;
using Microsoft.Owin;
using Microsoft.Owin.Security.Infrastructure;

namespace AspNetWebAPIOAuth.OAuth.Providers
{
    public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider
    {
        public async Task CreateAsync(AuthenticationTokenCreateContext context)
        {
            Create(context);
        }

        public void Create(AuthenticationTokenCreateContext context)
        {
            object owinCollection;
            context.OwinContext.Environment.TryGetValue("Microsoft.Owin.Form#collection", out owinCollection);

            var grantType = ((FormCollection)owinCollection)?.GetValues("grant_type").FirstOrDefault();

            if (grantType == null || grantType.Equals("refresh_token")) return;

            //Dilerseniz access_token'dan farklı olarak refresh_token'ın expire time'ını da belirleyebilir, uzatabilirsiniz 
            //context.Ticket.Properties.ExpiresUtc = DateTime.UtcNow.AddMinutes(1);

            context.SetToken(context.SerializeTicket());
        }
    }
}

“CreateAsync” method’u içerisinden “Create” method’unu çağırıyoruz ve aynı işlemler için tekrardan bir kod tekrarı işlemi gerçekleştirmiyoruz. “Create” method’una baktığımızda ise “OwinContext.Environment” üzerinden “Microsoft.Owin.Form#collection” key’i ile access_token alabilmek için gönderilen parametrelere “FormCollection” üzerinden aşağıdaki gibi erişiyoruz.

Burada “grant_type” parametresinin refresh_token olup olmadığına bakıyoruz ve bu duruma göre context’e bir token set ediyoruz veya etmiyoruz. Bu kontrolü yapmamızdaki sebep ise access_token expire olduğunda veya olmaya yakın olduğunda client, refresh_token ile server’a geldiğinde tekrardan geçerli bir access_token daha üretilirken yeni bir refresh_token daha üretilmemesi içindir. Eğer sizler business’ınız gereği her seferinde yeni bir access_token üretilirken refresh_token’da üretilsin istiyorsanız, bu kontrolü bu noktada es geçebilirsiniz.

Bu işlemlere ek olarak da üretilen refresh_token’ın expire süresi, “Startup.cs” class’ı içerisinde daha önceden AccessTokenExpireTimeSpan parametresi üzerinden access_token için belirlemiş olduğumuz expire süresi ile aynı değeri taşımaktadır. Bu durumda eğer spesifik olarak herhangi bir değer atamazsanız, access_token sona erdiğinde refresh_token’da sona ereceği için yeni bir token üretilemeyecektir. “Create” method’u içerisinde aşağıdaki kod parçacığı ile refresh_token’ın expire süresini spesifik olarak belirleyebilmek mümkündür.

context.Ticket.Properties.ExpiresUtc = DateTime.UtcNow.AddMinutes(1);

Implementasyon işlemine şimdi “Receive” method’u ile devam edeceğiz. Bu method ise server’a refresh_token ile gelindiğinde, gerekli kontrolleri yaparak yeni bir access_token üretilmesinden sorumlu olan method’dur. Implementasyon işlemini aşağıdaki gibi gerçekleştirelim.

using System.Linq;
using System.Threading.Tasks;
using Microsoft.Owin;
using Microsoft.Owin.Security.Infrastructure;

namespace AspNetWebAPIOAuth.OAuth.Providers
{
    public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider
    {
        public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
        {
            Receive(context);
        }

        public void Receive(AuthenticationTokenReceiveContext context)
        {
            context.DeserializeTicket(context.Token);

            if (context.Ticket == null)
            {
                context.Response.StatusCode = 400;
                context.Response.ContentType = "application/json";
                context.Response.ReasonPhrase = "invalid token";
                return;
            }

            context.SetTicket(context.Ticket);
        }
    }
}

“ReceiveAsync” method’u içerisinden “Receive” method’unu çağırıyoruz ve implementasyon işlemlerini burada gerçekleştiriyoruz. Burada “context.Token” parametresi ile gelen token’ı, “context.DeserializeTicket” method’u ile token’ın deserialization işlemini gerçekleştiriyoruz. Deserialization işleminden sonra “context.Ticket” ın null kontrolünü gerçekleştirip, context içerisine “SetTicket” method’u ile token’ı aktarıyoruz. Bu işlemlerin ardından Owin bizim için geçerli diğer kontrolleri de sağlayarak, yeni bir access_token üretimini gerçekleştirecektir. Null kontrolünde ise sizlerinde bildiği üzere geçersiz bir token gelmiş ve deserialization işlemi null olarak gerçekleşmiştir. Bu durumda context üzerine “context.Ticket” property’sini set edemeyeceğimiz için içerisinde 400 status koduna sahip “invalid token” response’unu geriye dönüyoruz.

Implementasyon işlemi bu kadar şimdi sadece oluşturmuş olduğumuz bu “SimpleRefreshTokenProvider” ın “Startup.cs” içerisine tanıtılması kaldı. Bu işlemin öncesinde ise dilerseniz provider’ın tam implemente edilmiş bir halini görelim.

using System.Linq;
using System.Threading.Tasks;
using Microsoft.Owin;
using Microsoft.Owin.Security.Infrastructure;

namespace AspNetWebAPIOAuth.OAuth.Providers
{
    public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider
    {
        public async Task CreateAsync(AuthenticationTokenCreateContext context)
        {
            Create(context);
        }

        public void Create(AuthenticationTokenCreateContext context)
        {
            object owinCollection;
            context.OwinContext.Environment.TryGetValue("Microsoft.Owin.Form#collection", out owinCollection);

            var grantType = ((FormCollection)owinCollection)?.GetValues("grant_type").FirstOrDefault();

            if (grantType == null || grantType.Equals("refresh_token")) return;

            //Dilerseniz access_token'dan farklı olarak refresh_token'ın expire time'ını da belirleyebilir, uzatabilirsiniz 
            //context.Ticket.Properties.ExpiresUtc = DateTime.UtcNow.AddMinutes(1);

            context.SetToken(context.SerializeTicket());
        }

        public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
        {
            Receive(context);
        }

        public void Receive(AuthenticationTokenReceiveContext context)
        {
            context.DeserializeTicket(context.Token);

            if (context.Ticket == null)
            {
                context.Response.StatusCode = 400;
                context.Response.ContentType = "application/json";
                context.Response.ReasonPhrase = "invalid token";
                return;
            }

            context.SetTicket(context.Ticket);
        }
    }
}

Şimdi “Startup.cs” i açalım ve “ConfigureOAuth” method’u içerisinde “RefreshTokenProvider” property’sini aşağıdaki gibi tanımlayalım.

OAuthAuthorizationServerOptions oAuthAuthorizationServerOptions = new OAuthAuthorizationServerOptions()
{
    TokenEndpointPath = new Microsoft.Owin.PathString("/token"), // token alacağımız path'i belirtiyoruz
    AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(1),
    AllowInsecureHttp = true,
    Provider = new SimpleAuthorizationServerProvider(),
    RefreshTokenProvider = new SimpleRefreshTokenProvider()
};

Artık tüm yapı hazır durumda. Test işlemlerimizi bir önceki makalede de olduğu gibi Postman üzerinden gerçekleştireceğiz. Örneğimizde access_token 1 dakikalık bir expire süresine sahip olurken, refresh_token ise 2 dakikalık bir süreye sahip olacak. Projeyi çalıştıralım ve Postman üzerinden “http://localhost:55992/token” URI’ına aşağıdaki gibi bir POST request oluşturalım.

Headers’e eklenecek parametreler:

Header: Accept                  Value: application/json
Header: Content-Type     Value: application/x-www-form-urlencoded

Body kısmına eklenecek parametreleri de aşağıdaki gibi set edelim ve POST işlemini gerçekleştirelim.

POST işleminin sonucunda “access_token” ve “refresh_token” ı elde ettik. “expires_in” property’sinde ise 1 dakikalık bir süreye sahip olabildiğini görebiliyoruz.

“access_token” ı kullanarak bir önceki makalemizde olduğu gibi “Orders” resource’unu kullanalım. Bunun için yine Postman üzerinden “http://localhost:55992/api/orders/list” URI’ına aşağıdaki Headers parametrelerini kullanarak bir GET isteğinde bulunalım ve execute edelim.

“Orders” resource’unu access_token ile başarılı bir şekilde consume edebildik. 1 dakikanın sonunda ise aynı access_token ile aynı resource’a tekrardan bir GET isteğinde bulunalım ve response’a bir bakalım.

Geçen 1 dakikanın ardından gördüğümüz gibi access_token expire olduğu için “Authorization has been denied for this request.” hatasını aldık. Hatırlarsak refresh_token’ın expire süresini ise 2 dakika olarak belirleyeceğiz demiştik. Hemen tekrardan “http://localhost:55992/token” URI’ına refresh_token’ı kullanarak aşağıdaki gibi bir POST isteğinde bulunalım.

Headers’e eklenecek parametreler:

Header: Accept                  Value: application/json
Header: Content-Type     Value: application/x-www-form-urlencoded

Body kısmına eklenecek parametreleri de aşağıdaki gibi set edelim ve POST işlemini gerçekleştirelim.

Body kısmında ise bu sefer “grant_type” ı “refresh_token” olarak set ederken, “refresh_token” parametresini ise ilk access_token alırken elde ettiğimiz değeri set ettik. Bu işlemlerin sonucunda ise yukarıdaki resimde olduğu gibi yeni bir access_token elde ettik.

Bir konunun daha sonuna geldik, umarım herkes için faydalı bir yazı olmuştur. Geçmiş kurban bayramınızı kutlar ve takipte kalmanızı temenni ederim.

Örnek uygulamaya aşağıdan erişebilirsiniz.

aspnetwebapioauth

Gökhan Gökalp

View Comments

  • Refrefh Token nın gerekliğini tam anlayamadım. Örnek : Access Token ın Expire olunca ben (client : örneğin Native App) bu Access Token ile Authorization Server a gidip , orada bunu kontrol edip expire oldu ise yenisini almamda nasıl bir sakınca olabilir ?

    • Merhaba, bu durumlar genelde access token'ın snifflenmesine engel olunmak için tercih edilmektedir. Long-lived access token'lar tercih edilmemektedir. Belirli bir expire date verirsiniz, token sniff'lense dahi o expire date'inde sona erecektir. Refresh token'la ise genelde sadece bir kerelik yeni bir token daha üretilmektedir. Sizin bahsetmiş olduğunuz çözüm ise sonsuz bir session işlemi gibi.

  • Merhaba,
    kullanıcı login olduktan sonra yapacağı istekler için sadece token gönderiyor. DB ile bir bağlantısı kalmıyor. Peki farklı yerlerden aynı kullanıcı ile login olduğunda bunu nerede yakalayarak iki kullanıcıdan bir tanesini sistem dışı bırakabiliriz?

    teşekkürler.

    • Merhabalar, ilgili kullanıcıya token generate ederken claims'e set etme sırasında DB'de kullanıcıya ait bazı bilgileri güncelleyebilir, onları daha sonrasında kontrol edebilirsiniz.

  • Merhaba,
    token süresi bittiğinde, refresh token ile yenilemek ve bunu sürekli devam ettirmek dogru bi yapimidir.kullaniciya sürekli bağlı kal gibi bir altarnatif sunabilirmiyim

    • Merhaba, sürekli yenilemek diye bir olay yok. refresh token bir kere kullanımlıktır default olarak. Eğer siz isterseniz customize ederek her refresh token call edildiğinde, access token ile birlikte her seferinde yeni bir refresh token da verdirebilirsiniz. Fakat bu işlem pek istenilen yol değildir. Saygılarımla

      • Merhaba, refresh_token'in süresi sınırsız iken nasıl sadece tek defa kullanılmasını sağlayabiliriz. bunlar için auth serverda ayrı bir invalidate token listesi mi tutmamız gerekecek, bu çok maliyetli olmaz mı? kullanıcı şifre vs. değiştirmediği sürece ya da özellikle tüm cihazlardan logout ol demedikçe refresh_token'i invalidate etmeye gerek var mı, bu bir güvenlik zaafı oluşturur mu?

        • Merhaba, kusura bakmayın geç cevap için. Herhangi bir standart yöntemi yok maalesef. Refresh token'ın amacı, kullanıcının her seferinde kendi şifresini girerek login olmamasını sağlamak ve çeşitli claim'leri sürekli güncel tutabilmek. Bu sebeple expire süresi boyunca limitsiz bir kullanım sunmaktadır. Invalidate etmek yada etmemek tamamen size kalmış, ister refresh token'ın ilk kullanımından sonra invalidate olmasını sağlarsınız, isterseniz sürekli bırakırsınız.

  • Merhaba
    Token süresi 1 dakika,Refresh token süresi 2 dakika diyelim.
    kullanıcı token aldıktan sonra 5 dk boyunca hiç bir işlem yapmadı böyle bi durumda çözüm ne olur peki.
    birde refresh token kendisine bağlı olan token ile birlikte çalışmak zorundamıdır.
    kolay gelsin

    • Merhaba, kusura bakmayın geç cevap için. Token + Refresh token süresi expire olduğunda sizin kulanıcıya log out yaptırıyor olmanız gerekmektedir zaten. İkinci sorunuzu tam anlamı ile anlayamadım fakat eğer siz refresh token özelliğini implemente ederseniz, ilk token aldığınızda yanında o token'ı yenileyebilmek için bir refresh token verilir.

      Saygılarımla.

  • Merhaba,

    bu owin kütüphanesinin ürettiği token'lar bir yerde mi tutuluyor yoksa access token üzerinden çözümleme falan mı yapıyor? Mesela 15 günlük bir token verdik. Kullanıcı aynı token ile 13. gün yine giriş yaptı. Bu kütüphane token'ın halen geçerli olup olmadığını nasıl anlıyor? Yada kullanıcı bilgilerini hala nasıl tanıyabiliyor.(Örneğin claim'e tanımladığımız username gibi)?

    • Merhaba, token'lar persist edilmemektedir. Default olarak on the fly hesaplanıp, encrypt ve decrypt edilmektedir. Encrypt edilen verinin içerisinde expire date'leri gibi bilgiler bulunmaktadır.

  • Merhaba,

    Mobil uygulamalarda (Hybrid/Native) web api güvenliğini sağlamak için makalelerinizden ciddi derecede faydalandım. Güzel anlatımlarınız için teşekkür ederim, bir sorum olacak.

    Mobil uygulama kullanıcısı uzun süre uygulamaya girmediğini varsayalım, yani hem access_token hemde refresh_token expire olduğu durumda, uygulamayı açan kullanıcıya login ekranı getirmeden, uygulama kullanımına geçebilmesi için nasıl bir mantık geliştirilmeli ?

    App içerisinde kullanıcı-adı şifre tutmanın çok doğru bir yöntem olduğunu düşünmüyorum.
    Access_Token ve Refresh_Token süresine 1-2 yıl gibi bir değer vermeninde doğru olmadığını düşünüyorum.

    Bu senaryo için fikrinizi almak isterim.

    • Merhaba, teşekkür ederim öncelikle.
      Açıkçası söz konusu mobile olunca işler biraz değişiyor. Doğru olan şudur diyemiyorum bu konuda. Herkesin izlediği farklı yöntemler mevcut. Kullanıcının bilgilerini persist etmek pek efektif olmayacaktır bu case'de. En etkin yol, mobile kullanıcılarına özel daha uzun süreli bir refresh token vermek olacaktır sanırım. Ama buda best practice'dir diyemiyorum.

      Kolay gelsin.

      • Yanıtınız için teşekkür ederim, çözüm olarak söylediğiniz gibi uzun süreli bir refresh_token verip, uygulama her açıldığında access_token expire olmuş ise refresh_Token ile hem access_token hemde refresh_token bilgisini yeniliyorum. şuan için iş görüyor farklı bir şey aklıma gelirse deneyip sizinle paylaşırım.

        Tekrardan teşekkür ederim

  • Merhaba Access token aldık expired suresi 5 dk süre doldu refresh ile tekrar acces token aldık buda 5 dk sonra bitti ben tekrar refresh token kullanamaz mıyım?
    Cevaplarınız için teşekkür ederim.
    iyi çalışmalar.

    • Merhaba, istediğiniz kadar refresh token kullanabilirsiniz ama pek tavsiye edilen bir yöntem değildir açıkcası. Snifflendiği taktirde, kötü niyetli kişiler refresh token'ı kullanarak, sonsuz bir oturum elde edebilirler.

  • merhaba Hocam
    asp.net ile bir api yaptım
    Token alıp android tabletle kullanıyorum
    token in süresi 14 gün fakat tablet uyku moduna girip bir müddet sonra uyandığında
    token geçerliliğini kaybediyor ve autohorization denied mesajı alıyorum.

    bunun sebebi nedir acaba

    • Merhaba, android tarafında token'ı nasıl store ettiğinizi veya davranışını malesef bilmiyorum. Token'ın hala mevcut olduğundan emin misiniz?

  • Merhaba Gökhan Bey,

    Açıklayıcı makaleniz için öncelikle teşekkür ederim.

    Mantığı kavramaya çalışıyorum, buradaki implementasyon tamamlandıktan sonra artık tüm talepleri refreshing token ile mi yapacağız?

    • Merhaba, hayır. Refresh token'ın da belirli bir kullanım ve üretim sayısı olmalıdır. Eğer, sürekli bir refresh token ürettirirsek, token snifflendiği an "kötü niyetli şahıs" claim'ler doğrultusunda istediği işlemi yapabilecektir. Bu karar tamamen sizin ve business'ınıza kalmış bir olaydır.

      • Merhaba tekrar,

        Cevabınız için teşekkür ederim.

        Loglama noktasında o oturum için (FormsAuth kulllanıyorum) benzersiz bir Guid oluşturup hangi oturumda ne işlem yapıldığını takip ediyorum.

        Bu Guid yerine oluşturulan auth token'ı ikame etmek sakıncalı olmaz sanırım, nasıl olsa refresh token tutulmayacağı için, expire olmuş bir token. Tavsiyeniz nedir?

        • Merhaba token correlation işlemleri için açıkçası uygun değildir veya nasıl olur bilemiyorum. Onun yerine bir Guid ile ilerlemeniz daha iyi olacaktır.

  • Merhaba, bu işlemi yapmak için illa bu web api mi kullanmak lazım normal web site ile nasıl oturum açma, token alma ve sonuçları çekme işlemini yapabiliriz?

    • Merhaba, kullanmak şart değil. Hangi platformda yapmak istiyorsanız gerçekleştirebilirsiniz.

Recent Posts

Overcoming Event Size Limits with the Conditional Claim-Check Pattern in Event-Driven Architectures

{:en}In today’s technological age, we typically build our application solutions on event-driven architecture in order…

2 months ago

Securing the Supply Chain of Containerized Applications to Reduce Security Risks (Policy Enforcement-Automated Governance with OPA Gatekeeper and Ratify) – Part 2

{:tr} Makalenin ilk bölümünde, Software Supply Chain güvenliğinin öneminden ve containerized uygulamaların güvenlik risklerini azaltabilmek…

7 months ago

Securing the Supply Chain of Containerized Applications to Reduce Security Risks (Security Scanning, SBOMs, Signing&Verifying Artifacts) – Part 1

{:tr}Bildiğimiz gibi modern yazılım geliştirme ortamında containerization'ın benimsenmesi, uygulamaların oluşturulma ve dağıtılma şekillerini oldukça değiştirdi.…

9 months ago

Delegating Identity & Access Management to Azure AD B2C and Integrating with .NET

{:tr}Bildiğimiz gibi bir ürün geliştirirken olabildiğince farklı cloud çözümlerinden faydalanmak, harcanacak zaman ve karmaşıklığın yanı…

1 year ago

How to Order Events in Microservices by Using Azure Service Bus (FIFO Consumers)

{:tr}Bazen bazı senaryolar vardır karmaşıklığını veya eksi yanlarını bildiğimiz halde implemente etmekten kaçamadığımız veya implemente…

2 years ago

Providing Atomicity for Eventual Consistency with Outbox Pattern in .NET Microservices

{:tr}Bildiğimiz gibi microservice architecture'ına adapte olmanın bir çok artı noktası olduğu gibi, maalesef getirdiği bazı…

2 years ago