Let's make our SPA more secure by setting up a .NET BFF with Duende and Auth0

profile
Tim Deschryver
timdeschryver.dev

The Backend for Frontend (BFF) architecture is gaining popularity for multiple reasons.

The main reason, for me as a developer, for using a BFF is that it simplifies the communication between the front-end and multiple backend services. Instead of having the frontend communicate with multiple backend services, the frontend communicates with a single BFF service that will then communicate with the backend services.

The BFF is responsible to prepare the data in the shape that the frontend expects. This can be for one backend service, or the BFF can aggregate data from multiple backend services. Doing this work in the BFF service improves the user experience because the frontend delegates this work to the BFF service, resulting in a faster experience for the user.

The security aspect of a BFF link

This sounds good, but another big reason for using a BFF is security.

Most of the frontend applications are currently using the Implicit (grant) flow to authenticate users. It's the flow where you store the access token in the browser, and where you append the token to the requests to the backend service. Sadly, as we know too well, the browser is not a secure place to store the access token. This makes it that this flow has a serious security flaw. Because the access token is exposed to the frontend application, it becomes vulnerable to access token leakage. That's why the Implicit flow is not recommended anymore. In fact, this has been the case for 3 years already.

format_quote

It is not recommended to use the implicit flow (and some servers prohibit this flow entirely) due to the inherent risks of returning access tokens in an HTTP redirect without any confirmation that it has been received by the client.

Sadly, the Implicit flow is still widely used by many Single Page Applications.

Besides the security risk, many browser vendors are starting to restrict the use of third-party cookies across site boundaries because of the extra vulnerabilities they bring. This also has a big impact on the Implicit flow and breaks the current silent token renewal implementation.

So, what can we do to solve these risks? The BFF architecture solves both problems:

How the BFF architecture solves the security risks link

Instead of the frontend application is responsible to handle the authentication process by communicating with the Identity Provider (and storing the access token), the BFF now becomes in charge of the authentication process. By using the Authorization Code flow with Proof Key for Code Exchange (PKCE), the BFF architecture solves these security issues.

The PKCE flow is the recommended flow to prevent CSRF and authorization code injection attacks. The BFF using the PKCE flow hides the vulnerable information from the frontend application, and thus also from the attackers.

In this blog post, we'll set up a .NET BFF using the Duende.BFF NuGet Package in combination with Auth0.

Creating an Auth0 Application link

Note

To follow along with me, you can create a (free) Auth0 account here.

The first thing we need to do is to create a new Auth0 application. Give the application a name and select the "Regular Web Applications" application type, even though in the end it's consumed by a SPA.

Select "Regular Web Applications" in the wizard to create a new Auth0 application.

When the application is created, open the "Settings" tab and grab the basic information from the "Basic Information" section, which we'll use later to configure the BFF .NET API.

The Auth0 settings page after the application is created.

Lastly, scroll down until you see the "Application URIs" section. Here, add the following URIs. You can get the port number from the launchSettings.json file when the .NET API is created in the next step. For me, my Web API is running on port number 7012.

URL Value
Application Login URI https://127.0.0.1:7012/bff/login (for some reason you need to use 127.0.0.1 instead of localhost)
Allowed Callback URLs https://localhost:7012/signin-oidc
Allowed Logout URLs https://localhost:7012/
The Auth0 Application URIs settings to configure the application urls.

Creating a BFF API link

Next, create a new .NET WebApi project using the dotnet CLI. While creating the project, also add the Duende.BFF NuGet package to the project. This package contains the necessary components to secure browser-based frontends (e.g. SPAs or Blazor WASM applications) with ASP.NET Core backends. You could write this integration yourself, but I'm sure the Duende team got more knowledge about this topic than I am.

Open the MyBFFApplication project in your favorite editor and add the following code to the Program.cs file:

What does this get us? link

This setup gives us the most basic setup to secure your endpoints. Most of the implementation is using the built-in ASP.NET functionality to configure the authentication and to require that the user is authorized to access the weather forecast endpoint.

Let's take a look at what happens when we invoke a couple of endpoints.

First, let's try to access the /api/weatherforecast endpoint. As expected because the weather forecast is annotated with RequireAuthorization() and we're currently not authenticated this request results in a 401 Unauthorized response, with the corresponding logs:

To authenticate us, we need to call the /bff/login endpoint, which is provided by the Duende.BFF package. If the .NET Web API and Auth0 are configured correctly, this endpoint redirects us to the Auth0 login page. After logging in, we're redirected back to the API.

At this point, you should see a Cookie with the name __MySPA (configured within the Services.AddCookie method) in your browser. To verify that the Cookie exists, open de Developer Tools in your browser, go to the Application tab, and click on the cookies.

The browser's developer tools on the Application tab with the cookie selected.

If you see a cookie with the name __MySPA, this means that you're correctly authenticated and you're good to go.

Next, let's try to access the /api/weatherforecast endpoint again. To my surprise, this still results in a 401 Unauthorized response. This time with a different cause:

After reading the logs, this makes sense. The BFF middleware is configured to require an anti-forgery token for the requests. By default, the BFF middleware expects the X-CSRF header to be present in the request with a value of 1. To change these defaults, you can override the options within the Services.AddBff method.

As we could see, when this header is not present, then the request is rejected.

When the correct anti-forgery token is present, the request is accepted and the response is returned.

To get the information about the authenticated user, there's the /bff/user endpoint. In my case, this returns the following information when I'm authenticated via GitHub:

Lastly, to end the user's session use the /bff/logout endpoint and append the session id, which is added to the user response. In this case, this is https://localhost:7012/bff/logout?sid=-8PY-AuhF53pC_OU3aBiPZnzMShXimCk.

Conclusion link

While the primary reason for using the BFF architecture for me was developer convenience, I recently learned (see the resources below) and realized that it also provides a more secure way of building applications. Because, in short, storing sensitive tokens in the browser is broken beyond repair.

In this blog post, we've seen how to transfer the authentication process from the SPA to the .NET Web API. In doing so, we removed the security risk of leaking tokens to potential attackers. Additionally, we made our application future-proof of changing browser implementations.

The BFF architecture provides an answer to this security flaw. Instead of storing the tokens in the browser, a cookie is used on the server to hold the user's information. A BFF also reduces the attack surface, because the BFF is the only trusted application (instead of the SPA and the API). By using a BFF we centralize the authentication and authorization process.

From my limited experience, it looks like that a BFF using the Authorization Code flow with PKCE has fewer moving parts in comparison to the Implicit flow. This makes it easier to understand, implement, deploy, and maintain.

In the next blog post, we'll consume this BFF from an Angular application.

You can play with the demo BackendForFrontend application on Github.

Resources link

Incoming links

Outgoing links

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.

Buy Me a Coffee at ko-fi.com PayPal logo

Share this post

Twitter LinkedIn