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