1

I have an MVC application and one exposed API Endpoint. I authenticated my MVC application with the defaults from Identity Core, I use User.FindFirstValue(ClaimTypes.NameIdentifier) to find if a certain user is logged in, etc.

For my API Endpoint, I use JWT authentication below is the configuration code for JWT:

services.AddAuthentication(options =>
        {
            options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        })
        .AddJwtBearer(jwt =>
        {
            var key = Encoding.ASCII.GetBytes(Configuration["Jwt:Secret"]);

        jwt.SaveToken = true;
        jwt.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(key),
            ValidateIssuer = false,
            ValidateAudience = false,
            RequireExpirationTime = false,
            ValidateLifetime = true
        };
    });

Here is the controller for token request:

[HttpPost]
[Route("token")]
public async Task<IActionResult> Token([FromBody] UserLoginRequest user)
{
    if (ModelState.IsValid)
    {
        var existingUser = await _userManager.FindByEmailAsync(user.Email);
        if (existingUser == null)
        {
            return BadRequest();
        }

         var isCorrect = await _userManager.CheckPasswordAsync(existingUser, user.Password);
         if (isCorrect)
         {
             var jwtToken = _identityService.GenerateJwtToken(existingUser);

             return Ok(new RegistrationResponse()
                {
                    Result = true,
                    Token = jwtToken
                });
         }
         else
         {
             return BadRequest();
         }

    }

    return BadRequest();
}

On my MVC controllers, I use [Authorize]

On my API Endpoint i use [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]

My GenerateJWTToken method:

public string GenerateJwtToken(IdentityUser user)
    {
        var jwtTokenHandler = new JwtSecurityTokenHandler();

        var key = Encoding.ASCII.GetBytes(_jwtConfig.Secret);

        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = new ClaimsIdentity(new[]
            {
            new Claim("Id", user.Id),
            new Claim(JwtRegisteredClaimNames.Sub, user.Email),
            new Claim(JwtRegisteredClaimNames.Email, user.Email),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
        }),
            Expires = DateTime.UtcNow.AddHours(6),
            SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha512Signature)
        };

        var token = jwtTokenHandler.CreateToken(tokenDescriptor);

        var jwtToken = jwtTokenHandler.WriteToken(token);

        return jwtToken;
    }
}

But obviously, this solution fails to function because once I start my MVC Application and try to log in, I get redirected back to Index and I'm still unauthorized. And vice versa with the API, when I make a Postman call, I get the token, and when I try to call my Bookmarks Controller to query user's bookmarks I get zero, although there are bookmarks for that certain user.

Any ideas on how could I make this work would be welcomed.

11
  • In making request to the bookmarks, do you send the bearer token with the request? And secondly, your mvc authentication is failing because you've specified JWT authentication as the defualtauthentication scheme. Commented Jun 10, 2021 at 8:32
  • Yes, I do send the token with the request. My bookmarks entity has a UserId property that I use to retrieve all the bookmarks from that specific user. When this is in MVC i easily retrieve the userId with (User.FindFirstValue(ClaimTypes.NameIdentifier)), but on my API call i get zero bookmarks. @Jeffery Commented Jun 10, 2021 at 8:40
  • @Jeffery And also what can I do to make both session authentication and JWT authentication as a default Commented Jun 10, 2021 at 8:41
  • For all JWT authentication I've done, I'd written my own JWT Token Generator where I specify the claim types like the NameIdentifier and other claims I may need specifically. Commented Jun 10, 2021 at 8:59
  • 1
    I posted my code as an answer with comments to help. Please see if it solves your problem Commented Jun 10, 2021 at 13:00

2 Answers 2

1

Inside my JWT Token Generator, I get details of the user I would like to store as claims such as the username, which can be used to identify the user.

public static class JwtTokenExtensions
{
    /// <summary>
    /// Generates a JWT Bearer token containing the users email
    /// </summary>
    /// <param name="user"></param>
    /// <returns></returns>
    public static string GenerateJwtToken(this Identity user)
    {
        // Set our token claims
        Claim[] claims = {
            // Unique ID for this token
            new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString("N")),

            new(JwtRegisteredClaimNames.Email, user.Email),
            // The username using the Identity name so it fills out the HttpContext.User.Identity.Name value
            new(ClaimsIdentity.DefaultNameClaimType, user.UserName),
            // Add user Id so that UserManager.GetUserAsync can find the user based on Id
            new Claim(ClaimTypes.NameIdentifier, user.Id)
        };

        // Create the credentials used to generate the token
        SigningCredentials credentials = 
        new SigningCredentials(SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:SecretKey"])),
            SecurityAlgorithms.HmacSha256);

        // Generate the Jwt Token that lasts for an hour before expiring
        JwtSecurityToken token =
            new JwtSecurityToken
            (Configuration["Jwt:Issuer"],
            Configuration["Jwt:Audience"], 
            claims:claims,
            signingCredentials:credentials,
            expires: DateTime.Now.AddHours(1));

        // Return the generated token.
        return new JwtSecurityTokenHandler().WriteToken(token);
    }
}

Inside the api controllers with JWT authorization, I can get the user via the HttpContext var user = await _userManager.GetUserAsync(HttpContext.User);

Sign up to request clarification or add additional context in comments.

1 Comment

This solution is the correct answer to my question. Thank you :) @Jeffery
1

In the controller for your token request, try adding [AllowAnonymous], like this:

[AllowAnonymous]
[HttpPost]
[Route("token")]
public async Task<IActionResult> Token([FromBody] UserLoginRequest user)
{
    // snip...
}

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.