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));
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 am trying to send user login credentials from Reactjs as Json using Axios to spring boot application: however, it is not working. But when I tried it in postman it work find and even generated a toke and send it back.
Below is a test react code I am using. I have use both method and none work
const loginInfo = {
"username": "root",
"password": 'password'
};
class EmployeeAuthenticate {
employeeLogin(username, password) {
return axios
.post(`http://localhost:8080/login`, JSON.stringify(loginInfo),
{ headers: {'Content-Type': 'application/json'}
}
).then(res => {
console.log(res)
}).catch(error => console.log(error))
}
}
employeeLogin(username, password) {
return axios
.post(`http://localhost:8080/login`, {
"username": "root",
"password": "password"
},
{ headers: {'Content-Type': 'application/json'}
}
).then(res => {
console.log(res)
}).catch(error => console.log(error))
}
Below is the method I am trying to send the post request to.
public class JwtUsernameAndPasswordAuthFilter extends AbstractAuthenticationProcessingFilter {
private final AuthenticationManager authenticationManager;
private final ApplicationJwtSecurityKey securityKey;
public JwtUsernameAndPasswordAuthFilter(String url, AuthenticationManager authenticationManager,
ApplicationJwtSecurityKey securityKey) {
super(new AntPathRequestMatcher(url));
this.authenticationManager = authenticationManager;
this.securityKey = securityKey;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
try {
System.out.println("User name is " + request.getParameter("username") + " Param name ");
EmployeeAuthenticate employeeAuthenticateRequest = new ObjectMapper().readValue(request.getInputStream(), EmployeeAuthenticate.class);
Authentication authentication = new UsernamePasswordAuthenticationToken(
employeeAuthenticateRequest.getUsername(),
employeeAuthenticateRequest.getPassword()
);
// Will check if the username exist and if it exist then it will check if the password is correct
return authenticationManager.authenticate(authentication);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Below is the Security configuration in WebSecurityConfig Class
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(new JwtUsernameAndPasswordAuthFilter("/login", authenticationManager(), securityKey), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/employee-management/**").hasAnyRole(ROLE_ADMIN.name())
.anyRequest()
.authenticated();
}
This is the errors message I keep getting
java.lang.RuntimeException: com.fasterxml.jackson.databind.exc.MismatchedInputException: No content to map due to end-of-input
at [Source: (org.apache.catalina.connector.CoyoteInputStream); line: 1, column: 0]
I debug it in chrome when I send the request and this is the result
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
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.
When I am trying to authenticate an user from AngularJS, I am seeing this warning in Spring Boot log:
[WARN ] 2017-02-04 17:09:20.085 [http-nio-8080-exec-1] DefaultHandlerExceptionResolver - Resolved exception caused by Handler execution: org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'null' not supported
And the browser response is:
415 Unsupported Media Type
My LoginController:
#RestController
// #RequestMapping("/")
public class LoginController {
public Logger logger = LoggerFactory.getLogger(this.getClass());
#RequestMapping(value = "/login", method = RequestMethod.GET,
consumes = MediaType.APPLICATION_JSON_VALUE
/*produces = MediaType.APPLICATION_JSON_VALUE*/)
public ResponseEntity<Admin> login(#RequestBody UserDTO user, BindingResult result, WebRequest request) {
logger.info("********** Inside login of LoginController **************");
Admin authenticatedUser = (Admin) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
HttpStatus httpStatus = null;
if (authenticatedUser == null) {
httpStatus = HttpStatus.NOT_FOUND;
} else {
httpStatus = HttpStatus.OK;
}
return new ResponseEntity<Admin>(authenticatedUser, httpStatus);
}
}
My AngularJS code:
service.login = function(user, successHandler, errorHandler) {
// Obtain a CSRF token
loginResources.options().$promise.then(function (response) {
console.log('Obtained a CSRF token in a cookie', response);
// Extract the CSRF token
var csrfToken = Cookies.getFromDocument($http.defaults.xsrfCookieName);
console.log('Extracted the CSRF token from the cookie', csrfToken);
// Prepare the headers
var headers = {
'Content-Type': 'application/json'
};
headers[$http.defaults.xsrfHeaderName] = csrfToken;
console.log("Before calling /login, user : ", user);
// Post the credentials for logging in
$http.get(ApiBasePath + '/login', user, {headers: headers})
.success(successHandler)
.error(function (data, status, headers, config) {
if (isCSRFTokenInvalidOrMissing(data, status)) {
console.error('The obtained CSRF token was either missing or invalid. Have you turned on your cookies?');
} else {
// Nope, the error is due to something else. Run the error handler...
errorHandler(data, status, headers, config);
}
});
}).catch(function(response) {
console.error('Could not contact the server... is it online? Are we?', response);
});
};//login function ends
I have an exactly same registration controller with an exactly same AngularJS register function (with different endpoint of course), but that works perfectly.
I doubt one thing though, when I am using Spring Security, do I really need the LoginController with the endpoint /login or the security configuration will take care of that? My security config:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/*/**").permitAll()
.antMatchers("/login").permitAll()
.antMatchers("/register").permitAll()
.antMatchers("/", "/**/*.css", "/**/**/*,css",
"/**/*.js", "/**/**/*.js").permitAll()
.antMatchers("/dashboard", "/dasboard/**", "/logout").authenticated();
// Handlers and entry points
http
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
http
.formLogin()
.successHandler(authenticationSuccessHandler);
http
.formLogin()
.failureHandler(authenticationFailureHandler);
// Logout
http
.logout()
.logoutUrl("/logout")
.logoutSuccessHandler(logoutSuccessHandler);
// CORS
http
.addFilterBefore(corsFilter, ChannelProcessingFilter.class);
// CSRF
http
.csrf().requireCsrfProtectionMatcher(
new AndRequestMatcher(
// Apply CSRF protection to all paths that do NOT match the ones below
new NegatedRequestMatcher(new AntPathRequestMatcher("/", HttpMethod.OPTIONS.toString())),
new NegatedRequestMatcher(new AntPathRequestMatcher("/", HttpMethod.GET.toString())),
new NegatedRequestMatcher(new AntPathRequestMatcher("/", HttpMethod.POST.toString())),
new NegatedRequestMatcher(new AntPathRequestMatcher("/", HttpMethod.HEAD.toString())),
new NegatedRequestMatcher(new AntPathRequestMatcher("/", HttpMethod.TRACE.toString())),
new NegatedRequestMatcher(new AntPathRequestMatcher("/css/**", HttpMethod.GET.toString())),
new NegatedRequestMatcher(new AntPathRequestMatcher("/js/**", HttpMethod.GET.toString())),
new NegatedRequestMatcher(new AntPathRequestMatcher("/js/**/**", HttpMethod.GET.toString())),
// We disable CSRF at login/logout, but only for OPTIONS methods
new NegatedRequestMatcher(new AntPathRequestMatcher("/login*/**", HttpMethod.OPTIONS.toString())),
new NegatedRequestMatcher(new AntPathRequestMatcher("/logout*/**", HttpMethod.OPTIONS.toString())),
//Disable CSRF at register for all methods
new NegatedRequestMatcher(new AntPathRequestMatcher("/register*/**", HttpMethod.OPTIONS.toString()))
)
);
http
.addFilterAfter(new CsrfTokenResponseCookieBindingFilter(), CsrfFilter.class); // CSRF tokens handling
}
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
auth.authenticationProvider(authProvider());
}
#Bean
public DaoAuthenticationProvider authProvider() {
final DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(encoder());
return authProvider;
}
#Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder(11);
}
Finally I got the answer. It is true, if I try to send json object instead of request parameters, that I have to use Custom UserNamePasswordAuthenticationFilter. It is also true, I have to use POST.
Thanks #dur for pointing that.
Finally, a big thanks to this post. Without this post, I wouldn't have possibly find out how to customize the filter.