.Net Core Web API – JWT + Identity Kullanımı

10 Ağu

Merhaba arkadaşlar,
Bugün sizlere JWT(Json Web Token)’nin identity ile birlikte kullanımını anlatmaya çalışacağım.
Öncelikle bir proje oluşturalım. ASP.NET Core Web API seçtim ve Enable OpenAPI support’u işaretleyerek ilerledim. Bu seçenek isteğe bağlıdır.

Henüz işlemlere geçmeden önce ilgili kütüphaneleri projemize ekleyelim. Proje boyunca bu kütüphaneleri kullanıyor olacağız.

Modeller

Products modeli

public class Products
{
	[Required]
	public int Id { get; set; }

	[Required]
	[StringLength(60)]
	[DisplayName("Adı")]
	public string Name { get; set; }

	[Required]
	[StringLength(60)]
	[DisplayName("Birim fiyat")]
	public float UnitPrice { get; set; }
}

ApplicationUser modeli
Identity modelinde yan yana ad soyad tutmak istediğim için extend edip FullName alanı açtım.

public class ApplicationUser : IdentityUser
    {
        [Required]
        [DisplayName("Ad soyad")]
        [StringLength(60)]
        public string FullName { get; set; }
    }

ApplicationUserTokens modeli
Identity modelinde, tabloda ben expireDate tutmak istediğim için böyle bir alan açtım.
Buradaki expireDate göre kullanıcı işlemler yapacağım.

public class ApplicationUserTokens : IdentityUserToken<string>
    {
        public DateTime ExpireDate { get; set; }
    }

DbContext classı;

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

namespace JwtTokenWithIdentity.Models
{
    public class APIDbContext : IdentityDbContext<ApplicationUser>
    {
        public readonly IHttpContextAccessor httpContextAccessor;

        public APIDbContext(DbContextOptions<APIDbContext> options, IHttpContextAccessor httpContextAccessor)
            : base(options)
        {
            this.httpContextAccessor = httpContextAccessor;
        }

        public DbSet<ApplicationUserTokens> ApplicationUserTokens { get; set; }
        public DbSet<Products> Products { get; set; }


        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);

            builder.Entity<Products>().ToTable("Products");
            builder.Entity<Products>().HasIndex(x => x.Name).IsUnique();
        }

    }
}

appsettings.json dosyasına JWT ve SQL ile ilgili bağlantı bilgilerimi girdim.
Local ortamımızda appsettings.Development.json dosyası çalışır, bu yüzden geçerli bilgileri burayada tanımladığınızdan emin olun.

{
  "ConnectionStrings": {
    "Connection": "Server=.\\SQLInstanceName;Database=JwtWithIdentityDb;Trusted_Connection=True;MultipleActiveResultSets=true"
  },

  "Application": {
    "Secret": "CustomSecretKey111.",
    "Audience": "JwtTokenWithIdentity",
    "Issuer": "JwtTokenWithIdentity"

  },

  "Roles": {
    "Admin": "Admin",
    "Manager": "Manager",
    "User": "User"
  },

  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

Şimdi sıra geldi, sisteme JWT kullanacağımızı belirttiğimiz aşamaya. Startup.cs dosyamı aşağıdaki şekilde düzenledim.
Burada her bir alanı tek tek açıklamayacağım, sitesine gidip merak ettiklerinize bakabilirsiniz. Özetle yaptığımız işlem, sisteme JWT ve Identity‘i kullanacağımızı belirtmek oldu.

secretStr En önemlisi budur diyebiliriz. Burada yazdığımız key ile JWT token bilgilerini doğrulayacak. O yüzden bu keyi sistem içinde appsettingsten aldık. Her doğrulamayı buraya yazacağımız key ile yapacak.

ClockSkew = TimeSpan.Zero özelliğini göreceksiniz. Bu özellik, saniye bazında token’ı saklayabilirim diye belirttiğimiz bir özellik. Böylece token oluştururken saniyelik token oluşturabileceğim.
Token oluştururken şu kadar saat, gün, ay, yıl diye tokenın geçerlilik süresini belirttiğimiz bir ayar mevcut. Token oluşturken bunu göreceksiniz.
Bu ayar tanımlanmamışsa saniyelik token oluşturamıyoruz bu yüzden ben test amaçlı yazmış oldum. Sizler saniyelik token oluşturmayacaksanız bu özelliği hiç set etmeyebilirsiniz.
Bu özellik hiç set edilmediğinde, sistem saniyelik token oluşturulmasına izin vermiyor. Sanırım bu durumda minimum 5 ya da 15 dk’lık token oluşturabilirsiniz.
Yani sizler zaten 15 dk’dan az bir token süresi tanımlamayacaksanız bu özelliği hiç tanımlamanıza gerek yok.

Configure methodu içinde app.UseAuthentication(); app.UseRouting(); app.UseAuthorization(); kodları ile autorization kullanacağımızı belirtmiş olduk.
Bu ayar ile [Authroize] property’lerinin identity ile birlikte çalışmasını sağlamış oluyoruz. Eğer bu ayarları yapmazsak, token doğru olsa bile identity tanımadığı için 401 Unauthorized hatası almaya devam ederiz. O yüzden bu kısımların doğru yapılması çok önemli.

Startup.cs

using JwtTokenWithIdentity.Models;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using System;
using System.Text;

namespace JwtTokenWithIdentity
{
    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)
        {
            string connectionString = Configuration.GetConnectionString("Connection");
            string secretStr = Configuration["Application:Secret"];
            byte[] secret = Encoding.UTF8.GetBytes(secretStr);

            services.AddDbContext<APIDbContext>(options =>
                                                options.UseSqlServer(connectionString));

            services.AddCors();

            services.AddControllers().AddNewtonsoftJson(options =>
                                                        options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);

            // rol bazlı attribute lar çalışsın diye eklendi
            services.AddIdentity<ApplicationUser, IdentityRole>()
                    .AddRoleManager<RoleManager<IdentityRole>>()
                    .AddEntityFrameworkStores<APIDbContext>().AddDefaultTokenProviders();

            services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
            }).AddJwtBearer(options =>
            {
                options.Audience = Configuration["Application:Audience"];
                options.SaveToken = true;
                options.RequireHttpsMetadata = false;
                options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
                {
                    IssuerSigningKey = new SymmetricSecurityKey(secret),
                    ValidateLifetime = true,
                    ValidateIssuerSigningKey = true,
                    ValidateIssuer = false,
                    ValidateAudience = true,
                    ClockSkew = TimeSpan.Zero
                };
            });

            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "JwtTokenWithIdentity", Version = "v1", Description = "JwtTokenWithIdentity test app" });
                c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
                {
                    Name = "Authorization",
                    Type = SecuritySchemeType.ApiKey,
                    Scheme = "Bearer",
                    BearerFormat = "JWT",
                    In = ParameterLocation.Header,
                    Description = "Enter 'Bearer' [space] and then your valid token in the text input below.\r\n\r\nExample: \"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\"",
                });

                c.AddSecurityRequirement(new OpenApiSecurityRequirement
                {
                    {
                        new OpenApiSecurityScheme
                            {
                                Reference = new OpenApiReference
                                {
                                    Type = ReferenceType.SecurityScheme,
                                    Id = "Bearer"
                                }
                            },
                            new string[] {}
                    }
                });
            });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, APIDbContext context)
        {
            //if (env.IsDevelopment())
            //{
                app.UseDeveloperExceptionPage();
                app.UseSwagger();
                app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "JwtTokenWithIdentity v1"));
            //}

            app.UseCors(x => x
            .SetIsOriginAllowed(origin => true)
            .AllowAnyOrigin()
            .AllowAnyMethod()
            .AllowAnyHeader());

            app.UseAuthentication();
            app.UseRouting();
            app.UseAuthorization();

            context.Database.Migrate();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

Şuan Modellerimiz ve startup dosyamız hazır. Artık controller kısmına geçebiliriz.

Controller

ProductsController classını oluşturalım.

using JwtTokenWithIdentity.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace JwtTokenWithIdentity.Controllers
{
    [Authorize]
    [Route("[controller]")]
    [ApiController]
    public class ProductsController : ControllerBase
    {
        private readonly APIDbContext _context;

        public ProductsController(APIDbContext context)
        {
            _context = context;
        }

        [HttpGet]
        [Route("GetAll")]
        public ActionResult GetAll()
        {
            List<Products> productList = new List<Products>();

            foreach (var item in _context.Products)
            {
                productList.Add(item);
            }

            return Ok(productList);
        }
    }
}

Products controllerinda Authorize özelliği ile token alınmadan, giriş yapılmadan bu controllera ulaşılamayacağını belirttik.

