ASP.NET Core'da JWT ile Güvenliğin Sağlanması
JWT nedir?
JSON Web Token (JWT), bir uygulamaya erişim için gerekli olan access token'ı oluşturmak için kullanılan bir standarttır.
IdP sunucusu kullanıcı kimliğini onaylayan bir token üretir ve bunu istemciye gönderir. Bu üretilen token'ı istemci erişmek istediği kaynağa göndererek kimliğini doğrulamış olur.
JWT, Header, Payload ve Signature olmak üzere 3 kısımdan oluşmaktadır.
Header
Token tipinin ve kullanılan hash algoritmasıyla ilgili bilginin bulunduğu JSON formatındaki veridir.
{
"alg": "HS256",
"typ": "JWT"
}
Bu header örneğinde görüldüğü üzere token'ın hash algoritması olarak HS256 ve token tipi olarak ise JWT yazdığı görülmektedir.
Payload
Kimlik doğrulama için kullanılan bir JWT, kullanıcı kimliği ve kullanıcı rolü gibi kullanıcı hakkında bazı önemli bilgileri depolar.
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022,
"exp": 3600
}
Yukarıda payload'a ait örnek bir JSON verisi bulunmaktadır.
sub
kimlik için benzersiz bir değeri temsil etmektedir. name
kimliğin ismini belirtmektedir. iat
token'ın oluşturulduğu zamanı belirtir. exp
ise token'ın ne kadar süre geçerli olduğunu belirtir.
Payload, base64 olarak encode edildikten sonra token'ın ikinci parçası olarak saklanır.
Signature
JWT token'ının son bölümü olan signature, token'ın yetkili uygulama sunucuları dışında değiştirilmediğini veya oluşturulmadığını doğrulamak için kullanılan bir kimlik doğrulama kodudur.
Signature, bir şifreleme algoritması (header bilgisinde belirtilen hash algoritması) ve sunucuda depolanan bir secret bilgisi kullanılarak birleştirilmiş JWT header ve payload bilgileri imzalanarak oluşturulur.
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
your-256-bit-secret
)
Örnek Uygulama
Bu örneğimizde ASP.NET Core ve JWT'yi kullanarak bir korumalı endpoint'lere erişmeye çalışacağız.
Öncelikle aşağıdaki komutu terminalimize yazarak bir Web API projesi oluşturalım.
dotnet new webapi -n MonoSign.JWT
Proje başarıyla oluşturulduktan sonra ise JWT için gerekli olan Microsoft.AspNetCore.Authentication.JwtBearer paketini kurmamız gerekiyor. Bu paketi kurabilmek için terminalimize aşağıdaki komutu yazmamız gerekmektedir.
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
JWT ayarlarını yapabilmek için öncelikle Startup.cs
dosyasını aşağıdaki gibi düzenlememiz gerekmektedir.
using System;
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
namespace MonoSign.JWT
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(x =>
{
x.RequireHttpsMetadata = true;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = Configuration["Jwt:Issuer"],
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration["Jwt:Secret"])),
ValidAudience = Configuration["Jwt:Audience"],
ValidateAudience = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(1)
};
});
services.AddControllers();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
ConfigureServices
metodunun içerisinde ihtiyacımız olan ayarlamaları AddJwtBearer
ile yaptık.
Secret, Audience ve Issuer bilgilerini uygulamamızın appsettings.json
dosyasına yazmamız gerekmektedir. Dosyayı aşağıdaki şekilde düzenleyelim.
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"Jwt": {
"Issuer": "MonoSign",
"Audience": "https://localhost:5001",
"Secret": "555A4CBDF7323D4B7F7F8FC97E74E"
}
}
Bu ayarları yaptıktan sonra artık login, logout v.b. işlemleri yapacak düzenlemeleri yapmaya başlayabiliriz.
Token'ın Oluşturulması
Token oluşturabilmek için öncelikle projemize AuthController
isminde bir controller ekleyelim. Dosyayı aşağıdaki şekilde düzenleyelim.
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using MonoSign.JWT.Models;
namespace MonoSign.JWT.Controllers
{
[ApiController]
[Route("[controller]")]
public class AuthController : ControllerBase
{
private readonly IConfiguration _configuration;
public AuthController(IConfiguration configuration)
{
_configuration = configuration;
}
[HttpPost("login")]
public IActionResult Login([FromBody] LoginModel model)
{
if (model.UserName == "MonoFor" && model.Password == "WeAreMono2017")
{
var secret = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_configuration["Jwt:Secret"]));
var issuer = _configuration["Jwt:Issuer"];
var audience = _configuration["Jwt:Audience"];
var expiration = DateTime.UtcNow.AddMinutes(60);
var tokenHandler = new JwtSecurityTokenHandler();
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.NameIdentifier, model.UserName),
}),
Expires = expiration,
Issuer = issuer,
Audience = audience,
SigningCredentials = new SigningCredentials(secret, SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var accessToken = tokenHandler.WriteToken(token);
return Ok(new
{
AccessToken = accessToken
});
}
return Unauthorized();
}
}
}
Burada kullanıcın göndermiş olduğu kullanıcı adı ve şifre kontrolü sağlandıktan sonra JWT token'ının oluşturulma işlemi başlamıştır. appsettings.json
dosyasından okumuş olduğumuz Secret, Issuer ve Audience bilgisine göre token oluşturuldu. Ayrıca token'ın geçerlilik süresini 60 dakika olarak belirledik.
Şimdi gerekli testlerimizi yapalım. Uygulamamızı çalıştırdıktan sonra Postman veya Paw gibi bir uygulama ile https://localhost:5001/auth/login adresine POST isteği atalım.
Görüleceği üzere token oluşturma işlemi başarıyla tamamlandı.
Oluşan token'ın geçerli bilgileri içerip içermediğini kontrol etmek için jwt.io sitesini kullanacağız. Bu sitede JWT token'ları için debugger bulunmaktadır.
Siteye girdiğimiz zaman Encoded yazan yere uygulamamız tarafından oluşturulan token'ı yazalım. Yazdıktan sonra sağ taraftaki Decoded bölümünde token içeriğini görmüş olacağız.
Endpoint'lerin Korunması
Şimdi ise JWT ile korunmasını istediğimiz endpoint'lerin düzenlemelerini yapmaya geldi. Bu işlem için sadece controller'ı veya action'ı [Authorize]
attribute ile işaretlememiz yeterlidir. Örnek olarak aşağıdaki gibi bir controller üzerinden test edelim.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace MonoSign.JWT.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
[Authorize]
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
}
}
Görüleceği üzere Get action'ı Authorize attribute'ü ile işaretledik. Şimdi https://localhost:5001/weatherforecast adresine GET isteği atarak testimizi yapabiliriz.
Görüldüğü üzere herhangi bir token göndermediğimizden dolayı HTTP 401 Unauthorized cevabını aldık.
Şimdi ise oluşturduğumuz token'ı istekle beraber gönderelim. Token'ı Authorization
HTTP header'ı ile beraber göndereceğiz. Değer olarak ise Bearer <generated_token>
değerini göndereceğiz.
Görüldüğü üzere token'ı gönderdiğimiz zaman HTTP 200 OK ile beraber endpoint'den istemiş olduğumuz veriler de gelmiş oldu.
MonoSign Kullanarak JWT'nin Oluşturulması
IAM olarak MonoSign'ı kullanıyorsanız MonoSign üzerinden de JWT token'larını üretebilirsiniz.
Bunun için öncelikle MonoSign'ın Management ekranına giriş yapın. (Try it now üzerinden hesap oluşturduysanız https:<firma_adi>.monosign.com adresine girmeniz gerekmektedir.)
Giriş yaptıktan sonra sol tarafta bulunan menü listesinden Applications
'a tıklayın.
Applications sayfasında MonoSign üzerinde tanımlamış olduğunuz uygulamaların listesi gelecektir. Eğer yeni bir uygulama tanımlamak istiyorsanız sayfanın sağ üst köşesindeki Add New düğmesine tıklayarak bu işlemi gerçekleştirebilirsiniz. Mevcut bir uygulamayı kullanmak isterseniz uygulamanın Title bilgisine tıklamanız yeterlidir.
Uygulama detaylarının olduğu sayfaya girdikten sonra Keys
başlığına tıklayın.
Keys sekmesine girdiğinizde sizi uygulamanın JWT, oAuth, SAML, OpenId ve REST API gibi key çeşitleri karşılayacaktır. İlk sekmede JWT bilgilerini göreceksiniz.
Token'ı alabilmemiz için öncelikle Authorization Code'a ihtiyacımız var. Bunun için öncelikle Authorize Url
adresine bir GET isteği atıyoruz. Adresimiz https://<Authorize_URL>/jwt/authorize?client_id=<Client_ID>&response_type=code&redirect_uri=http://localhost
buna benzer şekilde olacaktır. Bu adrese istek attığınızda aşağıdaki gibi giriş ekranıyla karşılaşacaksınız.
Bu ekranda kullanıcı adı ve şifre bilgilerini doğru girdiğimiz takdirde MonoSign bizi https://localhost?code=<authorization_code>
adresine yönlendirecektir.
Artık elimizde Authorization Code olduğuna göre artık Access Token'ı alabiliriz. Bu işlem için Token Url
adresine POST isteği atmamız gerekmektedir. Örnek istek aşağıdaki gibidir:
POST /jwt/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Cookie: ms-lan=c%3Den%7Cuic%3Den
Host: <TOKEN_URL>
Connection: close
User-Agent: Paw/3.1.10 (Macintosh; OS X/10.15.7) GCDHTTPRequest
Content-Length: 1534
grant_type=authorization_code&client_id=<CLIENT_ID>&client_secret=<CLIENT_SECRET>&code=<AUTHORIZATION_CODE>&redirect_uri=http://localhost
client_id
, client_secret
ve code
alanlarını doldurup istek attığımızda artık access token'a erişmiş olacağız. Bu istek gönderildikten sonra aşağıdakine benzer bir cevap alacağız.
{
"token_type": "Bearer",
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NmI5YmYxZS0wY2Q3LTQyODEtYTBiOC1lOTkwYjUyZDgwYzMiLCJuYW1lIjoicm9vdCIsImp0aSI6IjkxZTAyZDY4LWMzM2QtNDlmMi1hMTYxLTdjNmY1OTRkZTE0NSIsInNpZCI6IjgzYjA5MDhmLTE1Y2UtNDQyOS05Y2U2LTgyMTc0NzZjOGEyZSIsInVuaXF1ZV9uYW1lIjoicm9vdCIsInNjb3BlIjpbInByb2ZpbGUiLCJvZmZsaW5lX2FjY2VzcyJdLCJwcm9maWxlX2lkIjoiZDgyOTRjZDMtMWNiOS00MDcxLWI2NTctZDFlNTUwNzgwZWQ2IiwicHJvZmlsZSI6eyJCaXJ0aERhdGUiOiJXZWQgU2VwIDE2IDIwMjAgMDA6MDA6MDAgR01UKzAzMDAgKEdNVCswMzowMCkifSwiZXhwIjoxNjAxOTgxNjY4fQ.Z000qbSo-zEhIzo-eBSO4K-XG0sfOEdQ72KB5EtTOUs",
"expires_in": 1800
}
Artık elimizde access token bilgisi bulunmaktadır.
Şimdi örnek uygulamamızı düzenlemeye başlayabiliriz. appsettings.json dosyasına eklemiş olduğumuz Secret bilgisini MonoSign Management ekranında yazan Shared Secret ile değiştirelim. Bu bilgiyi değiştirdikten sonra https://localhost:5001/weatherforecast adresine elimizdeki yeni access token ile beraber istek atalım.
Görüldüğü üzere isteğimiz HTTP 200 yanıtını dönmüş oldu. Artık token üretimi ve yönetimini başarıyla MonoSign'a devretmiş olduk.
https://github.com/monosign-blog/monosign-aspnetcore-jwt adresinden örnek projeye ulaşabilirsiniz.