.NET 8 has been released and many new exciting features are available to developers. In particular, this release brings new options for authentication and authorization for ASP.NET Core Identity. While these features have been long awaited, it seems that not everyone is happy with Microsoft's implementation choices, and there is some confusion among developers about the proper approach to use in their ASP.NET Core Identity-based applications. Let's try to shed some light on this not-so-simple topic.
ASP.NET Core Identity: Whatβs New
One of the most criticized features of ASP.NET Core Identity until .NET 7 has been its lack of modern authentication support for Single Page Applications. Whatever modern means, ASP.NET Core Identity has provided support for cookie-based authentication since version 3.1. It has also provided a set of web pages that allow users to register, authenticate, recover passwords, etc.
However, the current trend in authentication for SPAs seems to be token-based rather than cookie-based, and ASP.NET Core Identity didn't support it. The only option provided with the built-in SPA templates was based on Identity Server, a third-party OpenID Connect server. This approach has been deemed excessive for small projects by several developers, and in any case introduces a third-party dependency that not everyone may want.
To address this issue, .NET 8 introduces support for token-based authentication and a set of Identity API endpoints that let developers do through the API what they could already do through the old-style web pages.
To learn more about the new authentication and authorization features introduced by .NET 8, read this article.
Awesome! So the problem is fixed and we all live happily ever after.π
In fact, things are not quite like that. Some developers are not entirely happy. There are those who expected a different implementation, and those who are now confused about the correct use of the new options. Let's try to bring some order to all this confusion.
Disclaimer: This article is not a step-by-step tutorial on how to add ASP.NET Core Identity to your application. Its purpose is to discuss the authentication modes that it supports as of .NET 8.0.
Cookie or Token-based Authentication?
Now ASP.NET Core Identity provides you with two authentication options: cookie-based and token-based. But what is the actual difference between the two approaches?
Cookie-based authentication
Cookie-based authentication is the typical approach used by the traditional server-side rendered web page model. This is the model used by ASP.NET Core MVC and Razor Pages, to put it bluntly. In this type of authentication, a server-generated cookie proves successful user authentication and represents the user session.
To learn more about the relationship between cookies and sessions, read this article.
Roughly, the cookie-based authentication flow is as follows:
- The user authenticates through a web page.
- The server generates a cookie and sends it to the browser with the response page.
- The browser sends the cookie with each new page request.
- If the server finds the cookie in the incoming request, it can use it to get the relevant information to make its authorization decisions.
The fun fact is that the cookie's back and forth is automatically managed by the browser. Provided that the server applies all the security best practices to protect its cookies, your application doesn't need to care about storing them and sending them back with each request.
Token-based authentication
Token-based authentication doesn't rely on cookies to prove user authentication. It relies on a token, which is a unique string of characters that also represents the user's session.
To learn more about how tokens help build sessions, check out this article.
Awesome! How does the token-based flow work? Here is a rough description:
- The user authenticates via a client-side page that calls the login endpoint.
- The server generates a token and sends it to the app in the login endpoint's response.
- The app sends the token with each new HTTP request.
- If the server finds the token in the incoming request, it can use it to get the relevant information to make its authorization decisions.
As you can see, this flow is basically the same as the cookie-based flow. The only difference is that this one mentions the token instead of the cookie. So what is the real difference between the two approaches?
What does authentication mean in this context?
If you are an experienced developer, you should already know the difference between authentication and authorization. Very roughly, authentication verifies the identity of the user while authorization verifies what the user can do.
Cookie-based authentication and token-based authentication are somewhat ambiguous terms. In fact, in this context, the cookie and the token indicate that the user has been successfully authenticated. However, they are used to make authorization decisions. When the server receives a request from a client, it checks if a cookie or a token is included in it. If so, the cookie or the token allows the server to somehow retrieve information to determine whether or not the request can be fulfilled. This information may also include the user profile data, of course. The specific way used to retrieve this information does not really matter.
This means that there is no real user authentication when sending a request to the server. User authentication has already taken place at the beginning of the session.
Cookies and tokens: a quick comparison
As you learned in the previous sections, cookie-based and token-based authentication are very similar. The main visible difference is the artifact they rely on to prove authentication and support session creation. However, there are significant differences. I will not go into a detailed comparison between cookies and tokens, but I can't help but mention at least the following:
- Cookies are tied to a domain, require little storage, and are automatically managed by the browser. However, without proper precautions, they are vulnerable to CSRF and XSS attacks, are not suitable for multi-domain applications, and have scalability issues.
- Tokens are not tied to only web applications and they promote server scalability. However, their management and security is left to the developer. Depending on the type of token, there may be an invalidation problem, i.e. a revoked token may still be used by the client, and a data transfer overhead if the token size becomes significant.
It's worth noting that while cookies in general can lead to scalability issues when used as pointers to server-side data, this is not the case with the authentication cookie issued by ASP.NET Core Identity. By default, this cookie contains encrypted data that allows the server to retrieve the user's identity information without having to access a server-side store. However, you need to properly configure your servers in order to share the same encryption/decryption key.
That said, when should you use one type of authentication over the other?
Which authentication type to use?
There's a common belief that cookie-based authentication should be used for traditional server-side page-rendered applications, while token-based authentication is for SPAs and native applications.
Actually, the ASP.NET Core Identity framework uses the cookie-based authentication approach for ASP.NET Core MVC and Razor Pages applications. With these types of applications, each request triggers a complete reload of the entire web page in the browser. In previous versions of .NET, if you wanted to leverage the ASP.NET Core Identity built-in authentication pages in your SPA, the user experience would be disrupted. Users would pass from the partial page reload experience typical of a SPA to a full page reload experience. This is one of the reasons behind the request of token-based authentication from many developers. They feel that having a token and a set of authentication APIs, such as the Identity API, allows them to build a consistent user experience for SPAs. In addition, this API can also be used by native applications, i.e., desktop and mobile applications, which were excluded by the traditional cookie-based approach.
This criterion may seem reasonable, but...
Wait! Identity API Endpoints Have Cookies Too?
So, in general, you might think that cookie-based authentication is meant for traditional server-rendered applications, while token-based authentication is meant for SPAs and native applications.
However, if you dig into the currently sparse documentation, you will find that you can call the authentication endpoint (/login
) with the useCookies=true
parameter. In other words, you can call the following endpoint: /login?useCookies=true
. What does this mean? What scenario does it cover?
Well, despite many developers thinking that SPA should use token-based authentication, this is not always the case. Think about this for a moment:
- Why should you use tokens and manage the back-and-forth and their security when you can use cookies to take advantage of the built-in capabilities of browsers?
- What can you do with a token that you can't do with a cookie?
- In other words, do you really need a token in your specific scenario?
As a general rule, you should use the right tool for your specific needs. Over-designing or following common sense can lead to extra work and possible risks because you have to worry about token security.
If the client of your Web API application is a native client, you are forced to use token-based authentication. Supporting cookies in native applications would be impractical. However, you can use cookies in your SPA. Provided that your application lives in a single domain, you can use cookies and their automatic management by browsers. Why not?
If your application consists of a SPA and a Web API, and you authenticate users locally using ASP.NET Core Identity, you should use cookie-based authentication. Technically, your application is a first-party application, and even OAuth best practices recommend using this approach.
Why Are These Tokens Not JWTs?
Another common misconception is that token means JSON Web Token (JWT). I've seen many developers react as shown in the following picture when they learn that the token issued by the Identity API is not in JWT format:
Actually, that token has a proprietary format, although you can partially decode it. Read this article if you want to learn more about what's inside the access and refresh tokens issued by the Identity API.
Again, why do developers think the JWT format would have been better in this context? If your application is a first-party application in the authentication context, why do you need JWT?
The JWT format was invented to share information between two trusted parties. In the authentication and authorization context, a typical example of JWT adoption is when a third-party entity authenticates or authorizes a user. In this case, the third-party entity (the Identity provider or authorization server) issues a JWT containing the information related to the authentication or authorization result.
The need for a JWT arises from the fact that the third-party entity and your application are independent; they do not share a common data source.
Your application receives the result of the requested operation in the form of a JWT and it can verify that this information was actually issued by that entity by validating the token's signature. To be precise, only OpenID Connect defines a JWT-based format for ID tokens. OAuth 2.0 doesn't recommend a specific format for access tokens, although one is available.
To learn more about ID token and access token differences, see this article.
The point is, does your application really need the JWT format for access tokens? If so, your scenario may be different from what the Identity API was designed for. In a first-party scenario, your server should have all the information it needs to make authorization decisions.
When Do You Need OpenID Connect and OAuth 2.0?
Developer reactions to the new Identity API make me think that they have been waiting for a built-in OpenID Connect and OAuth 2.0 implementation. They would probably expect Microsoft to have replaced Identity Server with its own OpenID Connect server. However, this may sound somewhat contradictory, since many have complained about the complexity of Identity Server in simple authentication scenarios.
Leaving aside speculation about what some developers expected, .NET 8 and its version of ASP.NET Core Identity is now here with its new authentication options. When should you use it in your applications and when do you need OpenID Connect and OAuth 2.0?
ASP.NET Core Identity can be the right solution if you need to build an application with basic authentication needs. Your users are authenticated locally, you don't expect much traffic and therefore don't plan to scale anytime soon, you don't have complex custom authentication needs. You can also integrate with external identity providers. I recommend using cookie-based authentication for web applications (including SPAs) and token-based authentication only for native apps.
OpenID Connect comes into play when your needs go beyond a simple authentication. You should look for a standards-compliant Identity provider if you need Single Sign-On (SSO), for example. If you expose your API to third parties, you can leverage the benefits of a standard authorization approach such as OAuth 2.0. In these cases, ASP.NET Core Identity is not a good fit.
Check this document for some guidance on when ASP.NET Core Identity is sufficient or when you need an OpenID Connect server.
Some Caveats About Identity API Endpoints
The discussion so far is intended to help you make the right choices for authentication in your .NET applications based on your specific application scenario. This will help you avoid overdesign and make an informed choice about the right approach.
If you have decided to use the Identity API for your SPA and native application, there are still a few caveats that you still need to be aware of.
The Identity API is a great addition that allows you to leverage the user authentication and management under the hood, while you can customize your own UI. However, there is currently no way to make its endpoints unavailable. All Identity API endpoints are exposed. For example, anyone can call the /register
endpoint to create a new user, regardless your application's business logic expects self-registered users or not. Presumably this will be fixed in the future, but as for .NET 8, it's like this.
A second concern is that you can't customize the Identity API endpoints. In most cases, you may not need customization, but what can you do if you need to customize the data required in the /registration
endpoint? Or even add an unsupported authentication method, such as WebAuthn?
Include these questions in your ASP.NET Core Identity evaluation.
Recap
In summary, .NET 8 brings exciting new features to ASP.NET Core Identity: token-based authentication and the Identity API.
However, this does not mean that they are the right approach for your specific scenario. Take some time to analyze what you need right now and in the near future. Don't follow the trend without carefully analyzing your needs and what ASP.NET Core Identity can offer you. You probably don't need token-based authentication for your SPA. Perhaps the simple token format for the Identity API is enough for your needs.
Anyway, if your analysis leads you to use OpenID Connect, don't forget that Auth0 can help you! π