Standart olarak proje oluşturduğumuzda gelen WeatherForecastController classını Authorize özelliği koymadan bıraktım.

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace JwtTokenWithIdentity.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        private static readonly string[] Summaries = new[]
        {
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };

        private readonly ILogger<WeatherForecastController> _logger;

        public WeatherForecastController(ILogger<WeatherForecastController> logger)
        {
            _logger = logger;
        }

        [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();
        }
    }
}

Şimdi token almadan projemizi bu hali ile test edelim. Ben migration aktif ettiğim için, migration aktif edip, update-database ile SQL’de database oluşturulmasını sağladım.

Migration aktif etmek için, Microsoft.EntityFrameworkCore.Tools yüklü olmalıdır. Sonrasında package manager console açarak sırasıyla
1- enable-migrations -EnableAutomaticMigration:$true
2- add-migration migrationName
3- update-Database

Oluşan database aşağıdaki şekilde. Otomatik olarak Identity tabloları oluşturuldu.

Çalıştırdığımızda, bizleri Swagger UI arayüzü karşılayacak. OpenAPI Support işaretlediğimiz için otomatik yardımcı bir arüyüz çıkıyor. Swagger ayarlarınıda düzgün yaptığımız için şöyle hoş bir ekran geliyor.

Postman ya da farklı bir tool açıp /Products/GetAll controllerına istek attığımızda, token almadığımız için 401 Unauthorized hatasını göreceğiz.
ProductsController classına [Authorize] özelliğini koyduğumuz için, buraya erişimi kısıtlamış olduk. Kullanıcı önce token almalı ve aldığı token ile oturum açmalı, bu yüzden 401 hatası alıyoruz.

WeatherForecast methoduna istek attığımızda ise 200 Ok ile verilerin geldiğini görebilirsiniz.

Şimdi gelin sisteme giriş yapıp Token alalım ve bu Token ile Product verilerini çekmeye çalışalım.

Token işlemleri

Token işlemlerinde kullanmak için şöyle bir model oluşturdum.

public class TokenInfo
{
	public string Token { get; set; }
	public DateTime ExpireDate { get; set; }
}

Ben düzenli olmalı açısından Library isimli bir klasör açıp, onun içine AccessTokenGenerator isimli bir class oluşturdum.
Biz bu classın GetToken methodunu çağırıp direkt kullanacağız.

Detayları kodların commentlerinde görebileceksiniz fakat GenerateToken methodu için bir kaç açıklama yapmama izin verin;
ExpireDate: Bu değişken üzerinden tokenın geçerlilik süresini belirtiyorum. Dikkat ederseniz ben 50 saniye verdim. Bunu yapma amacım, tokenın çalışıp çalışmadığını anlamak.
Yeni bir token alıp, 50 saniye içinde Authorize özelliğine sahip her bir controllera erişim sağlayabileceğiz.
Fakat 50 saniye sonunda, aynı token ile istek attığımızda, 401 Unauthorized hatası aldığımızı göreceğiz.
Bu arada Startup.cs dosyasında bahsettiğim ClockSkew özelliğini TimeSpan.Zero yapmasaydık, biz burada 50 saniye versek bile sistem minimum olarak(5 ya da 15 tam hatırlamıyorum) bir süre atacaktı.

Claim: Token’a özel bilgilerin yer aldığı kısımdır. İstediğiniz her bilgi claimler üzerinde tutabilirsiniz. Ben özellikle email ve NameIdentifier bilgisini burada tutacağım. Aktif kullanıcımın Id’sini burada tutacağım. Kullanıcı Id’sini claimler ile taşımak pek güvenlikli/sağlıklı olmayabilir, bilemedim. Neyse sizler email üzerinden de doğrulama yapabilirsiniz. Ben bu örnekte id üzerinden gideceğim. Yani tokena bağlı kullanıcıyı claim içine gömdüğüm kullanıcı Idsi ile hangi kullanıcı olup olmadığını yakalayabileceğim.

SigningCredentials: Şifreleme türünü belirttiğimiz yer, genelde kullanılan HmacSha256Signature şifreleme türüdür. Detaylar hakkında jwt sitesine göz atabilirsiniz.

using JwtTokenWithIdentity.Models;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;

namespace JwtTokenWithIdentity.Library
{
    public class AccessTokenGenerator
    {
        public APIDbContext _context { get; set; }
        public IConfiguration _config { get; set; }
        public ApplicationUser _applicationUser { get; set; }

        /// <summary>
        /// Class'ın oluşturulması.
        /// </summary>
        /// <param name="_context"></param>
        /// <param name="_config"></param>
        /// <param name="_applicationUser"></param>
        /// <returns></returns>
        public AccessTokenGenerator(APIDbContext context,
                                    IConfiguration config,
                                    ApplicationUser applicationUser)
        {
            _config = config;
            _context = context;
            _applicationUser = applicationUser;
        }

