I have implemented JWT tokens to know who the current user is in an API application that is being used by an MVC controller. I'm building something like a forum app. The user must be logged in to be able to post, so I'm basically trying to use the JWT token to store the current user's email. When the user click on "Create Post" the action should get the token and its value, the problem is that I don't know how to use the token to protect controllers or retrieve data from current user, I have already copied and pasted the token in jwt.io to check if the data is stored correctly in the token and the value (the user's email) is stored correctly.
The API controller with the "login" action:
public async Task<IActionResult> login([FromBody] Usuario model)
{
//check if user exists and the password is correct
//generates the token
var SecretKey = config.GetValue<string>("SecretKey");
var key = Encoding.ASCII.GetBytes(SecretKey);
var claims = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Mail)
});
claims.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Mail));
var tokenDesc = new SecurityTokenDescriptor
{
Subject = claims,
Expires = DateTime.UtcNow.AddMinutes(20),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var tokenHandler = new JwtSecurityTokenHandler();
var createdToken = tokenHandler.CreateToken(tokenDesc);
string bearer_token = tokenHandler.WriteToken(createdToken);
using(var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer" + bearer_token);
}
return Ok(bearer_token);
}
}
The MVC controller from where the API is used:
public async Task<IActionResult> login(Usuario model)
{
HttpClient hc = new HttpClient();
hc.BaseAddress = new Uri("https://localhost:44325/api/Usuarios/");
var login = await hc.PostAsJsonAsync<Usuario>("login", model);
//check the response
var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
identity.AddClaim(new Claim(ClaimTypes.Name, model.Email));
var principal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);
HttpContext.Session.SetString("JWToken", login.ToString());
hc.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", login.ToString());
return RedirectToAction("IndexForumList", "ForumControllerMVC");
}
}
This is the API method to "Create Posts" and where the token should be used, here the userId is null:
public async Task<IActionResult> createPost([FromForm]ForumModel model)
{
string userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
//retrieves the current user email, validates and save the content to database
}
And this is the MVC method to "Create Posts":
public async Task<IActionResult> createPost(ForumModel model)
{
HttpClient hc = new HttpClient();
hc.BaseAddress = new Uri("https://localhost:44325/api/Usuarios/");
//var userPost = hc.PostAsJsonAsync<ForumModel>("Usuarios/createPost", model);
var userPost = await hc.PostAsync("createPost", formContent);
if(userPost.IsSuccessStatusCode == true)
{
return RedirectToAction("IndexForumList", "ForoControllerMVC");
}
}
I have been suffering with this due to my lack of knowledge about JWT, any help is appreciated.
UPDATE
The startup.cs
public void ConfigureServices(IServiceCollection services)
{
var key = Encoding.ASCII.GetBytes(Configuration.GetValue<string>("SecretKey"));
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});
services.AddSession(
options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(10);
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
}
ClaimTypes.NameIdentifiertwice.UserManagerwill look for claimsIdentityOptions.ClaimsIdentity.UserIdClaimType/.UserNameClaimTypeto fetch the DB User. You can change the claim type name in startup. Are your "API application" and "MVC controller" the same program or separate?ClaimTypes.NameIdentifierwith the value of the email, or something like that? And yeah, the API is on the same project than the MVC.