Asp.Net Web API kullanarak RESTful servis geliştirirken Token Tabanlı Kimlik Doğrulama işlemini konuşacağız bu makalemde. Token Tabanlı Kimlik Doğrulama, kullanıcıların servislerimize güvenli bir şekilde erişmelerini sağlayan bir yöntemdir ve modern web uygulamalarında sıkça kullanılan bir güvenlik yöntemidir.
RESTful servisler, basit ve esnek bir yapı sunar ve HTTP protokolü üzerine kurulmuş olmaları nedeniyle günümüz web dünyasındaki birçok uygulamanın tercih ettiği bir yapıdır. Client-side tabanlı uygulamaların artmasıyla birlikte, REST servisleri, SOAP veya RPC gibi kompleks mimarilere göre daha hafif ve esnek bir şekilde veri transferini gerçekleştirme olanağı sağlar.
Token Tabanlı Kimlik Doğrulama, kullanıcıların kimliklerini doğrulamak için bir token (jeton) kullanır. Kullanıcılar kimlik doğrulama işleminden geçtikten sonra bir token alır ve bu tokeni her istekte sunucuya göndererek kimliklerini doğrularlar. Bu yöntem sayesinde, kullanıcıların sürekli olarak kimlik bilgilerini sunucuya göndermelerine gerek kalmadan, kimlik doğrulama süreci hızlandırılır ve güvenlik sağlanmış olur.
Token Tabanlı Kimlik Doğrulama konusunu daha ayrıntılı ele alırsak, bu yöntem kullanıcıların servislere güvenli bir şekilde erişimini sağlamak için token tabanlı bir kimlik doğrulama yöntemidir. Kullanıcılar kimlik doğrulama işleminden geçtikten sonra bir token alır ve bu tokeni her istekte sunucuya göndererek kimliklerini doğrularlar. Token genellikle şifrelenmiş bir veri parçasıdır ve sunucu tarafında doğrulanır. Bu yöntem, kullanıcıların kimlik bilgilerini sürekli olarak göndermelerine gerek kalmadan güvenli bir şekilde servislere erişmelerini sağlar.
Günümüzde hemen hemen tüm uygulamaların mobil bileşeni bulunuyor veya servis mimarileri mobil desteğe sahip şekilde geliştiriliyor. Bu tür servisler genellikle REST mimarisi üzerine kurulmuş ve güvenlik için token (jeton) tabanlı yetkilendirme yöntemleri kullanıyor. Token tabanlı kimlik doğrulama, kullanıcıların kimlik bilgilerini güvence altına alarak güvenli iletişim ve etkili kullanıcı yetkilendirme sağlar. Bu yöntem, popüler teknolojilere kolayca entegre edilebilir ve uygulamaların güvenliğini artırmak için önemli bir adımdır.
Token tabanlı kimlik doğrulama sürecine bir bakalım. İlk olarak, WebApi istemcisi kendi güvenlik bilgilerini girer ve bu bilgiler Authorization Server’a gönderilir. Authorization Server, bu bilgileri doğrularsa istemciye bir Erişim Jetonu (Access Token) HTTP yanıtı döndürür. Artık istemci, erişmek istediği servislere, elde ettiği Erişim Jetonunu HTTP isteğinin Authorization Header’ına ekleyerek erişim sağlar.
Bu ön bilgilerden sonra, örneğimize geçebiliriz. Öncelikle OAuth 2.0 protokolünü kullanarak Authentication işlemleri için Microsoft’un Owin kütüphanesinden yararlanacağız. Owin, IIS ve uygulama arasında kendi pipeline’ını kurarak işlemleri yöneten hafif bir yapıdır. “OWIN__2 AspNetWebAPIOAuth” adında bir ASP.NET Web Application projesi oluşturuyoruz. Template olarak “Empty” seçip Core Referansını “Web API” olarak belirterek projeyi tamamlıyoruz.
Projemizi oluşturduktan sonra, NuGet Package Manager’ı açıp “OAuth” kelimesini aratarak sonuçlar arasından “Microsoft.AspNet.WebApi.Owin”, “Microsoft.Owin.Host.SystemWeb” ve “Microsoft.Owin.Security.OAuth” paketlerini seçerek projeye ekliyoruz. Proje içine “OAuth” adında bir klasör ekleyerek, Owin pipeline’ını başlatmak için kullanılacak olan Startup sınıfını hazırlamaya başlıyoruz ve gerekli konfigürasyon ayarlarını WebApiConfig’e kaydedip, Owin Sunucusu üzerinde uygulamanın kullanacağı konfigürasyon ayarını da belirtiyoruz. Startup.cs:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using System;
namespace AspNetWebAPIOAuth.OAuth
{
public class Startup
{
// Servis çalışmaya başlarken Owin pipeline’ını ayağa kaldırabilmek için Startup sınıfını hazırlıyoruz.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
HttpConfiguration httpConfiguration = new HttpConfiguration();
ConfigureOAuth(app);
WebApiConfig.Register(httpConfiguration);
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
private void ConfigureOAuth(IApplicationBuilder app)
{
OAuthAuthorizationServerOptions oAuthAuthorizationServerOptions = new OAuthAuthorizationServerOptions()
{
TokenEndpointPath = new 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.
app.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.
app.UseAuthentication();
app.UseAuthorization();
}
}
}
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 Bearer Authentication kullanacağız. Sebebi ise daha hafif olması, OAuth 2.0 ile standart bir hale gelmesi ve hem istemci (client) hem de sunucu (server) tarafı için authentication işlemlerini daha kolaylaştırmasıdır. Ayrıca tüm işlemler her ne kadar bir erişim belirteci (access token) üzerinden yürüyecek olsa da, istemci ile sunucu arasındaki veri güvenliği SSL ile sağlanmalıdır.
OAuthAuthorizationServerOptions ayarlarını tanımlarken Provider olarak OAuthAuthorizationServerProvider sınıfından miras alarak türeteceğimiz SimpleAuthorizationServerProvider sınıfını seçtik. Şimdi bu provider sınıfının kodlarını incelemeye geçelim. Öncesinde daha önce açtığımız OAuth klasörünün içine hemen bir Providers isimli klasör daha oluşturarak içerisinde ilgili sınıfımızı oluşturuyoruz. SimpleAuthorizationServerProvider.cs:
using Microsoft.AspNetCore.Owin;
using Microsoft.Owin.Security.OAuth;
using System.Security.Claims;
using System.Threading.Tasks;
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 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 == “xxx” && 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ış.”);
}
}
}
}
OAuthAuthorizationServerProvider sınıfının iki metodunu override ederek ezdiğimizi görebiliriz. Birincisi, ValidateClientAuthentication metodu ile Client’ın doğrulanmasıdır ve doğrulamayı doğrudan gerçekleştirdik. İkincisi ise GrantResourceOwnerCredentials metodu ile asıl kaynak erişimine verilecek yetkilerin ayarlandığı ana kısımdır. Bu kısımda ayrıca CORS (Cross-Origin Resource Sharing) ayarlarını da gerçekleştirdik.
CORS, farklı bir kök alan (domain, protokol veya port) üzerinde bulunan bir web kaynağına erişim yapmaya çalıştığımızda, tarayıcının bu isteği engelleyebildiği bir güvenlik mekanizmasıdır. Bu mekanizma, aynı kök alandan olmayan kaynaklara yapılan isteklere izin vermediği varsayımına dayanır.
Örneğin yukarıdaki kodda, context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
satırı ile CORS ayarları yapılmıştır. “*” ifadesi, tüm kök alanlardan gelen isteklere izin verildiğini belirtir. Bu, herhangi bir kök alandan gelen isteklere API’ye erişim izni verir. Ancak, güvenlik gereksinimlerinize bağlı olarak, bu değeri daha kısıtlı bir kök alan listesi veya belirli bir kök alana dönüştürebilirsiniz.
OAuthAuthorizationServerProvider sınıfının iki metodunu override ederek ezdiğimizi görüyoruz. Birincisi, Client’ı doğrulamak için direkt olarak doğruladık. İkincisi ise asıl kaynak erişimine verilecek yetkilerin ayarlandığı ana kısımdır. Bu kısımda ayrıca CORS (Cross-Origin Resource Sharing) ayarlarını da gerçekleştirdik. CORS, domainler arası kaynak paylaşımını sağlayan bir mekanizmadır ve bir domainin başka bir domainin kaynağını kullanabilmesine izin verir.
Kodda, validation işlemlerini gerçekleştirerek kullanıcının geçerli bir kullanıcı olup olmadığını doğrulayıp, bir kimlik yaratıp context üzerinde doğruluyoruz. Böylece Owin için OAuth 2.0 implementasyonunu tamamlamış oluyoruz. Şimdi gelelim Controller üzerinde kullanımına. OrdersController isimli bir Controller ekleyerek içine List isimli bir metot tanımlıyoruz. Form Authentication’dan hatırlayabileceğiniz gibi, metotların üzerine [Authorize] attribute’ını ekleyerek, Owin içinde aynı attribute’ı kullanarak yetkilendirme işlemini gerçekleştiriyoruz. OrdersController.cs dosyası aşağıdaki gibi olabilir:
using System.Collections.Generic;
using System.Web.Http;
using Microsoft.Owin.Security.OAuth;
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;
}
}
}
Bu örnekte, OrdersController isimli bir Controller içinde List isimli bir GET metodu tanımlanmıştır. Bu metot [Authorize] attribute’ı ile yetkilendirme yapılarak sadece yetkilendirilmiş kullanıcıların erişebileceği bir endpoint olmuştur. Metot, bir liste olan “orders” isimli string listesini doldurarak döndürmektedir. Bu örnekte, OrdersController üzerinde Owin ile OAuth 2.0 yetkilendirme kullanılarak API endpointine yetkilendirilmiş erişim sağlanmaktadır.
Projenizin API tarafında her şeyin hazır olduğunu düşünerek, Postman gibi bir aracı kullanarak API’nizi test edebilirsiniz. Postman, veri gönderirken daha fazla esneklik sağladığı için tercih edebileceğiniz bir araçtır.
Öncelikle API metodunuza doğrudan erişmeye çalıştığınızda “oauth_hata” hatası alacağınızı görürsünüz. Bunun nedeni, API endpointine erişim için yetkilendirme gerekliliğidir. Öncelikle “/token” yoluna gidip geçerli bir “access_token” almanız gerekmektedir. Bunun için POST tipinde “/token” URL’sine Headers ve Body kısımlarına birkaç parametre ekleyerek isteği göndermeniz gerekmektedir.
Headers bölümüne eklenmesi gereken parametreler: Header: Accept Value: application/json Header: Content-Type Value: application/x-www-form-urlencoded
Body bölümüne eklenmesi gereken parametreler: Data tipi “x-www-form-urlencoded” olarak seçilip,
Key: grant_type Value: password
Key: username Value: xxx (Kullanıcı adınız)
Key: password Value: 123456 (Şifreniz)
Gerekli bilgileri girdikten sonra POST işlemini gerçekleştirerek dönen sonuca bakabiliriz.
JSON formatında dönen sonuçta “access_token” oluşmuş ve “expires_in” süresi ile geldiğini görebiliriz. Bu süre, Startup kısmında yapılandırdığımız “AccessTokenExpireTimeSpan” özelliği ile belirlediğimiz süredir.
Artık bu “access_token” kullanarak tekrar API endpointi olan “/Orders/List” URL’ine GET isteği göndererek sonuçları alabiliriz. Ancak bu sefer “access_token”ı Header’a ekleyerek göndermemiz gerekmektedir.
Headers bölümüne eklenmesi gereken parametreler:
Header: Content-Type Value: application/json
Header: Authorization Value: Bearer jyMJNFpYdBOZxoUZsutu7vNe4JY–kdvdjTylrJi_rZPC5VUOFSTvej-Sq0jvCj1gYbg0HHAk6ILoj0U7G3zCYcl1lK9tA6YwMGODccsorhjwDTzuuGprU00f5j4Ly1DUhS54TejbrZtn1RMegSCXFfixjkYkeXeVd6eP0eGGrAr6f3ICVGz7KASR28soQEh_4sXpOZLmDpDJFKKAEoI_q0h9_7qvfIIjm8t0lDcCp4
“Bearer” token tipi olduğu için Authorization bölümünün değerine “access_token”i girmeden önce “Bearer” etiketini eklememiz gerekmektedir. Daha sonrasında “access_token”ı ekleyerek isteği göndeririz.
Bugünlük bukadar bu makalede sizlere Asp.Net Web API – Token Based Authentication konusunu anlatmaya çalıştım. Kendinize iyi bakın.