Abdulsamet İLERİ
4 min readDec 31, 2019

--

ASP.NET Core Uygulamasına GitHub Girişi Ekleme (OAuth)

Bu yazımıda kullanıcılarımızın GitHub hesaplarını kullanarak uygulamamıza giriş yapabilmelerini sağlayacağız. Başlıyalım.

1. OAuth’a Bir Bakalım

Kabaca bakarsak aslında iş şundan ibaret. Yaptığı iş yetki işlemini sağlayıcı’ya (bizim durumumuzda GitHub oluyor, Google, Facebook vs.) delege etmek. Sağlayıcımız kullanıcıyı yetkilendirdikten sonra, uygulamamıza geçici bir token ile geri döndürüyor sonrasında biz bu tokeni kullanarak access tokeni elde ediyoruz ve işlemlerimize güvenli bir şekilde devam ediyoruz. Olayı bizim senaryomuz üzerinden bakacaksak da şöyle;

Kullanıcı, Servis Sağlayıcının (GitHub)’ın sağladığı authorization endpoint’ine yetki almak için istek atıyor.

Servis Sağlayıcı kullanıcının kimliğini doğruluyor. Kullanıcının, uygulamaya kendi bilgilerine erişmesi için yetki verip vermeyeceğini soruyor.

Eğer kullanıcı yetkiyi verirse, servis sağlayıcımız kullanıcıyı, uygulamamız için daha önceden providerin sitesinde tanımlanan redirect uri’ye geçici bir token ile geri döndürüyor.

Uygulamamız da bu geçici token’i kullanarak, Sağlayıcının token endpoint’ine istek atıp ve kalıcı access tokenini elde ediyor.

Bu işlemlerin hepsi için bilinmesi gereken 5 önemli şey var.

Client ID ve Client Secret => Bu Sağlayıcıya uygulamamızı kaydettiğimiz zaman, sağlayıcı tarafından üretiliyor.

Redirect URI (Callback URL)=> Bunu biz kendimiz veriyoruz.

Authorization ve Token Endpointleride => Sağlayıcının dökümantasyonunda belirtiliyor.

GET https://github.com/login/oauth/authorize
POST https://github.com/login/oauth/access_token

2. Öncelikle GitHub Üzerinde OAuth Uygulamasını Oluşturmalıyız.

Dökümentasyonda nasıl yapılacağını resimlerle zenginleştirilip güzel bir şekilde anlatılmış, buradan bakabilirsiniz. Uygulamızı oluştururken en çok dikkat etmemiz gereken Authorization callback URL kısmı. Bunu https://localhost:5001/github-giris şeklinde veriyorum. Daha sonra buraya detaylıca bakacağız. Uygulamamızı oluşturduktan sonra GitHub bizim için Client ID ve Client Secret üretiyor, biz de bunları OAuth Middleware’imizde kullanacağız.

3. Uygulamamızı Oluşturup Authentication Middleware’imizi Ekleyelim

Startup sınıfında ConfigureServices metoduna gelip authentication middlewareimizi kayıt edelim.

services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
// Eğer authentication gerekli ise ve cookie de yoksa GitHub ile giriş yap.
options.DefaultChallengeScheme = "GitHub";
})
.AddCookie()
.AddOAuth("GitHub", options => {
{
options.ClientId = Configuration["CLIENT_ID"];
options.ClientSecret = Configuration["CLIENT_SECRET"];
options.CallbackPath = new PathString("/github-giris");

options.AuthorizationEndpoint = "https://github.com/login/oauth/authorize";
options.TokenEndpoint = "https://github.com/login/oauth/access_token";
options.UserInformationEndpoint = "https://api.github.com/user";

options.ClaimActions.MapJsonKey(ClaimTypes.Name, "name");
options.ClaimActions.MapJsonKey(ClaimTypes.Email, "email");
options.ClaimActions.MapJsonKey(ClaimTypes.UserData, "login");

options.Scope.Add("read:org");

options.SaveTokens = true;

options.Events = new OAuthEvents
{
OnCreatingTicket = async context =>
{
var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);

var response = await context.Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, context.HttpContext.RequestAborted);
response.EnsureSuccessStatusCode();

var jsonString = await response.Content.ReadAsStringAsync();
var user = JsonSerializer.Deserialize<JsonElement>(jsonString);
context.RunClaimActions(user);
}
};

}});

Şimdi yapılan işlemleri sırasıyla inceleyelim.

options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "GitHub";

Default olarak cookie authentication handler kullan dedikten sonra ChallangeScheme’ya sağlayıcımızı, yani GitHub veriyoruz. Bu atama ile uygulamamıza dedirtiyoruz ki, doğrulanmamış bir kullanıcı, yetki gerektiren bir url’e girmek istediği zaman GitHub OAuth service ile onu doğrula.

.AddCookie()

