JWT Авторизация ASP .NET Core MVC

Никогда до этого не работал с ASP .NET Core. Пока просто балуюсь, возникло желание сделать авторизацию по JWT на MVC. По итогу посмотрел несколько гайдов, почитал несколько статеек, может я чего-то и не понял, но почему-то у меня не работает авторизация, хотя токен я получаю, но сразу же делаю запрос к контроллеру с атрибутом Authorize и получаю ответ Bearer error="invalid_token"

POST https://localhost:7081/Account/Login
Content-Type: application/json

{
    "UserName": "Admin",
    "Password": "aA12345!"
}

###
GET https://localhost:7081/Users/Index
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiQWRtaW4iLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJBZG1pbiIsIm5iZiI6MTcyMDk1MDc1NSwiZXhwIjoxNzIwOTU0MzU1LCJpc3MiOiJNeVN0ZWFtREJEYXRhYmFzZSIsImF1ZCI6Ik15U3RlYW1EQkRhdGFiYXNlIn0.lgXz-1z7CJwJiJsqjn_q7WMQy6-rZ_EvbYlEDh9JrGk

Код контроллера

using Microsoft.AspNetCore.Authentication.OAuth;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using MySteamDBMetacritic.Db;
using MySteamDBMetacritic.Models;
using MySteamDBMetacritic.ViewModels;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Runtime.Intrinsics.Arm;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;

namespace MySteamDBMetacritic.Controllers
{
    public class AccountController : Controller
    {
        private readonly ILogger<HomeController> _logger;
        private readonly ApplicationDbContext _context;
        private readonly UserManager<User> _userManager;
        private readonly SignInManager<User> _signInManager;
        private readonly IConfiguration _configuration;

        public AccountController(ILogger<HomeController> logger, ApplicationDbContext context, UserManager<User> userManager, SignInManager<User> signInManager, IConfiguration configuration)
        {
            _context = context;
            _logger = logger;
            _userManager = userManager;
            _signInManager = signInManager;
            _configuration = configuration;
        }
        [HttpGet]
        public IActionResult Register()
        {
            return View();
        }
        [HttpPost]
        public async Task<IActionResult> Register([FromBody]RegisterViewModel model)
        {
            if (ModelState.IsValid)
            {
                User user = new User { Email = model.Email, UserName = model.UserName };
                if (_userManager.Users.FirstOrDefault(x=>x.Email==user.Email) != default(User))
                {
                    return View(model);
                }
                var result = await _userManager.CreateAsync(user, model.Password);
                if (result.Succeeded)
                {
                    _context.SaveChanges();
                    await _signInManager.SignInAsync(user, false);
                    return Json(new { token = Token(model.UserName, model.Password), returnUrl = Url.Action("Index", "Game") });
                }
                else
                {
                    foreach (var error in result.Errors)
                    {
                        ModelState.AddModelError(string.Empty, error.Description);
                    }
                }
            }
            return View(model);
        }
        [HttpGet]
        public IActionResult Login(string returnUrl = null)
        {
            return View(new LoginViewModel { ReturnUrl = returnUrl });
        }

        [HttpPost]
        public async Task<IActionResult> Login([FromBody]LoginViewModel model)
        {
            if (ModelState.IsValid)
            {
                var result = await _signInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, false);
                if (result.Succeeded)
                {
                    // проверяем, принадлежит ли URL приложению
                    if (!string.IsNullOrEmpty(model.ReturnUrl) && Url.IsLocalUrl(model.ReturnUrl))
                    {
                        return Json(new { token = ((JsonResult)Token(model.UserName, model.Password)).Value, returnUrl = model.ReturnUrl});
                    }
                    else
                    {
                        return Json(new { token = ((JsonResult)Token(model.UserName, model.Password)).Value, returnUrl = Url.Action("Index", "Game") });
                    }
                }
                else
                {
                    ModelState.AddModelError("", "Incorrect username or password");
                }
            }
            return View(model);
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Logout()
        {
            // удаляем аутентификационные куки
            await _signInManager.SignOutAsync();
            return RedirectToAction("Index", "Game");
        }
        public async Task<IActionResult> ChangePassword(int id)
        {
            User user = await _userManager.FindByIdAsync(id.ToString());
            if (user == null)
            {
                return NotFound();
            }
            ChangePasswordViewModel model = new ChangePasswordViewModel { Id = user.Id, Email = user.Email };
            return View(model);
        }

