dfmetro
dfmetro

Reputation: 4582

How to make JWT token authorization optional in controller methods

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.

Upvotes: 12

Views: 5805

Answers (3)

Jeremy Borg
Jeremy Borg

Reputation: 150

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.

Upvotes: 1

Viganon
Viganon

Reputation: 1

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

Upvotes: 0

Kirk Larkin
Kirk Larkin

Reputation: 93133

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.

Upvotes: 19

Related Questions