Wednesday, November 5, 2025

[Tips] Spring Boot - React Calls REST APIs Built on WebFlux with API Key Authentication

Last updated on:

 

When building a web app, we’d have a variety of considerations. Functions, along with operability, are what we must include; ease of use is always something that we should pursue; fancy graphical interfaces are a feature that we’d better to have to impress users. Mobile device compatibility is another input we should take into account. Take a look at how many smartphones and tablets are sold every year. It seems to become more and more demanding.

React is one of the most popular libraries for building frontend apps. Technically speaking, React encapsulates rendering logic and markup in components; hence it achieves excellence of interactivity. Facebook and Instagram are the representative examples. 

On the other hand, Java maintains as one of the top languages for building enterprise apps. Not to mention, there are enormous Java apps running in datacenters. With Spring framework, we can build REST APIs in an efficient and effective manner.

If we look beyond, REST APIs can be an effective tool to integrate enterprise apps. Through them, we can provide access to the existent apps or data, can extend the capability to support more flexible and loosely-coupled architectures. When we introduce a new app to the current portfolio or employ a new architecture or develop the enterprise blueprint, it is almost inevitable to look into integration.

The combination of these technologies appears to be an attractive option in terms of developing an enterprise app.

In the meanwhile, secure access to APIs should be ensured, meaning an authentication mechanism should be put into place.


In this demo, we are going to build:

  • A WebFlux app which publishes REST APIs (KeyAccessRestApi project);

  • A React client which consumes the APIs (reactclient project).

With regard to authentication, we’ll get API key access implemented.

Our first try is to display all the departments on the front page. This is what it looks like.



The demo projects can be downloaded from GitHub



Prerequisites


To create a WebFlux app, we’ll need Spring Tool Suite. In case you use Eclipse as the IDE, go to Eclipse Marketplace, find and install it. 

To do so, in the Help menu , click Eclipse Marketplace, type “Spring Tool Suite” in the Find box, and click Go button. 

Locate Spring Tools (aka Spring Tool Suite) [Version, for example, 4.32.1.RELEASE] and then click Install button.

Moreover, the below tables need to be created beforehand. 

  • Departments - Holds the list of the departments that will be displayed on the front page.

  • App_ApiKey - Keeps all the valid keys issued to API users.


SpringMVC project/schema.sql
CREATE TABLE Departments (
   Department_Id NUMBER(4)
 , Department_Name VARCHAR2(20)
 , Manager_Id NUMBER(6)
)
 NOLOGGING
 PARALLEL;

CREATE TABLE App_ApiKey (
   Key VARCHAR2(64) UNIQUE
  ,Username VARCHAR2(30) NOT NULL
  ,Description VARCHAR2(128)
)
 NOLOGGING;

The data classes associated with these tables are DptDto and ApiKeyDto, defined in the DptDto.java and ApiKeyDto.java files, respectively.

For the demonstration, we load some test data into the tables.


SpringMVC project/schema.sql
INSERT INTO Departments
WITH tabrows AS (
 SELECT 10, 'Administration', 100 FROM DUAL UNION ALL
 SELECT 20, 'Marketing', 200 FROM DUAL UNION ALL
 SELECT 30, 'Compliance', 300 FROM DUAL UNION ALL
 SELECT 40, 'Channel', 400 FROM DUAL UNION ALL
 SELECT 99, 'Dummy', null FROM DUAL
)
SELECT * FROM tabrows;

INSERT ALL
 INTO App_ApiKey 
  VALUES('XvcItqrOmhPb2gxalIiGWhe7kOQ5cT6E5ZpUpYLM8RU=', 'DEMO', 'Key for user DEMO')
 INTO App_ApiKey 
  VALUES('nXW29eDKVKZnwT2Hu5LXVHp+m0Xd32py9OdRp0qSZIw=', 'TEST', 'Key for user TEST')
SELECT * FROM DUAL;


Create a WebFlux App


To create a WebFlux app, from the main menu, select New > Others, and then choose Spring Starter Project.

In the New Spring Starter Project dialog box, type KeyAccessRestApi as the project name.

In the Dependencies page, check Spring Reactive Web, Srping Data JDBC, Oracle Driver, Spring Security options.

Click Finish button.

Here you go. The KeyAccessRestApi project has been created. It is empty at this point.



Route the Request to the Handler Function


We build a RouterFunction bean to map the API path to the handler, which processes the request and returns the set of departments to the client.

Take a look at the example below, RouterFunctions.route, which indicates:

API Method: GET

Path: /rest/findalldpts (AppConstant.REST_ROOT + AppConstant.API_DPT_FINDALL)

