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


Ready to Learn Vue with ASP.NET Core?

Shawn's 4-hour course will get you up to speed in no time. Vue.js is a great middle-ground between React and Angular for people who don't like the complexity of Angular, and the overly componentized React. Learn today at Wilder Minds Training!

Enroll Today


Shawn
Shawn Wildermuth
Author, Teacher, and Coach




My Courses

Wilder Minds Training
Vue.js by Example (Now Available)
Bootstrap 4 by Example
Intro to Font Awesome 5 (Free Course)
Pluralsight
Less: Getting Started (Coupon Available)
Building a Web App with ASP.NET Core, MVC6, EF Core, Bootstrap and Angular (updated for 2.1)
Using Visual Studio Code for ASP.NET Core Projects
Implementing ASP.NET Web API
Web API Design

Application Name WilderBlog Environment Name Production
Application Ver v4.0.30319 Runtime Framework x86
App Path D:\home\site\wwwroot\ Runtime Version .NET Core 4.6.27019.06
Operating System Microsoft Windows 10.0.14393 Runtime Arch X86