6
votes

I use JWT tokens in my ASP.NET Core 2 web application

If the JWT token fails I still want the controller method to be hit but the ASP.NET Identity User object will just be null. Currently the controller method won't get entered if authentication fails so I can't apply logic inside the method to deal with non authenticated users which I want to deal with as guests instead of blocking them.

My code:

Startup.cs

    public void ConfigureServices(IServiceCollection services)
    {

        services.AddAuthentication()
            .AddJwtBearer(cfg =>
            {
                cfg.TokenValidationParameters = new TokenValidationParameters()
                {
                    ValidIssuer = _config["Tokens:Issuer"],
                    ValidAudience = _config["Tokens:Audience"],
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Tokens:Key"]))
                };

            });

When a user logs in

   private IActionResult CreateToken(ApplicationUser user, IList<string> roles)
    {
        var claims = new List<Claim>
        {
          new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString()),
          new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
          new Claim(JwtRegisteredClaimNames.UniqueName, user.UserName)
        };



        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Tokens:Key"]));

        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

        var token = new JwtSecurityToken(
          _config["Tokens:Issuer"],
          _config["Tokens:Audience"],
          claims.ToArray(),
         expires: DateTime.Now.AddMinutes(60),
          signingCredentials: creds);

        var results = new
        {
            token = new JwtSecurityTokenHandler().WriteToken(token),
            expiration = token.ValidTo,
        };

        return Created("", results);

I make sure after authenticated the various API calls pass the JWT token.

In my Controller I make use of an Authorize attribute

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
 public class MyController : Controller
{

This allows me to capture the logged in user with the following code for the various API calls

 var loggedInUser = User.Identity.Name;

If I remove the authorize attribute then User.Identity.Name will always be null even if the user is authenticated.

How do I use the authorize attribute but still allow the controller method to be entered if the token is not valid? I want to use the same method for both Authorized and non Authorized users to be entered. I will then use the User.Identity.Name to determine if they are not authenticated and have guest user logic inside the method for them.

3
I think you can write your own Authorize attribute e.g. AuthorizeWithGuests and put the logic inside of that attribute where you will do your checks and see if the user is not authenticated than treat that users as a guest. - Edward Chopuryan
I don't know the internals of the current Authorize attribute and I assume I have to rewrite it since I can't inherit from it since it blocks access - dfmetro
Yep exactly you have to write your own code there and just use it like this [AuthorizeWithGuests] - Edward Chopuryan

3 Answers

12
votes

It's actually easier than you might expect. You can just use both Authorize and AllowAnonymous, like so:

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[AllowAnonymous]
public class MyController : Controller

If authorization was successful, you'll have an authenticated User (User.Identity.IsAuthenticated = true), otherwise, you will have an anonymous User.

1
votes

I'm not sure when this changed but my application is on ASP.NET Core 3.1 and without my actions/endpoints specifying that I require authorization, the authentication middleware still provides the information I require inside my ClaimsPrincipal when a valid token is provided and a blank principal when no token is specified.

0
votes

By updating the ASP.NET Core a little bit, besides configuring your token guidelines, you can enable your controllers to require Bearer Token authentication by adding:

Startup.cs

  public void ConfigureServices(IServiceCollection services)
  {
      services.AddJwtSecurity(
                signingConfigurations, tokenConfigurations);
  }

Remembering that if you want to block the entire controller at once, just add it at the top of the Authorize class.

namespace <name_controller>
{    
    [Produces("application/json")]
    [Route("api/v1/[controller]")]
    [ApiController]
    [RequireHttps]
    [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
    public class clientesController : Controller
    {
    }
}