        /// <summary>
        /// Kullanıcı üzerinde tanımlı tokenı döner;Token yoksa oluşturur. Expire olmuşsa update eder.
        /// </summary>
        /// <returns></returns>
        public ApplicationUserTokens GetToken()
        {
            ApplicationUserTokens userTokens = null;
            TokenInfo tokenInfo = null;

            //Kullanıcıya ait önceden oluşturulmuş bir token var mı kontrol edilir.
            if (_context.ApplicationUserTokens.Count(x => x.UserId == _applicationUser.Id) > 0)
            {
                //İlgili token bilgileri bulunur.
                userTokens = _context.ApplicationUserTokens.FirstOrDefault(x => x.UserId == _applicationUser.Id);

                //Expire olmuş ise yeni token oluşturup günceller.
                if (userTokens.ExpireDate <= DateTime.Now)
                {
                    //Create new token
                    tokenInfo = GenerateToken();

                    userTokens.ExpireDate = tokenInfo.ExpireDate;
                    userTokens.Value = tokenInfo.Token;

                    _context.ApplicationUserTokens.Update(userTokens);
                }
            }
            else
            {
                //Create new token
                tokenInfo = GenerateToken();

                userTokens = new ApplicationUserTokens();

                userTokens.UserId = _applicationUser.Id;
                userTokens.LoginProvider = "SystemAPI";
                userTokens.Name = _applicationUser.FullName;
                userTokens.ExpireDate = tokenInfo.ExpireDate;
                userTokens.Value = tokenInfo.Token;

                _context.ApplicationUserTokens.Add(userTokens);
            }

            _context.SaveChangesAsync();

            return userTokens;
        }

        /// <summary>
        /// Kullanıcıya ait tokenı siler.
        /// </summary>
        /// <returns></returns>
        public async Task<bool> DeleteToken()
        {
            bool ret = true;

            try
            {
                //Kullanıcıya ait önceden oluşturulmuş bir token var mı kontrol edilir.
                if (_context.ApplicationUserTokens.Count(x => x.UserId == _applicationUser.Id) > 0)
                {
                    ApplicationUserTokens userTokens = userTokens = _context.ApplicationUserTokens.FirstOrDefault(x => x.UserId == _applicationUser.Id);

                    _context.ApplicationUserTokens.Remove(userTokens);
                }

                await _context.SaveChangesAsync();
            }
            catch (Exception)
            {
                ret = false;
            }

            return ret;
        }

        /// <summary>
        /// Yeni token oluşturur.
        /// </summary>
        /// <returns></returns>
        private TokenInfo GenerateToken()
        {
            DateTime expireDate = DateTime.Now.AddSeconds(50);
            var tokenHandler = new JwtSecurityTokenHandler();
            var key = Encoding.ASCII.GetBytes(_config["Application:Secret"]);

            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Audience = _config["Application:Audience"],
                Issuer = _config["Application:Issuer"],
                Subject = new ClaimsIdentity(new Claim[]
                {
                    //Claim tanımları yapılır. Burada en önemlisi Id ve emaildir.
                    //Id üzerinden, aktif kullanıcıyı buluyor olacağız.
                    new Claim(ClaimTypes.NameIdentifier, _applicationUser.Id),
                    new Claim(ClaimTypes.Name, _applicationUser.FullName),
                    new Claim(ClaimTypes.Email, _applicationUser.Email)
                }),

                //ExpireDate
                Expires = expireDate,

                //Şifreleme türünü belirtiyoruz: HmacSha256Signature
                SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
            };

            var token = tokenHandler.CreateToken(tokenDescriptor);
            var tokenString = tokenHandler.WriteToken(token);

            TokenInfo tokenInfo = new TokenInfo();
            
            tokenInfo.Token = tokenString;
            tokenInfo.ExpireDate = expireDate;

            return tokenInfo;
        }
    }
}

Modeller altına 3 yeni model ekledim.
RegisterViewModel, LoginViewModel ve ResponseViewModel

RegisterViewModel;

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;


namespace JwtTokenWithIdentity.Models
{
    public class RegisterViewModel
    {
        [Required]
        [DisplayName("Ad soyad")]
        [StringLength(60)]
        public string FullName { get; set; }

        [Required(ErrorMessage = "Email adresi zorunlu")]
        [EmailAddress]
        public string Email { get; set; }

