A guide to building a simple ASP.NET Core BFF with a React APP
Published: 2023-06-21 13:09:34

Implementing authentication in a BFF with a React app is deemed to be complicated. Especially because proper documentation or examples are hard to find. 

Implementing authentication a BFF with a react app is actually pretty straight forward. Read this article and learn how to build a BFF that hosts a React APP with ASP.NET Core.

## Understanding the BFF Pattern
The purpose of the BFF (Backend for Frontend) pattern is to minimize the number of HTTP calls made by the front-end to the underlying microservices. Because, as the term "micro" suggests, there might be quite a few.

The BFF runs on the server-side, and the front-end interacts with it through its API. The BFF invokes multiple downstream APIs, it consolidates the responses from these APIs into a single response, allowing the front-end to retrieve all the required data in a single request.

From an infrastructure perspective, this is a representation of the BFF pattern:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aihd8jydi4u4yhz115vn.png)

If authentication is necessary, this pattern presents certain difficulties. Recently, the development community discourages using tokens in the front-end because that is not deemed safe enough anymore.

Instead, tokens are to be used server-side only. This means anything with tokens should move to the BFF. This means:

* The site and the BFF must be on the same domain
* The BFF must manage the users' session

The following steps describe how to implement this:

## Implementing the BFF Security Pattern
As I stated earlier, fortunately, implementing the BFF pattern with ASP.NET Core isn't hard. A lot of boilerplate projects are available out of the box. To set up an ASP.NET Core project that hosts a React app, just type the following command in your CMD or Terminal:

```bash
dotnet new react
```

This will scaffold an ASP.NET Core App with a react app:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/umk7s2rcnjmqq5itujaz.png)

The pre-built application includes an API that generates weather forecasts. However, when it comes to a BFF implementation, the server-side should refrain from performing any calculations and instead focus on forwarding all requests to downstream services. So, the first thing to do is to remove the controllers in the projects.

## Implementing the Authentication Gateway
Implementing an authentication gateway is as easy as creating an ASP.NET Core app that hosts a React App.

Just add the following package to connect to any OpenId Connect compliant Identity Provider:

```bash
dotnet add package GoCloudNative.Bff.Authentication.OpenIdConnect
```

Register this component in the ASP.NET Core pipeline, modify the `Program.cs` to be similar to:

```csharp
using GoCloudNative.Bff.Authentication.ModuleInitializers;
using GoCloudNative.Bff.Authentication.OpenIdConnect;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

// !! Register the authentication gateway here
builder.Services.AddSecurityBff(o =>
{
    o.ConfigureOpenIdConnect(builder.Configuration.GetSection("OIDC"));
    o.LoadYarpFromConfig(builder.Configuration.GetSection("ReverseProxy"));
});
// !!

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

// !! Register the authentication gateway here too
app.UseSecurityBff();

// And remove this
// app.MapControllerRoute(
//     name: "default",
//     pattern: "{controller}/{action=Index}/{id?}");

app.MapFallbackToFile("index.html");

app.Run();

```

And, assuming you are using two microservices (as illustrated in the diagram in the beginning of the article), use the following appsettings.json:

```json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "Oidc": {
    "ClientId": "{yourClientId}",
    "ClientSecret": "{yourClientSecret}",
    "Authority": "https://{yourAuthority}",
    "Scopes": [
      "openid", "profile", "offline_access"
    ]
  },
  "AllowedHosts": "*",
  "ReverseProxy": {
    "Routes": {
      "api1": {
        "ClusterId": "api1",
        "Match": {
          "Path": "/api/{*any}"
        }
      },
      "api2": {
        "ClusterId": "api2",
        "Match": {
          "Path": "/api/bar/{*any}"
        }
      }
    },
    "Clusters": {
      "api1": {
        "Destinations": {
          "api1": {
            "Address": "http://localhost:8081/"
          }
        }
      },
      "api2": {
        "Destinations": {
          "api2": {
            "Address": "http://localhost:8082/"
          }
        }
      }
    }
  }
}
```

This code modifies the default ASP.NET Core React project:

* By adding the SecurityBff and configuring OpenId Connect, users can now authenticate.
* By loading the reverse-proxy config, all requests to endpoints that start with /api are forwarded to downstream services. The forwarded requests are enriched with the bearer token that was issued by the Identity Provider when the user has logged in.

Nontheless, by default, all requests are handled by the React app. To get the BFF to work, all requests to "/api" and to "/account" need to be handled by the ASP.NET Core app. Therefor, we need to modify the `setupProxy.js` file that is located in `ClientApp/src`. Register the api- and the account endpoints there:

```javascript
const { createProxyMiddleware } = require('http-proxy-middleware');
const { env } = require('process');

const target = env.ASPNETCORE_HTTPS_PORT ? `https://localhost:${env.ASPNETCORE_HTTPS_PORT}` :
  env.ASPNETCORE_URLS ? env.ASPNETCORE_URLS.split(';')[0] : 'http://localhost:51710';

const context = [
      "/api",
      "/account"
];

const onError = (err, req, resp, target) => {
    console.error(`${err.message}`);
}

module.exports = function (app) {
  const appProxy = createProxyMiddleware(context, {
    target: target,
    onError: onError,
    secure: false,
    headers: {
      Connection: 'Keep-Alive'
    }
  });

  app.use(appProxy);
};

```

And that's it. Check out the code here: https://github.com/appie2go/dotnet-spa-and-bff-react

## Logging in
Those are all the steps you need to do to create a BFF that hosts a React App. When you start the app, by default, users are not authenticated. To authenticate, navigate the user to `/account/login`. To see who's logged in, execute a GET request to the `/account/me` endpoint. To sign-out, navigate the user to `/account/end-session`.

## Sources
* https://bff.gocloudnative.org/integration-manuals/quickstarts/identityserver4/quickstart/