The issue of missing headers in a Spring Boot server when called from React, despite working in Postman, is almost always caused by the browser’s Cross-Origin Resource Sharing (CORS) policy. Browsers block requests that do not explicitly allow them, and for non-simple requests or those with custom headers, a preflight check occurs. If the server does not correctly respond to this preflight request with appropriate CORS headers, the actual request is never sent, making it appear as if the headers are missing.
Understanding the Browser's CORS Mechanism
When you use Postman, you are bypassing the browser entirely. Postman sends raw HTTP requests without enforcing same-origin policies or checking for CORS permissions. However, modern browsers enforce strict security measures to protect users from malicious scripts. When your React application attempts to make an API call to a different domain, port, or protocol than the one serving the frontend, the browser initiates a two-step process.
First, if the request involves methods other than GET, POST, or HEAD, or includes custom headers like
Authorization or
X-Custom-Header, the browser sends an
OPTIONS request known as a "preflight" request. This preflight asks the server: "Is it okay to send the real request with these specific headers?" The server must respond with specific headers indicating which origins, methods, and headers are allowed. If the server fails to respond correctly to this preflight, the browser cancels the actual request. Consequently, your Spring Boot controller never sees the request, and no headers arrive. This is often misinterpreted as headers being "stripped," but they are simply blocked before transmission.
Configuring CORS in Spring Boot
To resolve this, you must configure your Spring Boot application to handle CORS properly. The most robust way to do this is by creating a configuration class that implements
WebMvcConfigurer. This allows you to define global CORS settings that apply to all endpoints or specific ones.
Create a new class annotated with
@Configuration and implement
WebMvcConfigurer. Override the
addCorsMappings method to specify which origins are allowed. For development, you might allow your local React development server, typically running on port 3000. You should also specify which HTTP methods are permitted, such as
GET,
POST,
PUT, and
DELETE. Crucially, you must list the headers that your React client will send. If you are sending an
Authorization header, it must be explicitly included in the
allowedHeaders list. Without this, the preflight response will reject the custom header, causing the browser to block the request.
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:3000")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.allowCredentials(true);
}
}
Setting
allowedHeaders("*") permits all headers, which is convenient for development but may need tightening for production security. Setting
allowCredentials(true) is essential if your React app uses cookies or authentication tokens, as it tells the browser to include credentials in the cross-origin request. Note that when credentials are allowed, you cannot use
allowedOrigins("*"); you must specify the exact origin.
Verifying Header Transmission in React
Once the server is configured, ensure your React code is actually attaching the headers. Using the native
fetch API, you can pass headers via the
headers option in the request configuration. For example, to send an authorization token, structure your fetch call as follows:
fetch('http://localhost:8080/api/resource', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token
},
body: JSON.stringify(data)
});
If you are using Axios, set the headers globally or per-request using
axios.defaults.headers.common['Authorization'] = token. It is vital to verify that these headers are present in the Network tab of your browser's developer tools. Look for the initial
OPTIONS request. Check its Response Headers. You should see
Access-Control-Allow-Headers containing the names of your custom headers. If they are missing there, the server configuration is incorrect. If they are present but the subsequent
POST request is not sent, check the console for CORS errors.
Common Pitfalls and Debugging Steps
A frequent mistake is assuming that setting
allowedHeaders("*") on the server is sufficient without ensuring the client actually sends the headers. Another pitfall is mismatched origins. Ensure that the origin specified in
allowedOrigins matches exactly what the browser reports, including the scheme (
http vs
https) and port. Sometimes, developers forget that
localhost and
127.0.0.1 are treated as different origins by the browser.
Additionally, check if any intermediate proxies or load balancers are stripping headers. While less common in local development, it is a possibility in production environments. Ensure that your Spring Security configuration, if used, does not interfere with CORS preflight requests. By default, Spring Security might block
OPTIONS requests. You can configure Security to permit all requests for paths matching your CORS mapping, or disable security checks for preflight requests specifically.
For more insights into handling context and configurations in Spring Boot, you might find
Spring Boot Context Comes as Null When Implementing ImportBeanDefinitionRegistrar useful, as it touches on broader configuration challenges. Similarly, understanding how to manage profiles can help isolate environment-specific CORS issues, as discussed in
Excluding Included Profiles in Spring: A Comprehensive Guide.
Frequently Asked Questions
Why does my request work in Postman but fail in the browser?
Postman does not enforce browser security policies like CORS. The browser blocks the request because the server did not explicitly allow the cross-origin request during the preflight phase. This is a security feature, not a bug in your code.
What is a preflight request?
A preflight request is an automatic
OPTIONS request sent by the browser before the actual request. It asks the server if the real request is safe to proceed. If the server responds with incorrect or missing CORS headers, the browser aborts the actual request.
How do I allow custom headers in Spring Boot?
You must include the custom header names in the
allowedHeaders method within your
CorsRegistry configuration. Using
* allows all headers, but specifying them explicitly is better for security in production environments.
Can I use CORS filters instead of WebMvcConfigurer?
Yes, you can use a
FilterRegistrationBean to register a CORS filter. However, using
WebMvcConfigurer is generally preferred in Spring Boot as it integrates more cleanly with the MVC framework and is easier to maintain for most applications.
What if I am using Spring Security?
Spring Security can intercept CORS preflight requests and block them. You need to configure Security to permit
OPTIONS requests or ensure that the CORS configuration is applied before Security checks. This often involves adding a custom filter or adjusting the Security configuration chain.