        [Required(ErrorMessage = "Şifre zorunlu")]
        [DataType(DataType.Password)]
        public string Password { get; set; }

        [DataType(DataType.Password)]
        [Compare("Password", ErrorMessage = "Girmiş olduğunuz parola birbiri ile eşleşmiyor.")]
        public string ConfirmPassword { get; set; }
    }
}

LoginViewModel;

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;

namespace JwtTokenWithIdentity.Models
{
    public class LoginViewModel
    {
        [Required(ErrorMessage = "Email adresi zorunlu")]
        [EmailAddress]
        public string Email { get; set; }

        [Required(ErrorMessage = "Şifre zorunlu")]
        [DataType(DataType.Password)]
        public string Password { get; set; }
    }
}

ResponseViewModel;

namespace JwtTokenWithIdentity.Models
{
    public class ResponseViewModel
    {
        public bool IsSuccess { get; set; }
        public string Message { get; set; }
        public TokenInfo TokenInfo { get; set; }
    }
}

Şimdi Register ve Login işlemleri için kullanacağımız, controller tarafına geçelim.

Controller altına UserController.cs isimli bir controller oluşturdum. Kodlar aşağıdaki gibi, buradaki kodları tek tek açıklamayacağım.
Genel olarak yaptığımız işlem şu şekilde;
Kullanıcı register oluyor. Register olan kullanıcı Login oluyor. Login olan kullanıcı ise eğer sistemde kayıtlı bir token’ı varsa o tokenı bilgisi ile dönüp Login oluyor. Yok ise yeni bir token oluşturuyoruz ve o token ile login olup dönüyor.

using JwtTokenWithIdentity.Library;
using JwtTokenWithIdentity.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace JwtTokenWithIdentity.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class UserController : ControllerBase
    {
        private readonly APIDbContext _context;
        private readonly IConfiguration _config;
        private readonly SignInManager<ApplicationUser> _signInManager;
        private readonly UserManager<ApplicationUser> _userManager;
        private readonly RoleManager<IdentityRole> _roleManager;


        public UserController(APIDbContext context,
                              IConfiguration configuration,
                              SignInManager<ApplicationUser> signInManager,
                              UserManager<ApplicationUser> userManager,
                              RoleManager<IdentityRole> roleManager)
        {
            _context = context;
            _config = configuration;
            _signInManager = signInManager;
            _userManager = userManager;
            _roleManager = roleManager;
        }

        [HttpPost]
        [Route("Register")]
        public async Task<ActionResult> Register([FromBody] RegisterViewModel model)
        {
            ResponseViewModel responseViewModel = new ResponseViewModel();

            try
            {
                #region Validate
                if (!ModelState.IsValid)
                {
                    responseViewModel.IsSuccess = false;
                    responseViewModel.Message = "Bilgileriniz eksik, bazı alanlar gönderilmemiş. Lütfen tüm alanları doldurunuz.";

                    return BadRequest(responseViewModel);
                }

                ApplicationUser existsUser = await _userManager.FindByNameAsync(model.Email);

                if (existsUser != null)
                {
                    responseViewModel.IsSuccess = false;
                    responseViewModel.Message = "Kullanıcı zaten var.";

                    return BadRequest(responseViewModel);
                }
                #endregion

                //Kullanıcı bilgileri set edilir.
                ApplicationUser user = new ApplicationUser();

                user.FullName = model.FullName;
                user.Email = model.Email.Trim();
                user.UserName = model.Email.Trim();

                //Kullanıcı oluşturulur.
                IdentityResult result = await _userManager.CreateAsync(user, model.Password.Trim());

                //Kullanıcı oluşturuldu ise
                if (result.Succeeded)
                {
                    bool roleExists = await _roleManager.RoleExistsAsync(_config["Roles:User"]);

                    if (!roleExists)
                    {
                        IdentityRole role = new IdentityRole(_config["Roles:User"]);
                        role.NormalizedName = _config["Roles:User"];

                        _roleManager.CreateAsync(role).Wait();
                    }

                    //Kullanıcıya ilgili rol ataması yapılır.
                    _userManager.AddToRoleAsync(user, _config["Roles:User"]).Wait();

                    responseViewModel.IsSuccess = true;
                    responseViewModel.Message = "Kullanıcı başarılı şekilde oluşturuldu.";
                }
                else
                {
                    responseViewModel.IsSuccess = false;
                    responseViewModel.Message = string.Format("Kullanıcı oluşturulurken bir hata oluştu: {0}", result.Errors.FirstOrDefault().Description);
                }

                return Ok(responseViewModel);
            }
            catch (Exception ex)
            {
                responseViewModel.IsSuccess = false;
                responseViewModel.Message = ex.Message;

                return BadRequest(responseViewModel);
            }
        }

        [HttpPost]
        [Route("Login")]
        public async Task<ActionResult> Login([FromBody] LoginViewModel model)
        {
            ResponseViewModel responseViewModel = new ResponseViewModel();

            try
            {
                #region Validate

                if (ModelState.IsValid == false)
                {
                    responseViewModel.IsSuccess = false;
                    responseViewModel.Message = "Bilgileriniz eksik, bazı alanlar gönderilmemiş. Lütfen tüm alanları doldurunuz.";
                    return BadRequest(responseViewModel);
                }

                //Kulllanıcı bulunur.
                ApplicationUser user = await _userManager.FindByNameAsync(model.Email);

                //Kullanıcı var ise;
                if (user == null)
                {
                    return Unauthorized();
                }

                Microsoft.AspNetCore.Identity.SignInResult signInResult = await _signInManager.PasswordSignInAsync(user,
                                                                                                                   model.Password,
                                                                                                                   false,
                                                                                                                   false);
                //Kullanıcı adı ve şifre kontrolü
                if (signInResult.Succeeded == false)
                {
                    responseViewModel.IsSuccess = false;
                    responseViewModel.Message = "Kullanıcı adı veya şifre hatalı.";

                    return Unauthorized(responseViewModel);
                }

                #endregion

                ApplicationUser applicationUser = _context.Users.FirstOrDefault(x => x.Id == user.Id);

                AccessTokenGenerator accessTokenGenerator = new AccessTokenGenerator(_context, _config, applicationUser);
                ApplicationUserTokens userTokens = accessTokenGenerator.GetToken();

                responseViewModel.IsSuccess = true;
                responseViewModel.Message = "Kullanıcı giriş yaptı.";
                responseViewModel.TokenInfo = new TokenInfo
                {
                    Token = userTokens.Value,
                    ExpireDate = userTokens.ExpireDate
                };

                return Ok(responseViewModel);
            }
            catch (Exception ex)
            {
                responseViewModel.IsSuccess = false;
                responseViewModel.Message = ex.Message;

                return BadRequest(responseViewModel);
            }
        }
    }
}

