.NET Core MVC and Web API two authentication schemes

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.

Upvotes: 1

Views: 1004

Answers (2)

gcamp806
gcamp806

Reputation: 216

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...
}

Upvotes: 1

Jeffery
Jeffery

Reputation: 388

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

Upvotes: 1

Related Questions