Using cookies for authentication in an Angular application

Series: Building a secure application (4/4)
- Using YARP as BFF within .NET Aspire: Integrating YARP into .NET Aspire
- Secure your Yarp BFF with cookie-based authentication
- Forwarding authenticated calls to a downstream API using YARP
- Using cookies for authentication in an Angular application
In the prevous posts of this series, we've already seen how to set up a Backend for Frontend (BFF) using Aspire and how to implement a secure authentication flow for the .NET backend of the application. In this post, we will focus on the Angular frontend, and how to properly handle authentication.
Code flow with PKCE link
When building a secure Angular, or any SPA , application, there are two main approaches to handle authentication: using the Authorization Code flow with PKCE or using cookies. With the Authorization Code flow with PKCE, the frontend application is responsible for the entire authentication process. The process starts to redirect the user to the identity provider, handles the authorization code exchange, and manages tokens (access and refresh tokens) on the client side.
Because this is very specific (complex) code, but yet very important to get right, many choose to use libraries that abstract away the complexity. In Angular, we can use libraries like angular-auth-oidc-client which simplifies the implementation of the Authorization Code flow with PKCE.
However, this approach has some downsides:
- Security risks: Storing tokens in the browser (e.g., in local storage or session storage) can expose them to XSS attacks.
- Token management: Handling token refresh and expiration can be challenging on the client side.
- CORS issues: Making API calls to a backend from a different origin can lead to CORS issues that need to be managed.
- Bigger bundle size: Including authentication libraries can increase the size of the frontend application.
Using cookies for authentication link
An alternative approach is to use cookies for authentication. In this approach, the backend (BFF) handles the authentication process and manages the authentication state.
While it's possible to read the cookies' content using JavaScript, it's generally not recommended to have the frontend application directly access (authentication) cookies. That's why I mentioned in the previous post to set the cookies as HttpOnly and Secure, which prevents JavaScript from accessing them, enhancing the security.
Instead, the cookie should not be directly consumed by the frontend application. The backend is responsible for setting and reading the authentication cookies. The frontend application simply makes requests to the backend to access the authenticated state, just as it would with any other API resource.
While doing so, we avoid the downsides of the Authorization Code flow with PKCE. The only prerequisite is that the frontend and backend are served from the same domain, or that the cookie is configured to be sent across domains (using the SameSite=None; Secure attribute).
Implementing authentication in Angular link
The good news is that implementing cookie-based authentication in an Angular application is straightforward. The main idea is to create an authentication service that communicates with the backend to determine the user's authentication status. Let's take a look at how we can implement this in an Angular application.
The main concerns of the authentication implementation are the login and logout methods, and a way to fetch the authenticated user's information.
From a technical side, these can all be implemented within a single service that communicates with the backend.
In the example below, this service communicates with the /bff endpoints to perform these actions.
The login and logout methods simply redirect the user to the corresponding backend endpoints, while the user property fetches the authenticated user's information.
Another important part of the authentication is to protect routes that require an authenticated user. We can achieve this by creating a route guard that checks the user's authentication status before allowing access to certain routes, this is also a feature that's commonly provided by authentication libraries. If the user is not authenticated, the following guard will redirect the user to the login page.
For the completeness of the implementation, here are the three corresponding backend endpoints that the Angular application communicates with for authentication. Most of the heavy lifting is done by ASP.NET itself.
Conclusion link
Using cookies for authentication in an Angular application is a secure and straightforward approach. By delegating the authentication process to the backend, we can avoid many of the complexity and security risks associated with client-side token management.
In this post we've seen the most basic implementation of an authentication service in Angular that communicates with the backend to manage the user's authentication state, along with route guards to protect authenticated routes. This approach can further be extended with additional features such as role-based access control, or custom directives to show UI elements based on the user's authentication status.
The code samples provided in this post can be found in my Sandbox repository on GitHub.
Feel free to update this blog post on GitHub, thanks in advance!
Join My Newsletter (WIP)
Join my weekly newsletter to receive my latest blog posts and bits, directly in your inbox.
Support me
I appreciate it if you would support me if have you enjoyed this post and found it useful, thank you in advance.