Skip to main content

Backend For Frontend BFF

Repository

https://github.com/Mehdi-HAFID/bff

Credit

All credit for the BFF and the Reverse Proxy implemented in Nidam goes to Jérôme Wacongne and his article in baeldung.com OAuth2 Backend for Frontend With Spring Cloud Gateway. The BFF and the Reverse Proxy in Nidam is pretty much a copy paste from his article. I strongly recommend to go there and read his article.

Objective

The BFF has mainly 3 responsibilities:

  • Login flow initiation: when the user clicks the Login button in the frontend, the whole OAuth 2 Authorization Code Flow is started and the login form is displayed.
  • Token Storage: Tokens issued by the authorization server are saved and a corresponding SESSION Cookie is set which is what the frontend uses.
  • SESSION Cookie Translation: All calls by the frontend are sent to the BFF, with the SESSION Cookie. Before The BFF forwards the request to the resource server, it attaches the Token associated with the Cookie.

Crucial Dependency

Jérôme Wacongne has used a dependency called spring-addons-starter-oidc which he himself created. This version of Nidam will ship with this dependency. A future version will aim to remove dependency and only use Spring Security code. But for now, we will use this dependency. The dependency aims to simplify security configuration code.

pom.xml

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>nidam</groupId>
<artifactId>bff</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>bff</name>
<description>bff</description>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2023.0.0</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<artifactId>spring-addons-starter-oidc</artifactId>
<version>7.5.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

The BFF is an OAuth 2 Client, and it uses Spring Gateway for redirection.

Configuration

Declaration of URLs and client id and secret

src/main/resources/application.yml
scheme: http
hostname: localhost

reverse-proxy-port: 7080
reverse-proxy-uri: ${scheme}://${hostname}:${reverse-proxy-port}

authorization-server-prefix: /auth
issuer: ${reverse-proxy-uri}${authorization-server-prefix}

client-id: client
client-secret: secret

username-claim-json-path: $.sub
authorities-json-path: $.authorities

bff-port: 7081
bff-prefix: /bff

resource-server-port: 4003

audience:

server:
port: ${bff-port}

The BFF Core Functionality

The following is the most important piece of code in the BFF. It takes all calls made to /api/** and redirects them to the resource server.

  1. First, the front calls http://localhost:7080/bff/api/something which is handled by the reverse proxy and forwards it to http://localhost:7081/api/something.
  2. The BFF removes the /api and using Spring Gateway TokenRelay= attaches the corresponding token before forwarding the request to the resource server http://localhost:4003/something.

The SaveSession filter persists the token that is generated and returned by the authorization server during the login process.

src/main/resources/application.yml
spring:
cloud:
gateway:
routes:
- id: bff
uri: ${scheme}://${hostname}:${resource-server-port}
predicates:
- Path=/api/**
filters:
- DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin
- TokenRelay=
- SaveSession
- StripPrefix=1

OAuth 2 Client Definition

The BFF is an OAuth 2 Client, so we need to declare the properties we defined in the authorization server.

src/main/resources/application.yml
spring:
security:
oauth2:
client:
provider:
token-generator:
issuer-uri: ${issuer}
registration:
token-generator:
provider: token-generator
authorization-grant-type: authorization_code
client-id: ${client-id}
client-secret: ${client-secret}
scope: openid

spring-addons-starter-oidc Dependency

I will not explain it better than Jérôme Wacongne so, I will just quote his explanation for this part of the code.

Let’s understand the three main sections:

  • ops, with provider(s) specific values. This enables us to specify the JSON path of the claims to convert to Spring authorities (with optional prefixes and case transformation for each). If aud property is not empty, spring-addons adds an audience validator to the JWT decoders.

  • client, when security-matchers are not empty, this section triggers the creation of a SecurityFilterChain bean with oauth2Login(). Here, with client-uri property, we force the usage of the reverse-proxy URI as a base for all redirections (instead of the BFF internal URI). Also, as we are using SPAs, we ask the BFF to expose the CSRF token in a cookie accessible to Javascript. Last, to prevent CORS errors, we ask that the BFF respond to the RP-Initiated Logout with 201 status (instead of 3xx), which gives SPAs the ability to intercept this response and ask the browser to process it in a request with a new origin.

  • resourceserver, this requests for a second SecurityFilterChain bean with oauth2ResourceServer(). This filter chain having an @Order with the lowest precedence will process all of the requests that weren’t matched by the security matchers from the client SecurityFilterChain. We use it for all resources for which a session is not desirable: endpoints that aren’t involved in login or routing with TokenRelay.

src/main/resources/application.yml
com:
c4-soft:
springaddons:
oidc:
# Trusted OpenID Providers configuration (with authorities mapping)
ops:
- iss: ${issuer}
authorities:
- path: ${authorities-json-path}
aud: ${audience}
# SecurityFilterChain with oauth2Login() (sessions and CSRF protection enabled)
client:
client-uri: ${reverse-proxy-uri}${bff-prefix}
security-matchers:
- /api/**
- /login/**
- /oauth2/**
- /logout
permit-all:
- /api/**
- /login/**
- /oauth2/**
csrf: cookie-accessible-from-js
oauth2-redirections:
rp-initiated-logout: ACCEPTED
# SecurityFilterChain with oauth2ResourceServer() (sessions and CSRF protection disabled)
resourceserver:
permit-all:
- /error
- /actuator/health/readiness
- /actuator/health/liveness