Handler Function: findAllDpts


KeyAccessRestApi project/DptRouter.java
@Configuration(proxyBeanMethods = false)
public class DptRouter {
	@Bean(name = "dptroute")
	public RouterFunction<ServerResponse> route() {

	    return RouterFunctions
	      .route(GET(AppConstant.REST_ROOT+AppConstant.API_DPT_FINDALL)
	    		  .and(accept(MediaType.APPLICATION_JSON)), 
	    		  this::findAllDpts);
	  }
	
	@Autowired
	DptService dptService;
	
	public Mono<ServerResponse> findAllDpts(ServerRequest request) {
		List<DptDto> listDpt = dptService.findAllDpts();
		
		if (listDpt != null && !ObjectUtils.isEmpty(listDpt)) {
			return ServerResponse.ok()
					.contentType(MediaType.APPLICATION_JSON)
					.body(BodyInserters.fromValue(listDpt));
		} else {
			return ServerResponse.notFound()
					.build();
		}
	}
}

The constants used to indicate the route are defined in the AppConstant class.


KeyAccessRestApi project/AppConstant.java
public final class AppConstant {
	public static final String REST_ROOT = "/rest";
	public static final String API_DPT_FINDALL = "/findalldpts";
}


Service


As we don’t have more business logics included in this server app, the service bean, supposed to implement them in a multi-layer structure, simply functions like a wrapper to access the DAO.


KeyAccessRestApi project/DptService.java
@Service
public class DptService{
	@Autowired
	DptDao dptDao;
	
	public List<DptDto> findAllDpts() {
		List<DptDto> dptlist = dptDao.findAllDpts();		
		return dptlist;
	}
}


DAO


This is the actual interface to interact with the database. Typically, we instantiate a JDBC template, and then use it to execute the SQL query and to save the result dataset to our POJOs, shown in the following code.


KeyAccessRestApi project/DptDaoImpl.java
@Repository
public class DptDaoImpl implements DptDao {

	@Autowired
	JdbcTemplate jdbcTemplate;

	@Override
	public List<DptDto> findAllDpts(){	
		String sql = "SELECT department_id, department_name, manager_id "
				+ "FROM departments "
				+ "ORDER BY department_id";
	    	
		List<DptDto> dptlist = null;
		try {
			dptlist = jdbcTemplate.query(sql,
					(rs, rowNum) -> new DptDto(
							rs.getLong("department_id"), 
							rs.getString("department_name"), 
							rs.getInt("manager_id"))
			);
		} catch (Exception e) {
			System.out.println(e);
		}
		
		return dptlist;
	}
}

You can also refer to the previous blog for how to build WebFlux based REST APIs,  

Note that  the data sources are manually configured in that project.



Generate a Secret Key


We are going to make the key generation as a public service, by declaring an API as follows:

HTTP Method: GET

Path: /apputil/genkey (AppConstant.APPUTIL_ROOT + AppConstant.API_GEN_KEY)

Handler Function: generateAesKey

So, we can access the endpoint to get a key either using a web browser or issuing a CURL command.


CURL -X GET http://localhost:9090/apputil/genkey

Class KeyGenerator is used to generate secret keys. It supports algorithms such as “AES”, “DES”, and “DESede”. “AES” is the one that we chose for this demo. For the implementation, refer to the handler, generateAesKey.

This is a part of our system utilities, meaning it is separate from the app’s business functions. Therefore, we create another router for it, which is AppUtilRouter.


KeyAccessRestApi project/AppUtilRouter.java
@Configuration(proxyBeanMethods = false)
public class AppUtilRouter {
	@Bean(name = "apputilroute")
	public RouterFunction<ServerResponse> route() {
		return RouterFunctions
		.route(GET(AppConstant.APPUTIL_ROOT+AppConstant.API_GEN_KEY)
		.and(accept(MediaType.APPLICATION_JSON)), this::generateAesKey);
	}

	public Mono<ServerResponse> generateAesKey(ServerRequest serverRequest) {
		SecretKey  apiKey = null;
		
		try {
			KeyGenerator keyGen = KeyGenerator.getInstance("AES");
			keyGen.init(256); // 256-bit AES key
			apiKey = keyGen.generateKey();
		} catch (Exception ex) {
			System.out.println(ex.getMessage());
		}
		
		return ServerResponse.ok()
		    .contentType(MediaType.APPLICATION_JSON)
		    .bodyValue(apiKey==null? "failed to generate an access key.":apiKey);
	}
}


Manage the Keys in a Database Table


