← Back to Posts

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.