Back to Blog
Headshot of Nemanja Vasić

7 minutes read

Securing your web apps: Spring security OAuth 2.0 + BFF pattern

Nemanja Vasić

Software Engineer

A complete implementation guide

Modern web applications face constant security challenges, especially when handling user authentication and tokens. In this guide, we’ll walk through a step-by-step implementation of OAuth 2.0 with the Backend for Frontend (BFF) pattern using:

  • Spring security OAuth 2.0 authorization server
  • Spring Cloud gateway as BFF
  • Spring OAuth 2.0 resource server and
  • React as a frontend client.

In order to understand parts of this blog post it helps to be familiar with basic OAuth 2.0 concepts and flows. The following blog posts may be useful:

Basic OAuth 2.0 concepts

Authorization Code Flow for Confidential Clients with BFF

In this blog post I will give you an example of how to implement OAuth 2.0 backend including BFF (Backend For Frontend) for browser-based applications using Spring Security OAuth 2.0 authorization server, Spring Cloud Gateway for BFF, Sprint OAuth 2.0 Resource Server and React as a frontend client.

Why BFF is recommended for browser-based applications

The Backend for Frontend (BFF) in simple terms is a server-side application that is designed to support different front-end clients. One of the benefits of BFF and its architecture is that it makes it easier to secure client applications. In the context of this blog post this pattern is strongly recommended for browser-based applications because all token management and sensitive credential handling are handled on the secure server-side environment. Instead of storing vulnerable access and refresh tokens in the browser where they’re exposed to Cross-Site Scripting (XSS) attacks, the BFF securely maintains these tokens server-side. The browser only receives a secure, HTTP-only session cookie for BFF communication, significantly reducing the application’s attack surface and aligning with OAuth 2.0 security best practices.

More on BFF architecture in context of OAuth 2.0

Implementation of OAuth 2.0 backend with BFF pattern

The complete code can be found in this GitHub repository. If you want to try it out just follow the steps in README.md.

Architecture overview

The architecture of the application consists of the following components:

auth-server

The auth-server component serves as an Authorization Server implemented with Spring Boot and the OAuth 2.0 authorization server library. It connects to MariaDB for storing OAuth 2.0 objects, including registered clients, consents, and authorizations.

If you run the app, you can access MariaDB on

http://localhost:8080/?server=oauth_db&username=oauth&db=oauth_db

All configurations are implemented via Spring DSL (Domain-Specific Language) in Java.The most important configuration is in AuthorizationServerConfiguration.java.

secure-resource

The secure-resource component serves as a Resource Server implemented with Spring Boot and the OAuth 2.0 resource server library. It manages and protects APIs by ensuring that access is granted only to authenticated and authorized clients. For demo purposes, it has one controller for getting /resource that just returns text with a currently authenticated user. Learn more about building secure API integrations for your digital ecosystem.

The gateway component serves as the Backend for Frontend (BFF) layer implemented using Spring Boot and Spring Cloud Gateway library. It acts as a secure gateway between the frontend client and backend services, as well as a confidential client on Authorization Server (auth-server). It handles token management and authentication while providing a secure session-based communication channel through HTTP-only cookies.This component is crucial for enhancing security by keeping sensitive OAuth tokens server-side instead of exposing them to the browser. Session data is persisted in Redis DB for reliable state management.

If you run the app, you can access Redis UI on

http://localhost:8082

fe-client

The fe-client is a Single Page Application (SPA) built using React that serves as the frontend interface for the system. It communicates exclusively with the gateway (BFF) component through HTTP requests, using HTTP-only cookies for session management and authentication. This client-side application focuses purely on the user interface and business logic, while delegating all security-sensitive operations, such as token management, to the BFF layer.

Sequence diagram

1. User initiates request through the frontend application

2. Gateway checks Redis for a valid session

3. If no valid session exists:

    3.1 The user is redirected to the login page hosted on Authorization Server

    3.2 After successful login, the consent screen is shown if the user did not previously give his consent

    3.3 The authorization code is exchanged for access and refresh tokens

    3.4 A session is established with tokens stored in Redis

4. With a valid session:

    4.1 The request flow goes through gateway

    4.2 The gateway retrieves tokens from Redis

    4.3 Authenticated requests including access token are sent to resource server

    4.4 Responses are returned to the frontend

Implementation parts

Let’s now go through the  important parts of the code that are executed and reflect the flow in the sequence diagram above.

We start with the fe-client (React) code.

const axiosInstance = axios.create({<br>  baseURL: backendBaseUrl,<br>  withCredentials: true,<br>  xsrfCookieName: 'XSRF-TOKEN',<br>  xsrfHeaderName: 'X-XSRF-TOKEN',<br>});<br>

Here we have a configuration for Axios. The baseUrl sets a BFF backend URL.withCredentials: true is important because it ensures that session cookies are sent with each request to the backend.

xsrfCookieName: ‘XSRF-TOKEN’ and xsrfHeaderName: ‘X-XSRF-TOKEN’ are the settings that are telling Axios to look for the CSRF token in the cookie named XSRF-TOKEN and to include that token in the HTTP header named X-XSRF-TOKEN. This ensures that requests are coming from the legitimate frontend.

Next, when client code executes in the user’s browser, the getUserInfo function is called.

 const getUserInfo = async (): Promise<void> => {<br>    try {<br>      const response = await axiosInstance.get('/userinfo');<br>      if (response.data) {<br>        setIsAuthenticated(true);<br>        setUserName(response?.data?.sub);<br>      }<br>    } catch (error) {<br>      console.error('Error getting user info', error);<br>    }<br>  };