It is common that we manage the keys in a safe place. At the same time, the feasibility to access them is another factor that we need to consider. Database is a suitable choice for meeting these requirements.

Accordingly, we define a DAO to fetch all the keys from the table in the database.

Here are the keys in the App_ApiKey table.



Below is the DAO definition.


KeyAccessRestApi project/ApiKeyDaoImpl.java
@Repository
public class ApiKeyDaoImpl implements ApiKeyDao {
	@Autowired
	JdbcTemplate jdbcTemplate;
	
	@Override
	public List<String> getApiKeys() {
		String sql = "SELECT key, username, description "
				+ "FROM app_apikey "
				+ "ORDER BY username";
	    	
		List<ApiKeyDto> keylist = null;
		try {
			keylist = jdbcTemplate.query(sql,
					(rs, rowNum) -> new ApiKeyDto(
							rs.getString("key"),
							rs.getString("username"),
							rs.getString("description"))
			);
		} catch (Exception e) {
			System.out.println(e);
		}
		
		List<String> validKeys = new ArrayList<String>();
		if (keylist != null && !keylist.isEmpty()) {
			keylist.forEach(key -> {
				validKeys.add(key.getKey());
			});
		}
		return validKeys;
	}
}


Add a Custom Filter to Authenticate the Key


Let’s first think about what the filter needs to do in this authentication scenario. It can be summarized as follows.

Step 1: Extract the secret key from the header of a request.

Step 2: Check if the extracted key is valid.

Step 3: If valid, authorize the access; if not, decline it.


Step 1 is implemented in serverAuthenticationConverter. Step 2 and 3 go to reactiveAuthenticationManager.

Then, we create a web filter of type AuthenticationWebFilter to wrap the above beans, which is authenticationWebFilter.

Finally, we add this custom filter to our ServerHttpSecurity by using the addFilterAt method. So that every incoming request has to go through it first. 


KeyAccessRestApi project/SecurityConfig.java
@Configuration(proxyBeanMethods = false)
@EnableWebFluxSecurity
public class SecurityConfig {
	@Bean
	public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
		AuthenticationWebFilter authenticationWebFilter = 
			new AuthenticationWebFilter(reactiveAuthenticationManager());
		authenticationWebFilter.setServerAuthenticationConverter(
			serverAuthenticationConverter());
        
		// Disable CSRF for API key based auth
		http.csrf(ServerHttpSecurity.CsrfSpec::disable);
		http.authorizeExchange(exchanges -> exchanges
		    // App Utility endpoint
		    .pathMatchers(AppConstant.APPUTIL_ROOT + "/**").permitAll()
		    // All other endpoints require authentication
		    .anyExchange().authenticated());
		http.addFilterAt(authenticationWebFilter,
			SecurityWebFiltersOrder.AUTHENTICATION)
		    .httpBasic(ServerHttpSecurity.HttpBasicSpec::disable)
		    .formLogin(ServerHttpSecurity.FormLoginSpec::disable);
		
		return http.build();
    }

	@Bean
	public ReactiveAuthenticationManager reactiveAuthenticationManager() {
    		//
    		// Get the valid keys from the database
    		List<String> validApiKeys = apiKeyDao.getApiKeys();
    		
    		return authentication -> {
    			String apiKey = authentication.getCredentials().toString();
    			if (validApiKeys.contains(apiKey)) {
    				return Mono.just(
					new UsernamePasswordAuthenticationToken(apiKey, 
					  apiKey,
					  Collections.singletonList(
					  new SimpleGrantedAuthority("ROLE_USER"))));
    			}
    			return Mono.error(new BadCredentialsException("Invalid API Key"));
    		};
	}

	@Bean
	public ServerAuthenticationConverter serverAuthenticationConverter() {
	return exchange -> 
		Mono.justOrEmpty(
		  exchange.getRequest().getHeaders().getFirst(AppConstant.API_KEY_HEADER))
		.map(apiKey -> new UsernamePasswordAuthenticationToken(apiKey, apiKey));
	}
}


Don’t Forget CORS Configuration


Cross-Origin Resource Sharing (CORS) is a browser security mechanism that controls resource access crossing separate domains. To be more specific, if we’d like to enable the server to accept requests from another domain, by default this is not allowed, we’ll need to change the CORS setting in the server to grant the permission to that domain.

This is a concern that we should address in our demo, meaning we need to get the WebFlux app configured properly in order to accept and process requests from the React client. 

To do so, we create a CorsConfigurationSource bean and then add the client’s origin, "http://localhost:3000", to the allowed list, as depicted below.