Evet artık hazırız.
Şimdi projeyi çalıştırıp testlerimizi yapalım.

Öncelikle Register oluyoruz.

SQL’ime gidip baktığımda, kullanıcıyı oluşturuyor, user rolünü oluşturuyor ve kullancı rol eşleşmesini yaptığını görüyorum.

Şimdi Login olup token alalım, sonra bu token ile product verilerini çekmeye çalışalım.

Bize verilen token bilgisini jwt sitesinde doğrulayabiliriz. https://jwt.io/ sitesine gidip tokenı yapıştırdığımda önce şunları görüyorum.

Invalid Signature hatası alıyoruz. Sebebi sisteme özel tanımladığımız key’i girmemiş olmamız. Sağ tarafta PAYLOAD:DATA kısmında claimlerde tanımladığımız bilgilerimizi görebilirsiniz. NameId ve Email bilgilerimin sql’imdeki kayıt ile aynı olduğunu görebiliyorum.

Şimdi keyi doğru girip tekrar bakıyorum.


Gördüğünüz gibi jwt token bilgilerimizi doğruladı.

Şimdi bu token ile Products/GetAll methoduna tekrar istekte bulunalım.
Bearer ifadesinden sonra boşluk bırakmayı unutmayın

Gördüğünüz gibi 200 Ok cevabı geldi. Yani Authorize olmuş olduk. 50 Saniye sonra tekrar denediğimizde tokenımızın artık geçerli olmadığını görebilirsiniz.

Aktif kullanıcıyı yakayabilmek için aşağıdaki kodu kullanabilirsiniz. Bu kod yardımı ile artık claim içindeki değerleri çekip, istediğiniz tabloya find atıp kullanabilirsiniz.

context.httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);

Evet arkadaşlar işlemler bu kadar. Umarım faydalı olmuştur. Sanırım bugüne kadar yazdığım en uzun yazı olmuş oldu. Proje’inin tam halini GitHub‘ada koydum buradan erişebilirsiniz.

Sağlıklı, mutlu, huzurlu günler dilerim. Herkese iyi çalışmalar

Bir Cevap Yazın