This will either return 401 Unauthorized if the user doesn’t have an active session, or it will return a response with the user data. If there is no active session for the user, the Login button will be present on the UI. When clicking on the Login button,  following function is called.

  const login = () => {<br>    window.location.href = backendBaseUrl;<br>  };

It sets the browser’s location to the BFF backend URL.This redirects the browser to the BFF gateway.

Why is this important, and why not use an XHR request to call the BFF backend?

The OAuth 2.0 Authorization Code flow, which will be initiated when BFF base URL is called, inherently relies on browser redirects to securely initiate and complete the authentication process. Redirects allow the browser to handle cross-origin navigation, display the identity provider’s login UI (in this case, the login hosted by the auth-server), and properly manage secure, HttpOnly cookies.

In contrast, XHR requests can’t follow redirects to external login pages, often run into CORS issues, and aren’t suitable for handling interactive authentication flows.

Next, let’s examine the gateway. This service is implemented with a reactive, non-blocking approach using Web Flux.

We have Route Configuration, which includes the customRouteLocator method where routes are defined. This method uses the TokenRelayGatewayFilterFactory instance to configure filters for defined routes. The tokenRelay is responsible for taking the cookie from the client request and matching it with the session, and taking an access_token from the session and adding it as a Bearer token in request to downstream services.

Another important part is .removeRequestHeader(“Cookie”) which will prevent session cookies from being forwarded to the downstream services. This ensures that BFF session cookies stay within the gateway.

In the Security Configuration, the main security rules are configured using a reactive approach. The main difference from the standard blocking approach in Spring Security here is the use of ServerHttpSecurity instead of HttpSecurity.

The securityFilterChain defines the following:

  • CSRF Protection setup: Uses cookies for handling CSRF tokens. An important part here is setting withHttpOnlyFalse() for CSRF Token Repository, in order to make the CSRF token readable by JavaScript. This will allow React client applications to read the token and include it in the requests to the backend.
  • CSRF Cookie Web Filter: Ensures CSRF token is included in the response.
  • Authentication and Authorization setup: Requires all requests to be authenticated, and configures OAuth 2.0 login to be able to handle Authorization Code Flow with auth-server. It also configures the OAuth 2.0 client to be able to handle client credentials and token management.
  • Logout Handler: Handles the OIDC logout flow and integrates with the auth-server logout flow, ensuring that the user session is also removed on the auth-server side. It also sets the post logout redirect URL which is the URL of fe-client React application.

Next, let’s look at the auth-server.

The authorizationServerSecurityFilterChain is the base security configuration for OAuth 2.0 authorization server endpoints. It configures essential services such as:

  • authorizationService – Handles OAuth 2.0 authorizations.
  • authorizationConsentService – Handles OAuth 2.0 user consents.
  • authorizationClientRepository – Handles OAuth 2.0 client registrations.
  • OIDC – Enables OpenID Connect support with default standard-compliant OIDC settings.

This gives us the /userInfo endpoint which is used by the gateway to get user information. Security configuration also includes rules that require all requests to OAuth 2.0 endpoints to be authenticated. It also configures how unauthenticated requests are handled; in this case, HTML requests are redirected to a custom login page.

Next, let’s examine the last service – secure-resource.

The securityFilterChain is the security configuration for the Resource Server. It sets up two authorization rules:

  • The /resource endpoint requires the authority (OAuth2 scope) SCOPE_resource.read
  • All other requests must be authenticated (as a fallback rule). The .oauth2ResourceServer configures this service as an OAuth 2.0 resource server, and defines the default JWT configuration. This configuration will create a secure endpoint /resource that will accept only valid JWT tokens and requires the SCOPE_resource.read scope to access protected resources. An important part in this service is jwk-set-uri which tells the Resource Server where to find the public keys for validating JWT tokens. This is a standard OAuth 2.0 endpoint that returns a JSON Web Key Set (JWKS) containing the public keys used to validate tokens. This endpoint is exposed by the Authorization Server.

Conclusion

In this guide, we’ve explored the implementation of a secure OAuth 2.0 backend utilizing the BFF pattern for browser-based applications.

This architecture demonstrates several key benefits:

Enhanced Security: By implementing the BFF pattern, we’ve moved sensitive token handling to the server side, significantly reducing the risk of token exposure through XSS attacks.

Clean Separation of Concerns: Each component (Authorization Server, Resource Server, Gateway/BFF, and Frontend) has well-defined responsibilities, making the system more maintainable and scalable.

Modern Technology Stack: The implementation leverages current best practices using Spring Security OAuth 2.0 authorization server, Spring Cloud Gateway, Spring OAuth 2.0 Resource Server, and React.

Production-Ready Features: The solution includes essential components like Redis for session management, CSRF protection, and proper token management.

The provided implementation serves as a practical reference for building secure, modern web applications following OAuth 2.0 security best practices. The complete source code is available in the GitHub repository. Feel free to fork or clone it to start building your own secure applications.

Remember, hands-on experience is the best way to learn.

Headshot of Nemanja Vasić

Nemanja Vasić

Software Engineer

Nemanja is a seasoned Software Developer with over five years of professional experience in the information technology industry. His current tech stack comprises ReactJs, NextJs, Javascript, NodeJs, Java, Spring Boot, and MongoDB, among others.
He holds a degree from the Faculty of Technical Sciences, University of Novi Sad, and has a proven track record of delivering high-quality software solutions.

Related posts.