KeyAccessRestApi project/SecurityConfig.java
@Configuration(proxyBeanMethods = false)
@EnableWebFluxSecurity
public class SecurityConfig {

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedOrigin("http://localhost:3000"); // "*": Allow all origins
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/**", config);
        return source;
    }
}


Create a React App


Create React App has been officially deprecated as of Feb. 2025. The React team recommends us to build a new React app with more sophisticated frameworks such as Next.js, React Router, Expo etc. 

We are going to create one based on Next.js. We can do this by using the “create-next-app” command.


npx create-next-app@latest reactclient

After the creation, we add a few files to the project.

  • department.tsx – Defines the Department component, indeed a React function.

  • apidpt.ts – Defgnes the axios APIs that call the REST APIs.

  • constant.js – Defines the constants used in the app.



Build an Axios API to Consume the WebFlux REST API


ApiFindAllDpts is the axios API that retrieves the dataset of all departments.

Frist, we call getAxios to create an axios instance which has the key embedded in its header.

Then, we use the instance’s get method to access the endpoint in the backend app.


reactclient project/apidpt.ts
export function ApiFindAllDpts() {
  const axiosInstance = getAxios();
  return axiosInstance.get(PathFindAllDpts);
}

/*
 * Create an Axios instance
 */
function getAxios(){
  return axios.create({
      baseURL: RestEndpoint,
      headers: {
          "Api-Key": ApiAccessKey,
      },
  });
}


Below is the definition of the constant variables, RestEndpoint and ApiAccessKey and PathFindAllDpts.


reactclient project/constant.ts
/* Root endpoint of the REST APIs */
export const RestEndpoint = 'http://localhost:9090/rest';
export const ApiAccessKey = "XvcItqrOmhPb2gxalIiGWhe7kOQ5cT6E5ZpUpYLM8RU="
export const PathFindAllDpts = '/findalldpts';


Wrap the Data Fetch Operation in a useEffect Hook


Fetching data from an external source is what useEffect is designed for. A classical use case.

ApiFindAllDpts calls the backend REST API and returns a dataset of departments. Each department object contains department ID, department name and manager ID fields.

Considering the editing features that we’ll implement, we add the following additional fields to each department item (Refer to transformDptList).

  • checked: Indicates whether the item is selected. It is bound with the Checkbox.

  • updated: Indicated whether the item’s department name or manager ID has been edited.

  • id: Identifier of the item.

And we declare a state variable dptList to keep the list of these adjusted items.


reactclient project/department.tsx
export default function Department(){	
  const [dptList, setDptList] = React.useState(null);
  …
  /*
   * Get all the departments from the backend
   */
  React.useEffect( () => {
    let ignore = false;
    ApiFindAllDpts()
      .then(response => {
        if (!ignore) {
          transformDptList(response.data);
        }
      })
      .catch(error =>{
        console.error(error);
      });

    return () => {
      ignore = true;
    }
  }, []);

  /*
   * Add the additional fields to each department object
   */
  const transformDptList = (dptlist) => {
    setDptList(dptlist.map((dpt) => 
      dpt={...dpt, id:dpt.department_id, checked:false, updated:false}));
  };
  …
}


Present the List of Departments


This is the markup part of the Department component, which is rendered on the page by React. 

A table is used to display the entire list of departments. We use map function to iterate the items. Each of them shows up on the page with four columns:

  • Checkbox
  • Department ID
  • Department Name
  • Manger ID

Both Department Name and Manager ID are presented with “input” elements. When the Checkbox is selected, those elements become editable. So, we can update the selected department accordingly. 


reactclient project/department.tsx
export default function Department(){
  const [dptList, setDptList] = React.useState(null);
        …
        <tbody>
          {
            dptList && dptList.map((dpt, index) => (
              <tr className="contents" key={index}>
                {/* Checkbox Column */}
                <td className="contents" >
                  <input type="checkbox"
                    id={dpt.id}
                    checked={dpt.checked}
                    onChange={() => handleCheckboxChange(dpt.id)}/>
                </td>
                {/* Department Id Column */}
                <td className="contents" >{dpt.department_id}</td>
                {/* Department Name Column */}
                <td className="contents" >
                  <input type="text"
                    readOnly={!dpt.checked}
                    name="department_name"
                    value={dpt.department_name}
                    onChange={(e)=>onUpdateChange(e, dpt.department_id)} />
                </td>
                {/* Manager Id Column */}
                <td className="contents" >
                  <input type="number"
                    readOnly={!dpt.checked}
                    name="manager_id"
                    value={dpt.manager_id}
                    onChange={(e)=>onUpdateChange(e, dpt.department_id)} />
                </td>
              </tr>
              )
            )
          }
        </tbody>
        …
}


Try the Full Set of CRUD Operations


We can incorporate more functions, the likes of creation and update and deletion, into the app. To do so, we  add the corresponding routes and handlers to the router class.

Here is what the class looks like.


KeyAccessRestApi project/DptRouter.java
@Configuration(proxyBeanMethods = false)
public class DptRouter {
	@Bean(name = "dptroute")
	public RouterFunction<ServerResponse> route() {
	    return RouterFunctions
	      .route(GET(AppConstant.REST_ROOT + AppConstant.API_DPT_FINDALL)
	    		  .and(accept(MediaType.APPLICATION_JSON)), 
	    		  this::findAllDpts)
	      .andRoute(GET(AppConstant.REST_ROOT + AppConstant.API_DPT_FINDBYID), 
	    		  this::findById)
	      .andRoute(POST(AppConstant.REST_ROOT + AppConstant.API_DPT_INSERT), 
	    		  this::insertDpt)
	      .andRoute(PUT(AppConstant.REST_ROOT + AppConstant.API_DPT_UPDATE), 
	    		  this::updateDpt)
	      .andRoute(DELETE(AppConstant.REST_ROOT + AppConstant.API_DPT_DELETE), 
	    		  this::deleteDpt);
	  }
	@Autowired
	DptService dptService;
	