AddCookie() ile Cookie Aauthentication Handleri kayıt ediyoruz.

.AddOAuth("GitHub", options => {
...
}

AddOAuth() ile OAuth Authentication Handleri kayıt ediyoruz. İlk parametresi de yukarıda ChallangeScheme verdiğimiz sağlayıcımız.

Burda kayıt etme sırası önemli, ilk başta cookie middleware çalışacak sonrasında gerekli ise OAuth devreye girecek. Mesela kullanıcının verisi Cookiede varsa, OAuth devreye girmeden, Cookieden veri okunup öyle devam edilecek.

options.ClientId = Configuration["CLIENT_ID"];
options.ClientSecret = Configuration["CLIENT_SECRET"];
options.CallbackPath = new PathString("/github-giris");

Client ID ve Client Secret’i sağlayıcımızın sitesinden alıyoruz. Ben burada secret storage kullanıyorum. CallbackPath ise adı üstünde, sağlayıcı kullanıcıdan yetkiyi aldıktan sonra CallbackPath’e ne verdiysek oraya yönleniyor (redirect). Burda önemli olan nokta sağlayıcının sitesinde Authorization callback URL hangi url’i verdiysek onla aynı olacak. Yukarıda bahsettiğimiz sağlayıcının ürettiği geçici kod bu url üzerinden veriliyor.

https://localhost:5001/github-giris?code=4064befa6813fd3df424&state=CfDJ8MOkigGk-cdNsZ_Ea4yErbv….

uygulamamız çalışırken loglara baktığımızda verdiğimiz callback url’yi görüyoruz içinde parametre olarak sağlayıcının geçtiği bir takım değerler var. Mesela ordaki code değeriyle kalıcı token’i elde ediyoruz. Middlewareimiz araya giriyor bu geçici code değerleriyle access tokeni elde ediyor. Fakat bunun içinde bizim sağlayıcının bize sağladığı birkaç endpoint’ide middleware’e söylememiz gerekiyor. Onları da AuthorizationEndpoint, TokenEndpoint, UserInformationEndpoint olarak yukarıda olduğu gibi belirtiyoruz. Uygulamamız access tokeni aldıktan sonra UserInformation endpointine istek atıyor. O endpointden gelen json da şu şekilde:

Biz de, ClaimActionlar ile bu json datadan istediğimiz verileri alıp onları claim şeklinde ekliyoruz ki global olarak uygulamamızın başka yerlerinden kullanalım. User aslında HttpContext’in içinde yer alan bir ClaimsPrincipal objesi ve authentication middleware tarafından ataması yapılıyor.

var name = User.FindFirst(c => c.Type == ClaimTypes.Name)?.Value;
var email = User.FindFirst(c => c.Type == ClaimTypes.Email)?.Value;

Gibi. Sonraki gelen isteklerde doğrulanmış kullanıcının bilgileri güvenli bir şekilde cookie de tutulduğundan, oradan alınıp kullanılıyor.

options.Scope.Add("read:org");

Scope ile de kullanıcının GitHub hesabından okuyacağımız verilerin izinleri vs. ayarlıyoruz. Örnek olarak biz read:org diyerek kullanıcının hangi organizasyonda olduğunu bilgisini okumak istediğimizi söylüyoruz. GitHub bunu kullanıcıdan onay istediği ekranda gösteriyor. Bu uygulama şunu şunu yapmak istiyor izin veriyor musun diye..

options.SaveTokens = true;

SaveToken’s le GitHub bize kalıcı olarak verdiği access token’i kaydet diyoruz böylelikle uygulamanın herhangi bir yerinden HttpContext.GetTokenAsync(“access_token”); ile access tokeni alıp, yetki gerektiren başka endpointlere de istek yapabiliyoruz.

OnCreatingTicket = async context =>
{
var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);

var response = await context.Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, context.HttpContext.RequestAborted);
response.EnsureSuccessStatusCode();

var jsonString = await response.Content.ReadAsStringAsync();
var user = JsonSerializer.Deserialize<JsonElement>(jsonString);
context.RunClaimActions(user);
}

Burada ise user information endpoint’e istek atılıyor sonrasında gelen json deserialize ediliyor. (Newtonsoft ile değil tabi :( .)

Sonrasında oluşturduğumuz bu middleware’i Configure metoduna ekliyoruz.

Sonrasında bir controller açıp işlemleri deniyoruz.

[HttpGet]
public IActionResult Login(string returnUrl = "/")
{
return Challenge(new AuthenticationProperties() { RedirectUri = returnUrl });
}

Client bu url’ye istek atınca Challenge ile GitHub’a işi kitle, Githubdan dönünce Redirect URI’ye ne verdiysem ona yönlen diyip yetkilendirme işini de bu şekilde yapıyoruz. (Tabi oraya yönlenmeden middleware’imiz kalıcı token’i elde ediyor.)

İşte bu kadar.

--

--