        [HttpPost]
        public async Task<IActionResult> ChangePassword(ChangePasswordViewModel model)
        {
            if (ModelState.IsValid)
            {
                User user = await _userManager.FindByIdAsync(model.Id.ToString());
                if (user != null)
                {
                    var _passwordValidator = HttpContext.RequestServices.GetService(typeof(IPasswordValidator<User>)) as IPasswordValidator<User>;
                    var _passwordHasher = HttpContext.RequestServices.GetService(typeof(IPasswordHasher<User>)) as IPasswordHasher<User>;

                    IdentityResult result = await _passwordValidator.ValidateAsync(_userManager, user, model.NewPassword);
                    if (result.Succeeded)
                    {
                        user.PasswordHash = _passwordHasher.HashPassword(user, model.NewPassword);
                        await _userManager.UpdateAsync(user);
                        return RedirectToAction("Index");
                    }
                    else
                    {
                        foreach (var error in result.Errors)
                        {
                            ModelState.AddModelError(string.Empty, error.Description);
                        }
                    }
                }
                else
                {
                    ModelState.AddModelError(string.Empty, "User is missing");
                }
            }
            return View(model);
        }
        public IActionResult Token(string username, string password)
        {
            var identity = GetIdentity(username, password).GetAwaiter().GetResult();
            if (identity == null)
            {
                return BadRequest(new { errorText = "Invalid username or password." });
            }

            var now = DateTime.Now;
            // создаем JWT-токен
            var jwt = new JwtSecurityToken(
                    issuer: _configuration["Jwt:Issuer"],
                    audience: _configuration["Jwt:Audience"],
            notBefore: now,
            claims: identity.Claims,
                    expires: now.Add(TimeSpan.FromMinutes(double.Parse(_configuration["Jwt:ExpiresMinutes"]))),
                    signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"])), SecurityAlgorithms.HmacSha256));
            var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);

            var response = new
            {
                access_token = encodedJwt,
                username = identity.Name
            };

            return Json(response);
        }

        private async Task<ClaimsIdentity> GetIdentity(string username, string password)
        {
            User user = await _userManager.FindByNameAsync(username);
            if (user != null)
            {
                var claims = new List<Claim>
                {
                    new Claim(ClaimsIdentity.DefaultNameClaimType, user.UserName),
                    new Claim(ClaimsIdentity.DefaultRoleClaimType, string.Join(',',_userManager.GetRolesAsync(user).Result))
                };
                ClaimsIdentity claimsIdentity =
                new ClaimsIdentity(claims, "Token", ClaimsIdentity.DefaultNameClaimType,
                    ClaimsIdentity.DefaultRoleClaimType);
                return claimsIdentity;
            }

            // если пользователя не найдено
            return null;
        }
    }
}

Я до этого с EF не работал, он создал свои таблицы, как я понимаю, в том числе и для авторизации тут тоже используются какие-то, может быть дело в том, что он туда ничего не сохраняет, но почему-то во всех гайдах, которые я смотрел у всех все как-то работает.

Думаю не стоит сюда постить прям весь код, но если вдруг понадобится что-то посмотреть, то вот https://github.com/Drobovik04/MySteamDBMetacritic

Так же еще один вопрос - есть ли какой-то простой механизм по отправке токена в запросах, а то почему-то много где цепляют js на view и подвязываются к нажатию кнопок на форме, затем описывают запрос через fetch, просто меня сильно смущает такая реализация, наверняка есть способ удобнее и правильнее


Ответы (0 шт):