简体   繁体   中英

How to authorize users by role in MVC Controllers using a JWT token obtained from ajax call

I'm working on a single ASP.NET Core MVC project where I'm placing a basic MVC website and also a WebApi controller to handle every call from the views.

I'm not really using the whole MVC workflow, only the routing and authorization parts, the views are written in plain javascript and html, so there are no elements from ASP.NET MVC like tag helpers or models.

In the very first index page I have a login form, it calls (using jquery) an action in a WebApi controller, there I'm using ASP.NET Identity Core to authorize the user, if the user and password are valid I return a JWT token.

[Produces("application/json")]
[Route("api/[controller]")]
[AllowAnonymous]
[ApiController]
public class AccountController : ControllerBase
{
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly SignInManager<ApplicationUser> _signInManager;
    private readonly ILogger _logger;
    private readonly IConfiguration _configuration;

    public AccountController(
        UserManager<ApplicationUser> userManager,
        SignInManager<ApplicationUser> signInManager,
        ILogger<AccountController> logger,
        IConfiguration configuration)
    {
        _userManager = userManager;
        _signInManager = signInManager;
        _logger = logger;
        _configuration = configuration;
    }

    [Route("test")]
    [HttpGet]
    public async Task<IActionResult> Test()
    {
        return Ok(await Task.FromResult("Endpoint working"));
    }

    [Route("login")]
    [HttpPost]
    public async Task<IActionResult> Login([FromBody] LoginDto model)
    {
        var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, false, false);

        if (result.Succeeded)
        {
            var appUser = _userManager.Users.SingleOrDefault(r => r.Email == model.Email);

            return Ok(await GenerateJwtToken(model.Email, appUser));
        }

        return BadRequest(string.Empty);
    }

    [Route("logout")]
    [HttpGet]
    public async Task Logout()
    {
        await _signInManager.SignOutAsync();
    }

    private async Task<object> GenerateJwtToken(string email, IdentityUser user)
    {
        var claims = new List<Claim>
        {
            new Claim(JwtRegisteredClaimNames.Sub, email),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
            new Claim(ClaimTypes.NameIdentifier, user.Id)
        };

        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JwtKey"]));
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
        var expires = DateTime.Now.AddDays(Convert.ToDouble(_configuration["JwtExpireDays"]));

        var token = new JwtSecurityToken(
            _configuration["JwtIssuer"],
            _configuration["JwtIssuer"],
            claims,
            expires: expires,
            signingCredentials: creds
        );

        return await Task.FromResult(new JwtSecurityTokenHandler().WriteToken(token));
    }
}

This part works fine, in the database I have a couple of users with its own roles. The problem is that after login I want to redirect the user to a MVC Controller that have actions secured by role, the controller looks like this:

public class SecuredController : Controller
{
    [Authorize]
    public IActionResult Index()
    {
        return View();
    }

    [Authorize(Roles = "Role1, Admin")]
    public IActionResult Register()
    {
        return View();
    }

    [Authorize(Roles = "Role2, Admin")]
    public IActionResult Fraud()
    {
        return View();
    }
}

After login I'm doing something like

window.location = '@Url.Action("Index", "Secured")'

But I'm always getting a 401 response.

Is it possible to add an Authorization header with the token to the redirection? Will the controller understand the user roles from the token?

I'll appreciate your guidance.

The good news is that you actually even have a few options here. I am writing this from my cellphone, so it will be very concise:

  1. You could use the ServiceFilterAttribute to specify a CustomJwtAuthorizationFilter that performs the parsing of your JWT, extracting the claims and materializing the IPrincipal.Identity to be bound to the User protenty in your secured controller, as follows:

     [ServiceFilter(typeof(CustomJwtAuthorizationFilter))] public class SecuredController : Controller { ... } public class CustomJwtAuthorizationFilter : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext actionContext) { // get claims out of JWT in the request's Authorization header var claims = ... var identity = new ClaimsIdentity(claims); actionContext.HttpContext.User = new ClaimsPrincipal(identity); ... } }

    The trick here is to make sure that the claims that represent roles are clearly distinguished by setting their ClaimType property to be the configured RoleClaimType , which in most cases should be by default "role" !!! 😄. But this is something that should almost transparently be done, as roles claims should come as such specified in the JWT.

  2. Use some existing middleware and configure DI at your Startup.ConfigureServices(IServiceCollection) .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM