Asp.Net Web API – Token Based Authentication
<h1>Kaynak : https://www.gokhan-gokalp.com/en/asp-net-web-api-token-based-authentication/?unapproved=15963&moderation-hash=0f9b496f23bd29ee2ec60290a88e0f08#comment-15963</h1> <h1> </h1> <h1>Asp.Net Web API – Token Based Authentication</h1> <p>Published by <a href="https://www.gokhan-gokalp.com/en/author/gok-gokalp/">Gökhan Gökalp</a> on <a href="https://www.gokhan-gokalp.com/en/2015/09/">September 2, 2015</a></p> <p>Merhaba arkadaşlar, bu makalemde Asp.Net Web API ile RESTful servis geliştirirken <strong>Token Based</strong> bir <strong>Authentication</strong> işlemi nasıl yapıldığına dair örnek bir proje yapacağız.</p> <p>RESTful’ün önemini kısaca hatırlamak gerekirse:</p> <ul> <li>Fazlasıyla basit ve esneklik sağlamaktadır.</li> <li>REST’in <strong>HTTP </strong>protokolü üzerine kurulmuş olmasıyla beraber günümüz modern web dünyasındaki bir çok uygulamalar kendini browser tabanlı uygulamalara bırakıyor ve artık bir çok işlem client-side tabanlı yapıldığı için REST servisleri bize bir artı daha sağlıyor bu anlamda.</li> </ul> <p>Özünde <strong>RESTful</strong> servisleri bize <strong>client-server</strong> arasındaki yapacak olduğumuz veri transferini <strong>SOAP</strong> veya <strong>RPC</strong> gibi kompleks mimariler yerine daha hafif ve esnek bir şekilde yapabilme olanağı sağlıyor.</p> <p>REST mimarisini hatırladığımıza göre, gelelim şimdi nedir bu <strong>Token Based</strong> <strong>Authentication?</strong></p> <blockquote> <p>Günümüz çağında geliştirilen neredeyse tüm uygulamaların bir mobil bacağı bulunmaktadır veya mobil tarafına da destek verebilecek şekilde servis mimarileri geliştirilmektedir. REST mimarisi üzerine kurulan bir serviste ise güvenlik işlemlerini ele alabilmek, <strong>client’ı yetkilendirebilmek</strong> için <strong>Token</strong> (Jeton) bazlı bir yetkilendirme işlemi yapılmaktadır.</p> </blockquote> <p><strong>Token Based Authentication</strong> işleminin yaşam döngüsüne bakmak istediğimizde ise:<a href="https://i2.wp.com/gokhan-gokalp.com/wp-content/uploads/2015/08/securitypattern.png?ssl=1"><img alt="securitypattern" src="https://i2.wp.com/gokhan-gokalp.com/wp-content/uploads/2015/08/securitypattern.png?resize=380%2C336&ssl=1" style="height:336px; width:380px" /></a></p> <ul> <li>Client kendi güvenlik bilgilerini girer ve bu bilgiler<strong> Authorization Server</strong>‘a gönderilir</li> <li>Authorization Server bu bilgileri doğrulursa, client’a bir <strong>Access Token</strong> Http Response’u döner.</li> <li>Client artık erişmek istediği servislere, elde etmiş olduğu Access Token’ı Http Request’in <strong>Authorization Header</strong>‘ına ekleyerek erişim sağlar.</li> </ul> <p>Bu ön bilgilerden sonra hemen örneğimize geçelim. Öncelikle örneğimizde <strong>Authentication</strong> işlemleri için <strong>OAuth 2.0</strong> protokolü ile sağlayacağız ve bunun için Microsoft’un <strong>Owin</strong> kütüphanesinden yararlanacağız.</p> <p>Owin temelinde <strong>IIS</strong> ile <strong>Application</strong> arasında kendi pipeline’ını kuruyor ve işlemleri burada handle ediyor. Lightweight bir pipeline’a sahiptir.</p> <p><a href="https://i1.wp.com/gokhan-gokalp.com/wp-content/uploads/2015/09/OWIN__2.png?ssl=1"><img alt="OWIN__2" src="https://i1.wp.com/gokhan-gokalp.com/wp-content/uploads/2015/09/OWIN__2.png?resize=797%2C277&ssl=1" style="height:216px; width:622px" /></a></p> <p><strong>AspNetWebAPIOAuth</strong> isminde bir <strong>Asp.Net Web Application</strong> oluşturuyorum. Oluştururken Template kısmından <strong>Empty</strong> seçip Core Referansını ise <strong>Web API</strong> seçerek tamamlıyorum.</p> <p>Projemizi oluşturduğumuza göre hemen projemiz üzerine sağ tıklayarak NuGet Package Manager’ı açıp Search kısmından <strong>OAuth</strong> yazarak çıkacak olan sonuçlar içinden<strong> Microsoft.AspNet.WebApi.Owin, Microsoft.Owin.Host.SystemWeb </strong>ve <strong>Microsoft.Owin.Security.OAuth</strong>‘u seçerek projemize kuruyoruz.</p> <p>Proje içerisine <strong>OAuth</strong> isimli bir klasör ekleyerek servis çalışmaya başlarken <strong>Owin </strong>pipeline’ını ayağa kaldırabilmek için <strong>Startup</strong> sınıfını hazırlamaya başlıyoruz ve içerisinde gerekli konfigürasyon ayarlarını <strong>WebApiConfig</strong>‘e register edip, <strong>Owin Server</strong> üzerinde uygulama oluşurken kullanacağı konfigürasyon ayarınıda belirtiyoruz.</p> <p>Startup.cs:</p> <pre> using AspNetWebAPIOAuth.OAuth.Providers; using Microsoft.Owin; using Microsoft.Owin.Security.OAuth; using Owin; using System; using System.Web.Http; [assembly: OwinStartup(typeof(AspNetWebAPIOAuth.OAuth.Startup))] namespace AspNetWebAPIOAuth.OAuth { // Servis çalışmaya başlarken Owin pipeline'ını ayağa kaldırabilmek için Startup'u hazırlıyoruz. public class Startup { public void Configuration(IAppBuilder appBuilder) { HttpConfiguration httpConfiguration = new HttpConfiguration(); ConfigureOAuth(appBuilder); WebApiConfig.Register(httpConfiguration); appBuilder.UseWebApi(httpConfiguration); } private void ConfigureOAuth(IAppBuilder appBuilder) { OAuthAuthorizationServerOptions oAuthAuthorizationServerOptions = new OAuthAuthorizationServerOptions() { TokenEndpointPath = new Microsoft.Owin.PathString("/token"), // token alacağımız path'i belirtiyoruz AccessTokenExpireTimeSpan = TimeSpan.FromDays(1), AllowInsecureHttp = true, Provider = new SimpleAuthorizationServerProvider() }; // AppBuilder'a token üretimini gerçekleştirebilmek için ilgili authorization ayarlarımızı veriyoruz. appBuilder.UseOAuthAuthorizationServer(oAuthAuthorizationServerOptions); // Authentication type olarak ise Bearer Authentication'ı kullanacağımızı belirtiyoruz. // Bearer token OAuth 2.0 ile gelen standartlaşmış token türüdür. // Herhangi kriptolu bir veriye ihtiyaç duymadan client tarafından token isteğinde bulunulur ve server belirli bir expire date'e sahip bir access_token üretir. // Bearer token üzerinde güvenlik SSL'e dayanır. // Bir diğer tip ise MAC token'dır. OAuth 1.0 versiyonunda kullanılıyor, hem client'a, hemde server tarafına implementasyonlardan dolayı ek maliyet çıkartmaktadır. Bu maliyetin yanı sıra ise Bearer token'a göre kaynak alış verişinin biraz daha güvenli olduğu söyleniyor çünkü client her request'inde veriyi hmac ile imzalayıp verileri kriptolu bir şekilde göndermeleri gerektiği için. appBuilder.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()); } } }</pre> <p>Owin ayarlarını başlangıçta içeren sınıfımızı oluşturduk. Sınıf satırlarındaki yorumlarda da belirttiğimiz üzere, Authentication type olarak <strong>Bearer Authentication</strong> kullanacağız. Sebebi ise daha fazla lightweight olup OAuth 2.0 ile standart bir hale gelmesi ve hem client hemde server side için authentication işlemlerini daha fazla kolaylaştırmasıdır. Ayrıca tüm işlemler her ne kadar bir access token üzerinden yürüyecek olsada, <strong>SSL</strong> ile client ile server arasındaki veri güvenliği sağlanmalıdır.</p> <p><strong>OAuthAuthorizationServerOptions</strong> ayarlarını tanımlarken <strong>Provider</strong> olarak <strong>OAuthAuthorizationServerProvider </strong>sınıfından miras alarak türeteceğimiz <strong>SimpleAuthorizationServerProvider</strong> ‘ı seçtik. Şimdi gelelim bu provider’ın kodlarını incelemeye. Öncesinde daha önce açtığımız OAuth klasörünün içine hemen bir <strong>Providers</strong> isminde klasör daha açarak içerisinde ilgili sınıfımızı oluşturuyoruz.</p> <p>SimpleAuthorizationServerProvider.cs:</p> <pre> using Microsoft.Owin.Security.OAuth; using System.Threading.Tasks; using System.Security.Claims; namespace AspNetWebAPIOAuth.OAuth.Providers { public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider { // OAuthAuthorizationServerProvider sınıfının client erişimine izin verebilmek için ilgili ValidateClientAuthentication metotunu override ediyoruz. public override async System.Threading.Tasks.Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) { context.Validated(); } // OAuthAuthorizationServerProvider sınıfının kaynak erişimine izin verebilmek için ilgili GrantResourceOwnerCredentials metotunu override ediyoruz. public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context) { // CORS ayarlarını set ediyoruz. context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" }); // Kullanıcının access_token alabilmesi için gerekli validation işlemlerini yapıyoruz. if (context.UserName == "Gokhan" && context.Password == "123456") { var identity = new ClaimsIdentity(context.Options.AuthenticationType); identity.AddClaim(new Claim("sub", context.UserName)); identity.AddClaim(new Claim("role", "user")); context.Validated(identity); } else { context.SetError("invalid_grant", "Kullanıcı adı veya şifre yanlış."); } } } }</pre> <p><strong>OAuthAuthorizationServerProvider </strong>sınıfının iki metodunu ezdiğimizi görüyoruz. Bunlardan birincisi, Client’ı doğrulamak için ki direkt olarak doğruladık biz. İkincisi ise asıl kaynak erişimine verilecek yetkilerin ayarlandığı ana kısım. Öncelikle burada CORS ayarlarını gerçekeleştirdik. Hemen <strong>CORS</strong> nedir hatırlatması yapmak gerekirse:</p> <blockquote> <p><strong>CORS</strong> domain’ler arası kaynak paylaşımını sağlamaya yarayan bir mekanizmadır. Bir domain’in bir başka domain’in kaynağını kullanabilmesini sağlar.</p> </blockquote> <p>Hızlıca CORS’u da tekrardan hatırladığımız üzere devamında koda baktığımızda da net bir şekilde görüldüğü gibi <strong>validation</strong> işlemlerini gerçekleştiriyoruz. Eğer kullanıcı geçerli bir kullanıcı ise bir kimlik yaratıp, <strong>context</strong> üzerinde doğruluyor.</p> <p>Evet şuan Owin için OAuth 2.0 implementasyonunu gerçekleştirmiş bulunuyoruz. Şimdi gelelim <strong>Controller</strong> üzerinde ki kullanımına. Hemen Controllers kısmına OrdersController ekliyorum ve içine List isminde bir metot tanımlıyorum. Form Authentication’dan da hatırlayabileceğiniz üzere metotların üstüne attirbute olarak [<strong>Authorize</strong>] attributunu ekliyorduk, Owin içinde aynı attribut’u kullanıyoruz.</p> <p>OrdersController.cs:</p> <pre> using System.Collections.Generic; using System.Web.Http; namespace AspNetWebAPIOAuth.Controllers { public class OrdersController : ApiController { [HttpGet] [Authorize] public List<string> List() { List<string> orders = new List<string>(); orders.Add("Elma"); orders.Add("Armut"); orders.Add("Erik"); return orders; } } }</pre> <p>Api tarafında herşey hazır olduğuna göre projemizi test edebiliriz. Ben tool olarak Postman’ı tercih ediyorum siz isterseniz Fiddler Composer’da kullanabilirsiniz. Postman data gönderirken bana daha fazla esneklik sağlıyor açıkcası. </p> <p>Öncelikle direkt olarak ilgili api metodumuza erişmeye çalıştığımızda alacağımız sonuca bir bakalım:</p> <p><a href="https://i1.wp.com/gokhan-gokalp.com/wp-content/uploads/2015/09/oauth_hata.jpg?ssl=1"><img alt="oauth_hata" src="https://i1.wp.com/gokhan-gokalp.com/wp-content/uploads/2015/09/oauth_hata.jpg?resize=1090%2C414&ssl=1" style="height:236px; width:622px" /></a></p> <p> </p> <p><strong>api/Orders/List</strong> url’i ile GET isteği attığımızda Authorization hatası aldığımızı görüyoruz. Öncelikle<strong> /token</strong> path’i ile belirttiğimiz adrese gidip geçerli bir <strong>access_token</strong> almalıyız.</p> <p>Bunun için <strong>POST</strong> tipinde<strong> /token</strong> url’ine Headers’a ve Body’e bir kaç parametre set ederek gitmemiz gerekmektedir.</p> <p>Headers’e eklenecek parametreler:</p> <p><strong>Header</strong>: Accept <strong>Value</strong>: application/json<br /> <strong>Header</strong>: Content-Type <strong>Value</strong>: application/x-www-form-urlencoded</p> <p>Body’e eklenecek parametreler:</p> <p>data tipi x-www-form-urlencoded olarak seçilip,</p> <p><strong>Key</strong>: grant_type <strong>Value</strong>: password<br /> <strong>Key</strong>: username <strong>Value</strong>: Gokhan (Kullanıcı adınız)<br /> <strong>Key</strong>: password <strong>Value</strong>: 123456 (Şifreniz)</p> <p>İlgili bilgileri girdikten sonra POST işlemini gerçekleştirelim ve gelen sonuca bakalım:</p> <p><a href="https://i0.wp.com/gokhan-gokalp.com/wp-content/uploads/2015/09/oauth_token.jpg?ssl=1"><img alt="oauth_token" src="https://i0.wp.com/gokhan-gokalp.com/wp-content/uploads/2015/09/oauth_token.jpg?resize=1086%2C577&ssl=1" style="height:331px; width:622px" /></a></p> <p> </p> <p>Geriye dönen JSON sorgusunda <strong>access_token</strong> oluşmuş ve expires_in süresi ile geldiğini görüyoruz. Bu süreyi hatırlarsak Startup kısmında konfigürasyon bölümünde <strong>AccessTokenExpireTimeSpan</strong> propertysi ile vermiştik.</p> <p>Artık bu token’ı kullanarak tekrardan <strong>api/Orders/List</strong> url’ine tekrardan bir GET sorgusunda bulunalım. Fakat bu sefer ilgili token’ı Header’a ekleyerek gönderiyoruz.</p> <p>Headers’e eklenecek parametreler:</p> <p><strong>Header</strong>: Content-Type <strong>Value</strong>: application/json<br /> <strong>Header</strong>: Authorization <strong>Value</strong>: Bearer jyMJNFpYdBOZxoUZsutu7vNe4JY–kdvdjTylrJi_rZPC5VUOFSTvej-Sq0jvCj1gYbg0HHAk6ILoj0U7G3zCYcl1lK9tA6YwMGODccsorhjwDTzuuGprU00f5j4Ly1DUhS54TejbrZtn1RMegSCXFfixjkYkeXeVd6eP0eGGrAr6f3ICVGz7KASR28soQEh_4sXpOZLmDpDJFKKAEoI_q0h9_7qvfIIjm8t0lDcCp4</p> <p>Token tipimiz <strong>Bearer</strong> olduğu için headerda Authorization kısmının değerine access_token’ı girmeden önce Bearer tag’ini ekleyip daha sonrasında access_token’ı ekliyoruz.</p> <p><a href="https://i1.wp.com/gokhan-gokalp.com/wp-content/uploads/2015/09/oauth_access.jpg?ssl=1"><img alt="oauth_access" src="https://i1.wp.com/gokhan-gokalp.com/wp-content/uploads/2015/09/oauth_access.jpg?resize=1078%2C494&ssl=1" style="height:285px; width:622px" /></a></p> <p> </p> <p>Servisten başarıyla bilgileri çektiğimizi görüyoruz. Bir sonraki Web API konumda ise Custom Token Based Authentication işlemi nasıl gerçekleştirilebilir hakkında bir şeyler yazmayı planlıyorum. Şimdilik sağlıcakla kalın.</p>