Sunday, November 24, 2024

Yet another ASP.NET Core JWT tutorial?

Why would you need yet another ASP.NET Core JWT tutorial?

Well, becuse most of them are unnecessarily complicated. Some involve unnecessary APIs like the Identity API. Some try to introduce an OAuth2/OpenID Connect stack. There are ocassional cases where false assumptions are used to justify the need of JWT tokens.

An example of the latter is an argument that "cookies are bad since they require data stored in server's session container", given here. Of course, cookies do not require that anything is stored in the container session, a cookie can just contain a username and this is how authentication cookies are often used. Anyone using .NET cookie authentication for years would be surprised to hear that instead of just a username, an ASP.NET authentication cookie would contain a session ID and the server would only lookup user data at the server's session container.

It's not that unnecessarily elaborated demo or a demo that fails at some subtle reasons is bad. It's just harder to follow.

Why would you prefer JWT from cookies then?

There's only one valid argument: JWT tokens work crossdomain, cookies don't. In a setup where an authentication token is issued at domain A and is required at domain B, cookies don't work. JWT tokens on the other hand, are usually put in custom headers in web browser calls and thus can be easily sent anywhere.

What would I personally expect from a JWT tutorial?

Well, I'd expect following:

  • a web app that authenticates users somehow, could even be an endpoint that accepts a username/password pair but an easy approach would be a cookie-based authentication with a usual login page. An advantage of the latter is that I expect a good demo to show how to have both in a single app: a cookie-based authentication on some endpoints and JWT based authentication on other endpoints
  • I want to see an endpoint that is set up to issue JWT tokens then. The endpoint could assume the user is authenticated (e.g. with an already issued cookie) and would just create JWT containing the username
  • I want to see a data endpoint that is authenticated only with a JWT token, a cookie should not be accepted. The endpoint could be exposed on the very same app but I'd easily think of any other app (a different domain) exposing it
  • I don't want any extra frameworks, APIs, anything, just bare minimum

And what? And there it is, https://github.com/wzychla/AspJwtBearerDemo. A complete demo. Here's an overview:

  • the demo contains both MVC and WebAPI controllers
  • there are two MVC controllers, the Home controller is only accessible by users authenticated with a cookie, the Account controller is used as a default redirect for unauthenticated calls and uses SignInAsync to issue the cookie
  • as long as the cookie is issued, the Home::Index view is rendered
  • there are two active endpoints
  • first, the Token::Acquire is used to get the JWT token. Note how easy it is: the used should be authenticated somehow (= cookie in my demo) and the code just creates a plain JWT with just the username
  • then, the Data::Get WebAPI endpoint is secured with the JWT token. Note that by configuring the JWT stack to recognize the JWT name claim as username I can access this.User.Identity.Name in a WebAPI code and still have the username. The this.User.Identity.Name is then used in the very same way in both MVC cookie secured controllers and WebAPI JWT secured controllers. The key point of this JWT config is
    cfg.TokenValidationParameters = new TokenValidationParameters()
    {
       ValidateAudience = false,
       ValidateIssuer   = false,
       IssuerSigningKey = signingKey,
       NameClaimType    = "name"     // map JWT's name claim to NET's IPrincipal::IIdentity::Name
    }; 
    

What is actually really nice in ASP.NET Core is the idea of authentication scheme - we define multiple authentication schemes (in this demo, there are two: cookie based and JWT based) and then each time Authorize is used on an action (both MVC and WebAPI) we specify which authentication scheme should is accepted. Thus, having some endpoints secured with a cookie and other secured with JWT tokens is easy and clean.

That's it. Clone the Github demo, follow the flow, take a look how both schemes are configured in AddCookies and AddJwtBearer. Enjoy.

No comments: