how to request in react + spring security? - reactjs

I've been troubled for days by the application of spring security...
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.addFilter(corsFilter);
http.authorizeRequests()
.antMatchers("/user/**").authenticated()
.antMatchers("/manager/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')")
.anyRequest().permitAll();
http.formLogin()
.loginProcessingUrl("/login-process")
.defaultSuccessUrl("/", false);
}
My front-end is React using 3000 port and not in spring source folder. so i think i can't use spring default login form. so i thought it would work if i sent a "POST" request to loginProcessingUrl.
fetch('http://localhost:8081/login-process', {
credentials: 'include',
headers: {
'Content-Type': 'application/json'
},
method:"POST",
body:{"username":"user","password":"d4ddeb71-c76b-43f3-a0af-2f3c0b36bbec"}
})
.then((json) => console.log(json));
but i got "http://localhost:8081/login?error" url as response.
username and password is spring gave to me. and if i enter "http://localhost:8081/login"(default login form) using chrome browser and login with these, it works well. so i don't think username or password is wrong.
i've seen https://www.baeldung.com/spring-security-login-react, but I think it's different from my case where the front end is separated.
what's the problem? should i approach it differently?

Related

react axios request is empty in loadUserByUsername spring security

When I send axios put to login, My back-end (Spring Boot) used UserDetails get empty login (""). I thought that, my variables in axios is incorrect. Even I set hardcoded data to check whether the collected data from the form is correct. Unfortunately, I'm still sending an empty string.
const handleSubmit = (event) => {
const axios = require('axios').default;
event.preventDefault();
axios.defaults.withCredentials = true
axios({
method: 'post',
url: 'http://localhost:8080/login',
data: {
'accountLogin': accountLogin,
'passwordAccount': passwordAccount
}
})
.then(function (response) {
console.log(response);
console.log('Good!')
})
.catch(function (error) {
console.log(error);
console.log('error :<')
});
result username in spring UserDetails
[result in my browser]
Spring security configure:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/", "/login", "/register", "/swagger-ui**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("http://localhost:3000")
.defaultSuccessUrl("/index", true)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.logout().permitAll()
.invalidateHttpSession(true);
}
there are several faults in your code and i highly suggest you read the chapter on formLogin in the spring security documentation
You have configured this:
.formLogin()
formLogin means that you are going to use the built in login functionality in spring security. This is a form login, meaning it takes FORM parameters. You are sending your login parameters as JSON which is completely different.
sending form data with fetch
const formData = new FormData();
formData.append('username', accountLogin);
formData.append('password', accountPassword);
axios({
url: "http://localhost:8080/login",
method: "POST",
data: formData
});
Spring security formLogin takes in a usernameand a password per default as form parameters. Not json. And it will create a default login address for you to post the data to at http://localhost:8080/login
.loginPage("http://localhost:3000")
Should be removed. Spring will provide a custom built in login page that will get served if you hit a protected endpoint when you are unauthorized. If you wish to serve a different endpoint, you set this here and make sure that a pake is served there. For instance:
.loginPage("/myCustomLogin")
And then it is up to you to make sure that a login page is served from here otherwise you will get a 404 from spring security.
Depending on how you are packaging your application, if you have a node server for your frontend and a spring server for the backend, then you dont need to use loginPage you just post form data to /login. If you on the otherhand server the loginpage from the spring server, you set loginPage to some value, build an endpoint there, package the html files in the spring server, and write code that will serve the HTML page when you hit that endpoint.
This is a better way of sending post requests with Axios. try this, if it didn't work I guess the problem is with your back-end code.
axios.post('http://localhost:8080/login', {
accountLogin: accountLogin,
passwordAccount: passwordAccount
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});

Unspecific CORS error with Post method using fetch on React (Spring Boot backend)

Very new to React coming from a decent understanding of Angular. I have had CORS issues with Angular and have always solved them with backend config. Unfortunately that has not been the case so far with React.
Also, quick note that this is app purely for educational purposes and no actual sensitive date is being submitted or stored in the database. My main concern is with just getting this thing working rather than flawless security, as I am still pretty basic in my React knowledge.
The issue comes with posting to my API using fetch in React. The post method works just fine in Postman, but when using the browser (Chrome) I get the following message:
POST https://bookkeeperdb.herokuapp.com/api/books/add net::ERR_ABORTED 403
Response {type: "cors", url: "https://bookkeeperdb.herokuapp.com/api/books/add", redirected: false, status: 403, ok: false, …}
body: (...)
bodyUsed: false
headers: Headers {}
ok: false
redirected: false
status: 403
statusText: ""
type: "cors"
url: "https://bookkeeperdb.herokuapp.com/api/books/add"
__proto__: Response
Initially, I was getting more informational errors about being more specific in my config rather than using the wildcard for everything. All of my research into this issue has bounced me from one error to the next and has made the CORS config on my backend more specific. However, I am stuck at this issue and cannot find any relevant solutions to this problem online.
Here is my code for reference:
FRONTEND (form submission in REACT):
handleSubmit(event) {
event.preventDefault();
const formFields = {
title: this.state.bookTitle,
synopsis: this.state.synopsisInput,
pageCount: parseInt(this.state.pageCountInput),
isbn: parseInt(this.state.isbnInput),
genre_name: this.state.genreInput,
author_first_name: 'Cormac',
author_last_name: 'McCarthy',
// author_first_name: this.state.authorInput.slice(0, this.state.authorInput.indexOf(" ")),
// author_last_name: this.state.authorInput.slice(this.state.authorInput.indexOf("")),
publisher_name: this.state.publisherInput
}
const headers = new Headers({
'Content-Type' : 'application/json',
'Authorization': `Bearer ${localStorage.token}`
})
console.log(`The following title was submitted: ${formFields.title}`);
fetch("https://bookkeeperdb.herokuapp.com/api/books/add", {
method: 'POST',
credentials: "include",
headers: headers,
body: JSON.stringify(formFields)
})
.then((response) => console.log(response))
.catch(error => console.log(error))
}
BACKEND (corsconfig in SPRING BOOT):
#Configuration
public class CorsConfig {
#Bean
public WebMvcConfigurer corsConfigurer(){
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry){
registry.addMapping("/**")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.allowedOrigins("http://localhost:3000",
"http://localhost:3000/contribute");
}
};
}
}
Thank you in advance for the help.
Okay, really just took some experimenting but turns out including 'Bearer' in my Authorization header was messing up my token authentication when submitting the post. Well that was anti-climatic.

React app throws 401 error when accessed through safari, but not otherwise

I have a react app hosted in an s3 bucket behind a cloudfront distribution. This talks to a django server. Login works correctly on all browsers (confirmation that credentials are correct, and no fundamental connection issues). Once logged in, the app redirects to a page that does a simple GET request via fetch (to a url that requires authentication). This works successfully on all browsers on desktop (macos) except safari, which gets a 401 unauthorised error. Similarly, I get a 401 unauthorised error on every browser on iOS.
I've tried flushing the DNS of the mac, to no avail. I'm totally stumped. If it didn't work at all, that would be fine, but working everywhere apart from safari?
fetch code:
componentDidMount() {
fetch(`${apiRoot}/info`, {
method: "GET",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: `Token ${this.props.auth.token}`
}
})
.then(response => response.json())
.then(data => {
this.setState({
Name: data.name,
Email: data.email
});
});
}
Add a forward slash to end of your fetch url:
fetch(`${apiRoot}/info/`, ...
Chrome and Firefox seem to resolve this on their own, but Safari doesn't.

axios set headers content-type not working in request headers in react redux

let input = {
'email': email,
'password': password
};
axios({
method:'POST',
url: URL,
headers: {
'Content-type': 'application/json; charset=utf-8',
'Accept': 'application/json; charset=utf-8'
},
data: input
}).then(function (response){
console.log(response);
})
.catch(function (error) {
});
I try to request API with axios using method post. It not seen set request headers content-type,but if I using jquery ajax it ok.
Usually the charset=utf-8 is not set along with headers.
Content-type: application/json; charset=utf-8 designates the content to be in JSON format, encoded in the UTF-8 character encoding. Designating the encoding is somewhat redundant for JSON, since the default (only?) encoding for JSON is UTF-8.
its a bug in axios -.- there are multiple issues for this Problem.
I can show you my solution. I hope it's working for you, too.
import qs from 'qs'
let input = qs.stringify {
'email': email,
'password': password
});
axios
.post(URL, input)
.then(response => {
console.log(response.headers)
console.log(response.data)
})
.catch(function(error) {
console.error(error)
})
Spring Framework provides first class support for CORS. CORS must be processed before Spring Security because the pre-flight request will not contain any cookies (i.e. the JSESSIONID). If the request does not contain any cookies and Spring Security is first, the request will determine the user is not authenticated (since there are no cookies in the request) and reject it.
The easiest way to ensure that CORS is handled first is to use the CorsFilter. Users can integrate the CorsFilter with Spring Security by providing a CorsConfigurationSource using the following:
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
// by default uses a Bean by the name of corsConfigurationSource
.cors().and()
...
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("*"));
configuration.setAllowedHeaders(Arrays.asList("*"));
UrlBasedCorsConfigurationSource source = new
UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}

