I've recently added Spring security to my project ( react apollo graphQl / springboot ) , and it seems like I cant send headers ?? I assume It's CORS.
my code REACT using apollo-client :
const httpLink = new HttpLink({ uri: 'http://localhost:8080/graphql' });
const authLink = new ApolloLink((operation, forward) => {
operation.setContext({
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
});
return forward(operation);
});
export const clientBackEnd = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
});
spring security config :
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable()
.authorizeRequests().antMatchers("/authenticate").permitAll().
anyRequest().authenticated().and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
Cors config :
public class CorsConfig {
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/graphql").allowedOrigins("http://localhost:3000", "http://localhost:3001");
}
};
}
}
devtools:
it looks like the headers are not even sent !?
can some figure out this issue! What am I doing wrong ?
As discussed on further comments the issue was based of whitelisting the origin where the request is generating.
So the issue is basically that - We need to allow origins in server side by whitelisting the domains where the request will be coming or originating
So here client runs in 3000 so when it is asking for data we need to whitelist that port and give the response back
for whitelisting and avoiding the cors issue refer here
Related
In my server-side application, I have enabled CORS globally like this.
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("GET", "POST", "PUT", "DELETE", "HEAD", "PATCH")
.allowedHeaders("*")
.allowedOrigins("*");
}
};
}
My client-side application running in react and in useEffect hook,s I'm calling a backend API like this.
useEffect(() => {
async function fetchData() {
const dataArray = await axios({
url: "http://localhost:9001/getAllCompany/7",
method: "GET",
headers: { 'Authorization': "Bearer " + authctx.token },
});
setCompany(dataArray.data);
return dataArray;
}
fetchData();
}, [authctx.token]);
Although I have enabled cors the is API calls not working and network call shows like
How can I fix this issue?
There are some situations when we are dealing with spring security. At that point we have to add http.cors(); inside the security configuration class.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors();
http.csrf().disable()
.authorizeRequests().antMatchers("/user").hasRole("USER")
.antMatchers("/admin").hasAnyRole("ADMIN")
.antMatchers("/company/login", "/register","/subscribe/{userId}", "/addDataRequest/{userId}").permitAll().anyRequest().authenticated()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).
and().addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
}
This works for me.
I guess you have forget to add OPTIONS in the allowedMethods block. This is the reason your pre-flight request from browser is failing and it's not working as expected.
Refer my old answer: https://stackoverflow.com/a/63202865/7200018
I have a ReactJS and Java Spring Boot applications, both secured by Keycloak 11.0.2.
Keycloak is on port 8083, ReactJS on 3000 and Spring App is on 8085.
If I try to use the configuration provided below, I'm not able to hit my endpoint and I'm getting CORS error.
Firefox:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:8083/auth/realms/sorcerer_realm/protocol/openid-connect/auth?response_type=code&client_id=event_sorcerer&redirect_uri=http%3A%2F%2Flocalhost%3A8085%2Fsso%2Flogin&state=f52216b1-c235-4328-a2f9-d8448c3bf886&login=true&scope=openid. (Reason: CORS request did not succeed).
Chrome and Microsoft Edge:
Access to XMLHttpRequest at 'http://localhost:8083/auth/realms/sorcerer_realm/protocol/openid-connect/auth?response_type=code&client_id=event_sorcerer&redirect_uri=http%3A%2F%2Flocalhost%3A8085%2Fsso%2Flogin&state=f57ffa9f-9679-4476-aa03-af86c3abb3c2&login=true&scope=openid' (redirected from 'http://localhost:8085/api/worker/create/product') from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
xhr.js:184 GET http://localhost:8083/auth/realms/sorcerer_realm/protocol/openid-connect/auth?response_type=code&client_id=event_sorcerer&redirect_uri=http%3A%2F%2Flocalhost%3A8085%2Fsso%2Flogin&state=f57ffa9f-9679-4476-aa03-af86c3abb3c2&login=true&scope=openid net::ERR_FAILED
When I try to hit my endpoint using Postman, I'm able to hit it. Below is my Keycloak Web Security configuration. The configuration uses application.properties file to configure Keycloak adapter.
When I set .authorizeRequests().antMatchers("/**").permitAll() in the config, I'm also able to hit my endpoint from browser and Postman.
#KeycloakConfiguration
public class SecurityConfiguration extends KeycloakWebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder authBuilder) throws Exception {
final KeycloakAuthenticationProvider authProvider = keycloakAuthenticationProvider();
authProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
authBuilder.authenticationProvider(authProvider);
}
/**
* Call superclass configure method and set the Keycloak configuration
*/
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.csrf().disable()
.cors()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().anonymous()
//.and().authorizeRequests().antMatchers("/**").permitAll() //Uncomment for requests to be allowed!
.and().authorizeRequests().antMatchers("/api/admin/**").hasRole("ADMIN")
.and().authorizeRequests().antMatchers("/api/manager/**").hasAnyRole("MANAGER")
.and().authorizeRequests().antMatchers("/api/worker/**").hasRole("WORKER")
.anyRequest().authenticated();
}
/**
* Setup Auth Strategy. Don't add prefixes and suffixes to role strings
*/
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
/**
* Don't use keycloak.json. Instead, use application.yml properties.
* #return
*/
#Bean
public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
}
Here is a part of application.properties that sets up Keycloak:
spring:
jersey:
type: filter
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://localhost:8083/auth/realms/sorcerer_realm/protocol/openid-connect/token
jwk-set-uri: http://localhost:8083/auth/realms/sorcerer_realm/protocol/openid-connect/certs
keycloak:
realm: sorcerer_realm
auth-server-url: http://localhost:8083/auth/
ssl-required: external
resource: event_sorcerer
verify-token-audience: true
credentials:
secret-jwt:
secret: d84611c9-af79-423b-b12c-bfa7fec23e85
use-resource-role-mappings: true
confidential-port: 0
Here is my ReactJS application's Keycloak adapter setup:
const keycloakConfig = {
"clientId": "event_sorcerer_frontend",
"realm": "sorcerer_realm",
"auth-server-url": "http://localhost:8083/auth/",
"url": "http://localhost:8083/auth",
"ssl-required": "external",
"resource": "event_sorcerer",
"public-client": true,
"verify-token-audience": true,
"use-resource-role-mappings": true,
"confidential-port": 0
};
const keycloak = new Keycloak(keycloakConfig);
const initKeycloak = (onSuccessCallback, onFailureCallback) => {
let success = false;
timeoutWrapper(() => {
if(!success){
onFailureCallback();
}
});
keycloak.init({
onLoad: 'check-sso',
silentCheckSsoRedirectUri: window.location.origin + '/silent-check-sso.html',
pkceMethod: 'S256',
}).then((isAuthenticated) => {
success = true;
if(isAuthenticated) {
onSuccessCallback();
} else {
login();
}
});
}
Here is how I send the request to server:
export const Request = {
configureAxiosDefault: () => {
axios.defaults.baseURL = axiosDefaultConfiguration.baseUrl;
},
create: (data, endpoint, callback, errorCallback, finalCallback) => {
axios.post(serverEndpoint + endpoint, {
data: data,
headers: {
Authorization: `Bearer ${UserService.getToken()}`
}
})
.then(response => Utility.isEmpty(callback) ? defaultCallback(response) : callback(response))
.catch(response => Utility.isEmpty(errorCallback) ? defaultErrorCallback(response) : errorCallback(response))
.finally(response => {
if(!Utility.isEmpty(finalCallback)) {
finalCallback(response);
}
});
},
}
Here is my Keycloak configuration for frontend. Backend is the same, except the Access Type is confidential and the Root/Base url are different (not 3000 but 8085):
Here is my CORS configuration bean:
#Configuration
public class CORSConfiguration {
/**
* Setup CORS
* #return
*/
#Bean
public CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.setAllowedOrigins(Arrays.asList("http://localhost:3000"));
config.setAllowedMethods(Arrays.asList(CorsConfiguration.ALL));
config.setAllowedHeaders(Arrays.asList(CorsConfiguration.ALL));
config.setAllowCredentials(true);
source.registerCorsConfiguration("/**", config);
return source;
}
}
And lastly, here is my endpoint. URL resolves to api/worker/create/product
#RestController
#RequestMapping(ControllerEndpointsPrefix.WORKER + "/create")
public class CreationController {
#Autowired
private UserAgregate userAgregate;
#PostMapping("/product")
public boolean createProduct(#RequestBody CreateProductCommand command) {
return true;
}
}
I've managed to solve this.
The problem wasn't on the server side, but on client side.
configureAxiosDefault: () => {
axios.defaults.baseURL = axiosDefaultConfiguration.baseUrl;
axios.defaults.headers.Authorization = `Bearer ${UserService.getToken()}`
},
create: (data, endpoint, callback, errorCallback, finalCallback) => {
axios.post(serverEndpoint + endpoint, data)
.then(response => Utility.isEmpty(callback) ? defaultCallback(response) : callback(response))
.catch(response => Utility.isEmpty(errorCallback) ? defaultErrorCallback(response) : errorCallback(response))
.finally(response => {
if(!Utility.isEmpty(finalCallback)) {
finalCallback(response);
}
});
},
Server was unable to process the token, because I was sending it as a JSON object property. These changes made everything work OK.
So, CORS wasn't an issue at all. The issue was that request didn't contain an Authorization header.
There are a lot of StackOverflow questions regarding KeyCloak, and some of them incomplete and cryptic. I encountered a good amount of errors, because of OpenJDK, JDK versions etc.
If anyone needs explanations and solutions, working Spring Boot configuration is on my repository:
https://github.com/milosrs/EventSorcererBackend
Context
Spring Boot
React Js
Issue
I want to make a login request from react to get the jsessionid. I am getting a weird response from Spring Boot. In the response i don't find any cookies. In postman i can just give the username and password in the url as parameters and in the response I am getting a response with the cookie jsessionid and for more requests I can just use it. But in react I am getting a weird response and I don't know how to get the cookie.
Here is the code that sends the request from React JS to Spring Boot:
const { username, password } = this.state;
const student = { username, password };
fetch("http://localhost:8080/login", {
method: "POST",
body: new URLSearchParams(student)
})
.then(res => {
console.log(res);
const jsessionid = document.cookie;
console.log("id", jsessionid);
//Here I am trying to get the jsessionid
})
.catch(error => console.log(error));
This is the response that I am getting and that I printed out in the console
And here is my Spring Securtiy Configuration Class:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
#Autowired
UserDetailsServiceImpl userDetailsService;
#Bean
DaoAuthenticationProvider authenticationProvider(){
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
daoAuthenticationProvider.setUserDetailsService(userDetailsService);
return daoAuthenticationProvider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable().cors().and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("*"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
Here I tried with curl and as you can see I am getting the cookie jsessionid .
You can access your jsessionid from response.header
for example
fetch("http://localhost:8080/login", {
method: "POST",
body: new URLSearchParams(student)
credentials: 'include',
headers: {
'Content-Type': 'application/json'
}
})
.then(res=>console.log(res.headers.get('set-cookie'));)
.catch(error => console.log(error));
I have easy mail sending springboot API and reactjs app for fetch.
Hosted in nginx server.
React app running well localhost, email working everything okei but if I run on a server it gives an error.
I have tried to add different Nginx server settings and add Springboot
#CrossOrigin(origins = "http://xx.xxx.xxx.xxx/") but have not found any help so far.
Where do I have to allow CORS if I deploy to server?
Nginx server default:
server {
listen 80 default_server;
root /var/www/name/build;
server_name xx.xxx.xxx.xxx;
index index.html index.htm;
location / {
}
}
Springboot:
#PostMapping
public void sendFeedback(#RequestBody Feedback feedback,
BindingResult bindingResult){
if(bindingResult.hasErrors()){
throw new ValidationException("Feedback is not valid");
}
// Create a mail sender
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
Properties props = mailSender.getJavaMailProperties();
mailSender.setHost(this.emailCfg.getHost());
mailSender.setPort(this.emailCfg.getPort());
mailSender.setUsername(this.emailCfg.getUsername());
mailSender.setPassword(this.emailCfg.getPassword());
// Create an email instance
SimpleMailMessage mailMessage = new SimpleMailMessage();
mailMessage.setFrom(feedback.getEmail());
mailMessage.setTo("test#gmail.com");
mailMessage.setSubject("Testing mail");
mailMessage.setText(feedback.getFeedback());
mailSender.send(mailMessage);
}
Reactjs code I have:
fetch(url,{
method: 'POST',
headers:{
'Content-Type': 'application/json'
},
body: JSON.stringify(state)
}).then(response =>{
console.log(data)
}).catch(error =>{
console.log(error)
})
Output:
Access to fetch at 'http://xx.xxx.xxx.xxx:8080/feedback-0.0.1-SNAPSHOT' from
origin 'http://xx.xxx.xxx.xxx' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check:
Redirect is not allowed for a preflight request.
Add a CORS config as below :
CORSConfig.java
#Configuration
public class CORSConfig implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "HEAD");
}
}
OR
Annonate your controller
#CrossOrigin
#PostMapping
public void sendFeedback(#RequestBody Feedback feedback,
BindingResult bindingResult){
if(bindingResult.hasErrors()){
throw new ValidationException("Feedback is not valid");
}
// Create a mail sender
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
Properties props = mailSender.getJavaMailProperties();
mailSender.setHost(this.emailCfg.getHost());
mailSender.setPort(this.emailCfg.getPort());
mailSender.setUsername(this.emailCfg.getUsername());
mailSender.setPassword(this.emailCfg.getPassword());
// Create an email instance
SimpleMailMessage mailMessage = new SimpleMailMessage();
mailMessage.setFrom(feedback.getEmail());
mailMessage.setTo("test#gmail.com");
mailMessage.setSubject("Testing mail");
mailMessage.setText(feedback.getFeedback());
mailSender.send(mailMessage);
}
This is caused by your server not responding with the proper headers. If you look at your network tab you will see and OPTIONS request that fires before your POST request. Your server needs to respond to that OPTIONS request to let the browser know its ok for the requesting client to access the resource. You can read more about it here at MDN but at the very least your server should respond to the preflight with headers like:
Access-Control-Allow-Origin: http://foo.example <== your domain here
Access-Control-Allow-Methods: POST, OPTIONS
Access-Control-Allow-Headers: Content-Type
#CrossOrigin(origins="http://localhost:9000")
#GetMapping("/hello")
public Greeting greeting() {
return "world";
}
or
#CrossOrigin(origins="http://localhost:9000", maxAge=3600)
#RestController
public class RestController {}
I have some issues with CORS using React fetch and Spring Boot.
The React application runs on localhost:3000.
The Spring Boot backend runs on localhost:3001.
My problem is when I try to logging in using using fetch with the http://localhost:3001/login url the response in javascript does not contain the Authorization token.
The authentication on backend side works.
When I open the Chrome Inspector I can see the Authorization in the Network tab at the login request only it is missing in the javascript response.
The React fetch request look like the following: In the code the const userToken = response.headers.get('Authorization'); returns "null" string instead of the token.
return fetch("http://localhost:3001/login",{
method: 'post',
headers: {
"Accept": "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify({
email,
password
})
})
.then(
response => {
if(response.ok) {
const userToken = response.headers.get('Authorization');
return true;
}
// Error handling
}
);
The Spring Boot Security config is like the following:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf()
.disable().authorizeRequests()
.antMatchers(HttpMethod.POST, REGISTRATION_URL).permitAll()
.anyRequest().authenticated()
.and()
.addFilter(// Auth Filter)
.addFilter(// Another auth Filter)
// this disables session creation on Spring Security
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
}
#Bean
public CorsConfigurationSource corsConfigurationSource() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
}
Another thing. When I used proxy: "http://127.0.0.1:3001" in the package.json the login worked and the React code above could read Authorization header. But I don't want to use proxy.