	public Mono<ServerResponse> findAllDpts(ServerRequest request) {
		…
	}
	
	public Mono<ServerResponse> findById(ServerRequest request){
		…
	}

	public Mono<ServerResponse> insertDpt(ServerRequest request){
		…
	}
	
	public Mono<ServerResponse> updateDpt(ServerRequest request){
		…
	}
	
	public Mono<ServerResponse> deleteDpt(ServerRequest request){
		…
	}
}

In the React client, we declare the axios APIs that access those CRUD endpoints, shown in the following code block.


reactclient project/dptapi.ts
export function ApiFindDptById(department_id) {
  const axiosInstance = getAxios();
  const apipath = PathFindDptById + '/' + department_id;
  return axiosInstance.get(apipath);
}

export function ApiInsertDpt(dpt) {
  const axiosInstance = getAxios();
  console.log(">> axios instance: ", axiosInstance);
  return axiosInstance.post(PathInsertDpt, dpt);
}

export function ApiUpdateDpt(dpt) {
  const axiosInstance = getAxios();
  console.log(">> axios instance: ", axiosInstance);
  return axiosInstance.put(PathUpdateDpt, dpt);
}

export function ApiDeleteDpt(department_id) {
  const axiosInstance = getAxios();
  const apipath = PathDeleteDpt + '/' + department_id;
  return axiosInstance.delete(apipath);
}


reactclient project/constant.ts
/* root endpoint of the rest apis */
export const RestEndpoint = 'http://localhost:9090/rest';
export const ApiAccessKey = "XvcItqrOmhPb2gxalIiGWhe7kOQ5cT6E5ZpUpYLM8RU="

export const PathFindAllDpts = '/findalldpts';
export const PathFindDptById = '/finddptbyid';
export const PathInsertDpt = '/insertdpt';
export const PathUpdateDpt = '/updatedpt';
export const PathDeleteDpt = '/deletedpt';

Surely, we need to update the Department component as well; but let’s focus on what we get on the GUI here. 

How the events are processed, how the state variables are handled, refer to department.tsx for more details.



Create a department


Click Create button -> Fill out the input boxes -> Click Submit button.




Search for a department


Enter a department ID -> Click Search button.



Update a department


Check a checkbox to select a department -> Modify its department name or manager ID or both -> Click Update button.




Delete a department


Check a checkbox to select a department -> Click Delete button.




How about the Traffic?


As you might have noticed, the secret key is exposed on the internet. This is a big blow from the perspective of security. To reinforce it and come up with a comprehensive solution, we have to secure the traffic across networks. 

To do so, we can introduce HTTPS to the backend app and only allow the access through HTTPS to the endpoints.



Recap


Leveraging React and Java-based REST API seems to be a practicable technical solution which  can meet various needs in an enterprise context. Needless to say, an authentication mechanism is required to be in place. API key authentication is one of the options. To implement it:

  • On the client side, we embed the key into the request header;

  • On the server side, we build a custom web filter which extracts and validates the key.

However, to make it a sophisticated solution, we’ll need to encrypt the traffic between networks as well.


[Tips] Spring Boot - React Calls REST APIs Built on WebFlux with API Key Authentication

Last updated on:   When building a web app, we’d have a variety of considerations. Functions, along with operability, are what we mu...