Basic Authentication of spring with Restangular

I am using Restangular with Spring's oauth security and in the client side i am using Restangular for login request.
Code in OAuth2ServerConfiguration:
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient("clientapp")
.authorizedGrantTypes("password", "refresh_token")
.authorities("USER")
.scopes("read", "write")
.secret("abc");
}
Login with postman needs these configurations:
1-Set Authorization as "Basic Auth".
2-Set username,password as {"username":"clientapp","password":"abc"}//credentials to access server side
3-In request body through "x-www-form-urlencoded" three parameters are sent.
{"username":"abc#gmail.com","password":"abc123","grant_type":"password"}//credentials to login which are checked from database.
This will do a successful login.but i cannot understand how to use these configurations in Angular JS Restangular call.
currently m trying with this.
In Config:
RestangularProvider.withConfig(function (RestangularConfigurer) {
return RestangularConfigurer.setDefaultHeaders({ "Authorization": "Basic Y2xpZW50YXBwOkxNUw==",
"username":"clientapp",
"password":"abc",
"Content-type": "application/x-www-form-urlencoded; charset=utf-8"
});
In Controller:
Restangualar.all("oauth/login").post({username;$scope.user.username,
password:"$scope.user.password","grant_type":"password"}).then(function(){
console.log(res);
});
But I am getting this error:
error:"unauthorized",error_description:"Full authentication is required to access this resource"
in browser.
Note:This resource is not secured.
Any Solution???
Update: I forgot to added a main information that my frontend with angular is running independently on localhost(through xampp) while spring login backend is on localhost:8080..
Error in network tab:
2-
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.authorizeRequests()
.anyRequest().permitAll()
//.antMatchers("/users").permitAll()
.antMatchers(HttpMethod.OPTIONS, "/oauth/token").permitAll()
.and().csrf().disable();
}
3-
endpoints
.tokenStore(this.tokenStore)
.authenticationManager(this.authenticationManager)
.userDetailsService(userDetailsService)
.addInterceptor(new HandlerInterceptorAdapter() {
public boolean preHandle(HttpServletRequest hsr, HttpServletResponse rs, Object o,FilterChain chain) throws Exception {
rs.setHeader("Access-Control-Allow-Origin", "*");
rs.setHeader("Access-Control-Allow-Methods", "GET,OPTIONS,POST");
// rs.setHeader("Access-Control-Max-Age", "7200");
rs.setHeader("Access-Control-Allow-Headers", "Origin, X- Requested-With, Content-Type, Accept, Authorization");
HttpServletRequest httpServletRequest = (HttpServletRequest) hsr;
if (httpServletRequest.getMethod().equalsIgnoreCase("OPTIONS")) {
chain.doFilter(hsr, rs);
} else {
// In case of HTTP OPTIONS method, just return the response
return true;
}
return false;
}
});
You can use Restangular custom post. See documentation.
Example:
Restangular.service("/oauth/login").one().customPOST(
{},
'',
{
// Params...
grant_type: 'password',
client_id: 'clientapp',
client_secret: 'abc',
username: 'abc#gmail.com',
password: 'abc123',
scope: 'read, write'
},
{
// headers...
}).then(
function (response) {
// Manage successfull response
},
function () {
// Manage error response
}
);
Hope it helps
UPDATED:
It seems to be a CORS problem, lots of answers already for it, but in your case using XAMPP you will need to configure your apache server:
https://enable-cors.org/server_apache.html.
PREVIOUS ANSWER BEFORE UPDATE:
The advantage of using restangular is the ability to manage resources in a more semantic way and the ability to get nested resources. All these advantages don't really apply for a call just to retrieve a token from an oauth2 provider.
I would recommend to forget about using restangular for this specific call (you still can use it for everything else in your application) and convert this call to a simple $http.post.
$http.post('oauth/login',
{ username;$scope.user.username,
password:"$scope.user.password",
"grant_type":"password"
},
{
headers: { "Authorization": "Basic Y2xpZW50YXBwOkxNUw==",
"username":"clientapp",
"password":"abc",
"Content-type": "application/x-www-form-urlencoded; charset=utf-8"
}
})
.then(function(response) {
Restangular.setDefaultHeaders({
"Authorization": "Bearer " + response.token
});
});
So, you just use $http.post, and on its response set the default headers in angular to use the retrieved token.
Cheers,

Resources