question

HeinrichLudike-0703 avatar image
0 Votes"
HeinrichLudike-0703 asked HeinrichLudike-0703 commented

DotNet Core 3.1 MVC JWT authentication for Identity

100194-image.png
I have an application that I am working on, we are using JWT for our WebAPI and then we have a very simple frontend. The problem I have is the moment I enable my JWTBearer in startup.cs and run my application it refuses to log in. It does not return a JWT object and I can do nothing further.

This is what my code looks like in startup.cs
100155-image.png

I think I might be missing something in startup.cs but I am unable to find it at the moment.

dotnet-aspnet-core-mvcmicrosoft-identity-manager
image.png (30.5 KiB)
image.png (41.2 KiB)
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

HeinrichLudike-0703 avatar image
0 Votes"
HeinrichLudike-0703 answered HeinrichLudike-0703 commented

My function to create the token

 private async Task<string> GenerateJSONWebToken(AppUser user)
 {
     var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
     var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
     var userRoles = await _userManager.GetRolesAsync(user);

     var authClaims = new List<Claim>
     {
          new Claim(ClaimTypes.Name, user.UserName),
          new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
     };

     foreach (var userRole in userRoles)
     {
         authClaims.Add(new Claim(ClaimTypes.Role, userRole));
     }

     var token = new JwtSecurityToken(_config["Jwt:Issuer"],
       _config["Jwt:Issuer"],
       authClaims,
       expires: DateTime.Now.AddMinutes(120),
       signingCredentials: credentials);

     return new JwtSecurityTokenHandler().WriteToken(token);
 }
· 2
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

I think you misunderstand security basics. Your MVC application will use an authentication cookie to access secured MVC actions not a JWT. You should have code that creates an authentication cookie after a successful authentication. Since authentication involves the return of a JWT, you need to write code to persist the JWT too. A cookie is a good container in browser based applications but any MVC state management feature will work. Pick a state management feature the fits your design.

Web API client submit a bearer token to gain access to secured Web API resources. At this point, it is not clear what types of clients your security design supports. Is the client a JavaScript application running in the browser? Is the client application the MVC application making requests from C#?

1 Vote 1 ·

Thank you! That clarifies it.

The idea is to have one application in C# that interacts with others written in Java & Python. That was why the JWT token was needed so they can authenticate. I have not used JWT in any application before but this does make it much clearer.

0 Votes 0 ·
AgaveJoe avatar image
0 Votes"
AgaveJoe answered HeinrichLudike-0703 commented

You did not share code that creates the JWT or persists the token. Below is an example service to create the JWT in a Web API application. Can you explain the MVC security design? Are you using an authentication cookie to persist claims/roles or are you using a JavaScript application to persist the JWT? Can you explain your Web API security intent?

 namespace JwtService.Services
 {
     public interface IApplicationUser
     {
         string Authenticate(string username, string password);
         LoginResponse AuthenticateMvc(string username, string password);
     }
    
     public class ApplicationUser : IApplicationUser
     {
         private readonly IConfiguration Configuration;
         public ApplicationUser(IConfiguration configuration)
         {
             Configuration = configuration;
         }
    
         private List<User> _users = new List<User>
         {
             new User { Id = 1, FirstName = "Hello", LastName = "World", Username = "username", Password = "password" }
         };
    
         public string Authenticate(string username, string password)
         {
             var user = _users.SingleOrDefault(x => x.Username == username && x.Password == password);
    
             // return null if user not found
             if (user == null)
                 return null;
    
             // authentication successful so generate jwt token
             var tokenHandler = new JwtSecurityTokenHandler();
             var key = Encoding.ASCII.GetBytes(Configuration["JwtConfig:secret"]);
             var tokenDescriptor = new SecurityTokenDescriptor
             {
                 Subject = new ClaimsIdentity(new Claim[]
                 {
                     new Claim(ClaimTypes.Name, user.Id.ToString()),
                 }),
                 IssuedAt = DateTime.UtcNow,
                 Expires = DateTime.UtcNow.AddDays(7),
                 SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature),
                 Issuer = "Issuer",
                 Audience = "Audience"
             };
             var token = tokenHandler.CreateToken(tokenDescriptor);
             return tokenHandler.WriteToken(token);
         }
    
         public LoginResponse AuthenticateMvc(string username, string password)
         {
             var user = _users.SingleOrDefault(x => x.Username == username && x.Password == password);
    
             // return null if user not found
             if (user == null)
                 return null;
    
             // authentication successful so generate jwt token
             var tokenHandler = new JwtSecurityTokenHandler();
             var key = Encoding.ASCII.GetBytes(Configuration["JwtConfig:secret"]);
             var tokenDescriptor = new SecurityTokenDescriptor
             {
                 Subject = new ClaimsIdentity(new Claim[]
                 {
                     new Claim(ClaimTypes.Name, user.Id.ToString()),
                 }),
                 IssuedAt = DateTime.UtcNow,
                 Expires = DateTime.UtcNow.AddDays(7),
                 SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature),
                 Issuer = "Issuer",
                 Audience = "Audience"
             };
             var token = tokenHandler.CreateToken(tokenDescriptor);
    
             //Mock response
             LoginResponse response = new LoginResponse()
             {
                 token = tokenHandler.WriteToken(token),
                 role = "User",
                 claims = new List<ClaimDto>()
                 {
                     new ClaimDto() {type = ClaimTypes.Role, value = "UserRole" },
                     new ClaimDto() {type= ClaimTypes.Email, value = "email@email.com" }
                 }
             };
    
             return response;
    
         }
     }
 }
· 1
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

It looks like it is /Areas/Identity/Pages/Account/Manage/Login.cshtml.cs that needs the change.

 public async Task<IActionResult> OnPostAsync(string returnUrl = null)
         {
             returnUrl = returnUrl ?? Url.Content("~/");
    
             if (ModelState.IsValid)
             {
                 var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false);
                 if (result.Succeeded)
                 {
                     //jwt here
                     _logger.LogInformation("User logged in.");
    
                     return LocalRedirect(returnUrl);


Should this not return the token instead? The problem is when I enable this my WebAPI authorize requests work perfectly. Logging in is where the problem still sits.

0 Votes 0 ·