Using JwtBearer Authentication in an API-only ASP.NET Core Project


In my Pluralsight courses1 on ASP.NET Core, I show how to use JWT Tokens to secure your API. In building a new example for my upcoming Vue.js course, I decided to only use JWT (not cookies and JWT like many of my examples are).

But I kept getting redirects on failure to call an API made me realize that I wasn't sure how to make JWT the only provider. After some fiddling I figured it out. This blog post is mostly to remind me of how to do it.

UPDATED!

After help from @khellang - I found the real culprit. See new section at the bottom.


Prior Blogpost

If you haven't seem how to handle Cookies & JwtBearer tokens, see my other post:

https://wildermuth.com/2017/08/19/Two-AuthorizationSchemes-in-ASP-NET-Core-2

Using JwtBearer

I knew I had to add the JwtBearer when I setup the AddAuthentication call:

public void ConfigureServices(IServiceCollection services)
{
  services.AddIdentity<IdentityUser, IdentityRole>(cfg =>
  {
    cfg.User.RequireUniqueEmail = true;
  })
    .AddEntityFrameworkStores<StoreContext>();

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

      };
    });

  services.AddDbContext<StoreContext>();
  services.AddScoped<IStoreRepository, ProductRepository>();
  services.AddScoped<StoreDbInitializer>();

  services.AddMvc();
}

But just adding a single provider didn't work making it the default. Originally, I decided to use the same method as the dual-authentication by adding the

[Route("api/[controller]")]
[Authorize(JwtBearerDefaults.AuthenticationScheme)]
public class OrdersController : Controller
  

But my goal was not to have to specify it, I want it to be the default. The trick seemed to be that I needed to tell Authentication that the default AuthenticateScheme and the DefaultChallengeScheme needed to be using the JwtBearer:

  services.AddAuthentication(cfg =>
  {
    cfg.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    cfg.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
  })
    .AddJwtBearer(cfg =>
    {
      cfg.TokenValidationParameters = new TokenValidationParameters()
      {
        ValidateIssuer = true,
        ValidIssuer = _config["Security:Tokens:Issuer"],
        ValidateAudience = true,
        ValidAudience = _config["Security:Tokens:Audience"],
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Security:Tokens:Key"])),

      };
    });

Then I could just use Authorize attribute and it would work:

[Route("api/[controller]")]
[Authorize]
public class OrdersController : Controller

UPDATE

While this worked, it wasn't quite right. One approach I didn't mention was just setting the default authentication when configuring it:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
  ... 

This also works except the redirection from cookie authentication is still there. The problem was that I was using AddIdentity before. When pointed at the source code, it was clear that this code was adding a lot of cookie based identity as the defaults:

AddIdentity

So I could just move the AddIdentity call to after the AddAuthentication but I hate it when order matters. So I was told that what I should have done was AddIdentityCore instead:

  services.AddIdentityCore<identityuser>      services.AddIdentityCore<IdentityUser>(cfg =>
  {
    cfg.User.RequireUniqueEmail = true;
  })
    .AddEntityFrameworkStores<StoreContext>();

  services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(cfg =>
    {
      cfg.TokenValidationParameters = new TokenValidationParameters()
      {
        ValidateIssuer = true,
        ValidIssuer = _config["Security:Tokens:Issuer"],
        ValidateAudience = true,
        ValidAudience = _config["Security:Tokens:Audience"],
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Security:Tokens:Key"])),

      };
    });

I like that it works and it's more correct. Thanks Kristian!

1 http://shawnw.me/learnaspnetcore2 & http:// http://shawnw.me/corewebapi


Bootstrap 4 is Here!

After a long development cycle, Bootstrap has been completely re-written to improve performance and be more consistent. Learn Bootstrap 4 now with my Wilder Minds course:

Enroll Today



Application Name WilderBlog Environment Name Production
Application Ver 2.0.0.0 Runtime Framework .NETCoreApp,Version=v2.0
App Path D:\home\site\wwwroot\ Runtime Version .NET Core 4.6.26212.01
Operating System Microsoft Windows 10.0.14393 Runtime Arch X86