Home > Blockchain >  How to add JwT token in request header ASP.Net MVC Core 6
How to add JwT token in request header ASP.Net MVC Core 6

Time:10-24

I have just started to use Asp.Net Core and I managed to create a mvc project. In This project I have created an API and it is secured with token based authorization.I have also used identity framework for user auhentication. Now I want to consume this API to perform CRUD operations with passing token but have no clear idea how to do that. After searching similar questions what I have tried is generate the token using user credentials (username, password) when user successfully logged in or registered and attach the generated token to header and as far as I know it will be passed through each subsequent request.

First I tried creating a method to call to generate the token after success login or registration. This includes in same controller which used for login and registration.

Token generate method

public string GenerateAuthToken(ApplicationUser applicationUser)
{
  var tokenHandler = new JwtSecurityTokenHandler();
  var key = Encoding.ASCII.GetBytes(_configuration.GetSection("JWT")["TokenSignInKey"]);
  var tokenDescriptor = new SecurityTokenDescriptor
  {
     Subject = new ClaimsIdentity(new Claim[] {
                 new Claim(type:JwtRegisteredClaimNames.Sub, applicationUser.Id),
                 new Claim(type:JwtRegisteredClaimNames.Email, applicationUser.Email),
                 new Claim(type:JwtRegisteredClaimNames.Iat, 
                        value:DateTime.Now.ToUniversalTime().ToString())
               }),
     Expires = DateTime.UtcNow.AddHours(1),
     SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), 
                          SecurityAlgorithms.HmacSha256Signature)
  };

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

  return stringToken;
}

I call this after success user login and register,

public async Task<IActionResult> Register(RegisterViewModel registerViewModel)
{
    if (ModelState.IsValid)
    {
       var user = new ApplicationUser { UserName = registerViewModel.Username, 
                      Email = registerViewModel.Email};

       var result = await _userManager.CreateAsync(user, registerViewModel.Password);

       if (result.Succeeded)
       {
           await _signInManager.SignInAsync(user, isPersistent: false);
           var token = GenerateAuthToken(user);

           var httpClient = new HttpClient();
           httpClient.DefaultRequestHeaders.Authorization = new 
                                             AuthenticationHeaderValue("bearer", token);
                return RedirectToAction("Index", "Home");
       }

       ModelState.AddModelError("", "User Registration Failed");
   }

   return View(registerViewModel);  

} When this executed, the token is successfully generated but does not attach the token. I do not know if I am doing any wrong here. But I found someone facing the same issue but has tried different way to achieve this. I think it is the correct way but not sure. Instead of generate the token on success login, have to generate it each api call. According to this solution I created another controller and action to generate the token.

public async Task<IActionResult> GetToken([FromBody] AuthViewModel authViewModel)
{
   var user = _context.Users.FirstOrDefault(u => u.Email == authViewModel.Email);
   if (user != null)
   {
      var signInResult = await _signInManager.CheckPasswordSignInAsync(user, 
                               authViewModel.Password, false);
      if (signInResult.Succeeded)
      {
          var tokenHandler = new JwtSecurityTokenHandler();
          var key = Encoding.ASCII.GetBytes(_configuration.GetSection("JWT") 
                    ["TokenSignInKey"]);
          var tokenDescriptor = new SecurityTokenDescriptor
          {
              Subject = new ClaimsIdentity(new Claim[] {
                        new Claim(type:JwtRegisteredClaimNames.Sub,authViewModel.Email),
                        new Claim(type:JwtRegisteredClaimNames.Email, 
                                  authViewModel.Email),
                        new Claim(type:JwtRegisteredClaimNames.Iat, 
                                  value:DateTime.Now.ToUniversalTime().ToString())
              }),
              Expires = DateTime.UtcNow.AddHours(1),
                        SigningCredentials = new SigningCredentials(new 
                                               SymmetricSecurityKey(key), 
                                               SecurityAlgorithms.HmacSha256Signature)
          };

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

          return Ok(new { Token = stringToken });
     }
     return BadRequest("Invalid User");
  }}

AuthViewModel

public class AuthViewModel
{
    [Required]
    public string Email { get; set; }
    [Required]
    public string Password { get; set; }
}

I added authViewModel to accept logged user credentials since I don't want add them manually, Then I have created another controller to perform the CRUD same as the above mentioned link Please note that I followed the solution mentioned below that page.

private async Task<string> CreateToken()
{
    var user = await _userManager.GetUserAsync(User);
    var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost:7015/Auth");
    request.Content = JsonContent.Create(new AuthViewModel{
                                 Email = user.Email, Password = user.PasswordHash
                                });

    var client = _clientFactory.CreateClient();
    HttpResponseMessage response = await client.SendAsync(request);
    var token = await response.Content.ReadAsStringAsync();
    HttpContext.Session.SetString("JwToken", token);
    return token;

}

request.Content I added to match my solution since token should be generated using user credentials. But I have no idea how to pass the logged in user's credentials with the request. This does not work. It is not possible to access the user password.

This is how I called the token generate action to perform CRUD. And I use JQuery Ajax to call the GetAllSales endpoint.

public async Task<IActionResult> GetAllSales()
{
    string token = null;
    var strToken = HttpContext.Session.GetString("JwToken");
    if (string.IsNullOrWhiteSpace(strToken))
    {
        token = await CreateToken();
    }
    else
    {
        token = strToken;
    }

    List<Sale> sales = new List<Sale>();
    var client = _clientFactory.CreateClient();
    var request = new HttpRequestMessage(HttpMethod.Get, 
                                         "http://localhost:7015/api/Sales");
    request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
    HttpResponseMessage response = await client.SendAsync(request, 
                                          HttpCompletionOption.ResponseHeadersRead);
    if (response.StatusCode == System.Net.HttpStatusCode.OK)
    {
        var apiString = await response.Content.ReadAsStringAsync();
        sales = JsonConvert.DeserializeObject<List<Sale>>(apiString);
    }
    Ok(sales);
}

This does not work. An exception throws 'System.InvalidOperationException: Unable to resolve service for type 'System.Net.Http.IHttpClientFactory' while attempting to activate '_7_ElevenRetail.Controllers.AccessApiController'. at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, Boolean isDefaultParameterRequired)'

Please suggest me and show me how to achieve this correctly. I am expecting all of your help. Thank you.

CodePudding user response:

System.InvalidOperationException: Unable to resolve service for type 'System.Net.Http.IHttpClientFactory' while attempting to activate '_7_ElevenRetail.Controllers.AccessApiController'

This issue means you inject IHttpClientFactory in AccessApiController without registering the service in Program.cs.

Register IHttpClientFactory by calling AddHttpClient in Program.cs:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddHttpClient();
  • Related