Vue + Spring: Does not pass the CORS test - spring

My Spring application is running on the address localhost:8081
My frontend Vue.js is running on the address localhost:8080.
I have the WebMvcConfig:
...
#Configuration
public class WebMvcConfig implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry
.addMapping("*")
.allowedOrigins("http://localhost:8080");
}
}
I have the request in Vue component
import HTTP from '../api/http-common'
...
HTTP.post('/registration', this.form)
.then(response => {
if (response.data.errors) {
this.errors = response.data.errors
} else {
this.$router.push("/");
}
})
Here http-common
import axios from 'axios'
export default axios.create({
baseURL: 'http://localhost:8081/api'
})
It used to work, I had in WebMvcConfig next:
#Configuration
public class WebMvcConfig implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry
.addMapping("/api/registration")
.allowedOrigins("http://localhost:8080");
}
}
Then I had to make a request to the address localhost:8081/api/activate/{code}.
And I replace "/api/registration" on "/api/activate/**" But it doesn't work. Now both settings don't work.
I want to allow all request from localhost:8080(where my frontend Vue.js is runnning)
I have next CORS test in browser Chrome:
GENERAL
Request URL: http://localhost:8081/api/registration
Request Method: OPTIONS
Status Code: 403
Remote Address: [::1]:8081
Referrer Policy: no-referrer-when-downgrade
RESPONSE HEADER
Allow: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Length: 20
Date: Fri, 23 Aug 2019 09:30:42 GMT
Expires: 0
Pragma: no-cache
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
REQUEST HEADER
Provisional headers are shown
Access-Control-Request-Headers: content-type
Access-Control-Request-Method: POST
Origin: http://localhost:8080
Referer: http://localhost:8080/registration
Sec-Fetch-Mode: no-cors
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36
Error from console:
Access to XMLHttpRequest at 'http://localhost:8081/api/registration' from origin 'http://localhost:8080' 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.
createError.js?2d83:16 Uncaught (in promise) Error: Network Error
at createError (createError.js?2d83:16)
at XMLHttpRequest.handleError (xhr.js?b50d:81)

I need to write next, then it's work:
.addMapping("/api/**")

Related

Wrong response from the server - It seems my FrontEnd is getting Options response instead of Post

I have read a lot about CORS, preflight etc, I know the problem is related to it, but couldn't figure out, what's going on here.
I'm using VueJs and SpringBoot with Spring Security and jsonwebtoken.
When I make a POST request /login on Postman:
{
"username":"admin",
"password":"password"
}
I got the right response with the expected token:
http 200 with headers:
{
Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImV4cCI6MTYwODI5OTIxMn0.9oXFpm9DivR3DNPcBaoc_KgsqNdBJbkFq_oA4pBJbXF2iUwx7_XfBwv-Xcn-da9LS9M5zxd8oRslr_wdVyoQkA,
X-Content-Type-Options:nosniff
X-XSS-Protection:1; mode=block
Cache-Control:no-cache, no-store, max-age=0, must-revalidate
Pragma:no-cache
Expires:0
X-Frame-Options:DENY
Content-Length:0
}
However, when the front-End calls the service I got the following answer with the wrong header, there is no token:
http 200 with headers
{
cache-control: "no-cache, no-store, max-age=0, must-revalidate",
content-length: "0", expires: "0", pragma: "no-cache"
}
When I open the browser with security disabled it works fine as well, I got the expected token on the header...
"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --user-data-dir="C://Chrome dev session" --disable-web-security
So it seems to be related to Cors indeed, but I don't get any Cors error message!
I used fiddler to track the requests and responses and I notice that the option method is happening, So my first question is, should the options response go up to the front-end? as far as I know the options stay within the browser boundary and then the browser makes the post request (if the server allowed).
Options Request:
OPTIONS http://ec2-52-4-252-232.compute-1.amazonaws.com:9090/login HTTP/1.1
Host: ec2-52-4-252-232.compute-1.amazonaws.com:9090
Connection: keep-alive
Accept: */*
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization
Origin: http://s3-sa-east-1.amazonaws.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36
Sec-Fetch-Mode: cors
Referer: http://s3-sa-east-1.amazonaws.com/
Accept-Encoding: gzip, deflate
Accept-Language: pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7,es;q=0.6
Options Response:
HTTP/1.1 200
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Access-Control-Allow-Origin: http://s3-sa-east-1.amazonaws.com
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: authorization
Access-Control-Allow-Credentials: true
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Length: 0
Date: Tue, 08 Dec 2020 14:58:56 GMT
Post Request:
POST http://ec2-52-4-252-232.compute-1.amazonaws.com:9090/login HTTP/1.1
Host: ec2-52-4-252-232.compute-1.amazonaws.com:9090
Connection: keep-alive
Content-Length: 42
Accept: application/json, text/plain, */*
Authorization: undefined
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Origin: http://s3-sa-east-1.amazonaws.com
Referer: http://s3-sa-east-1.amazonaws.com/
Accept-Encoding: gzip, deflate
Accept-Language: pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7,es;q=0.6
{
"username":"admin",
"password":"password"
}
Post Response:
HTTP/1.1 200
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Access-Control-Allow-Origin: http://s3-sa-east-1.amazonaws.com
Access-Control-Allow-Credentials: true
Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImV4cCI6MTYwODI5OTUzNn0.0UgsNHd9Aw9Ei5aq-k0y74BlxJ92-j7w-FrryZaDAwzLC1a2OpSH3rXhRWGIul3wqpWLbqJ7icNlM3d590UFWw
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Length: 0
Date: Tue, 08 Dec 2020 14:58:56 GMT
Part of My front-end Code:
login({ commit }, user) {
const cors = require('cors')({
origin: true
});
const qs = require('querystring')
return new Promise((resolve, reject) => {
commit('auth_request')
axios({
url: process.env.VUE_APP_BACKEND_ENDPOINT + '/login', data: user, method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded'}
})
.then(resp => {
console.log(resp.headers)
const token = resp.headers["authorization"]
localStorage.setItem('token', token)
axios.defaults.headers.common['Authorization'] = token
commit('auth_success', token, user.username)
resolve(resp)
})
.catch(err => {
commit('auth_error')
localStorage.removeItem('token')
reject(err)
})
})
Part of my Back-End Code:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.cors().configurationSource(corsConfigurationSource()).and()
.csrf().disable().authorizeRequests()
.antMatchers("/home").permitAll()
.antMatchers(HttpMethod.POST, "/login").permitAll()
.anyRequest().authenticated()
.and()
// filtra requisições de login
.addFilterBefore(new JWTLoginFilter("/login", authenticationManager()),
UsernamePasswordAuthenticationFilter.class)
// filtra outras requisições para verificar a presença do JWT no header
.addFilterBefore(new JWTAuthenticationFilter(),
UsernamePasswordAuthenticationFilter.class);
}
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
List<String> allowOrigins = Arrays.asList("*");
configuration.setAllowedOrigins(allowOrigins);
configuration.setAllowedMethods(singletonList("*"));
configuration.setAllowedHeaders(singletonList("*"));
//in case authentication is enabled this flag MUST be set, otherwise CORS requests will fail
//configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// cria uma conta default
auth.inMemoryAuthentication()
.withUser("admin")
.password("{noop}password")
.roles("ADMIN");
}
}```
Anyone knows what's going on here ? Why the Post response isn't coming to the front-end ?
I spend many hours but couldn't solve it, so I appreciate any help.
I can reproduce your Postman request with curl:
curl -vv -H "Content-Type: application/x-www-form-urlencoded" http://ec2-52-4-252-232.compute-1.amazonaws.com:9090/login -d '{"username":"admin", "password":"password"}'
Returns a 200 with the Authorization header filled in.
Can you run the frontend code with the Network tab in Developer Tools open and see how the POST request looks?
And possibly right-click and "copy as cURL" and then compare it to the curl request above.
Actually my problem wasn't related to options method as I thought, the problem was that I was missing the Access-Control-Expose-Headers which should also be presented in the headers of server response.
I just set it on my springboot app and it worked fine !
List<String> exposedHeaders = Arrays.asList("Authorization");
configuration.setExposedHeaders(exposedHeaders);
I got the answer from this one: axios response headers missing data when running in vuejs app

Cannot send POST request to Spring Resource server

I have a Spring Boot ResourceServer, and a React client application. I am trying to send a POST request to one of the server endpoints (which has the #CrossOrigin annotation btw.), however, I am getting this error in the console:
Access to XMLHttpRequest at 'http://localhost:8080/api/search' from
origin 'http://localhost:3000' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check: It
does not have HTTP ok status.
The preflight request returns a 401 Http status, and I don't know why.
The response headers for the preflight request look like this:
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: content-type
Access-Control-Allow-Methods: POST
Access-Control-Allow-Origin: http://localhost:3000
Allow: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Length: 0
Date: Sun, 20 Oct 2019 17:23:54 GMT
Expires: 0
Pragma: no-cache
Vary: Origin, Access-Control-Request-Method, Access-Control-Request-Headers
WWW-Authenticate: Basic realm="Realm"
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
My preflight request headers look like this:
Access-Control-Request-Headers: content-type
Access-Control-Request-Method: POST
Origin: http://localhost:3000
Referer: http://localhost:3000/movie-search
Sec-Fetch-Mode: no-cors
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.87 Safari/537.36
I am using Axios to send the request (if that matters). Anyone knows what's going on here?
Whoops...my HTTPSecurity config looked like this:
http
.authorizeRequests()
.antMatchers(HTTPMethod.POST, "/api/search").permitAll()
.anyRequest().authenticated()
.and().httpBasic()
instead of this:
http
.authorizeRequests()
.antMatchers("/api/search").permitAll()
.anyRequest().authenticated()
.and().httpBasic()

No Authorization Header sending POST from Angular to Spring

I have:
a backend server written with Spring
and a client written in Angular
I am trying to send a POST request...
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
#Injectable()
export class AuthenticationService {
constructor(private http: HttpClient) { }
login(username: string, password: string) {
const httpOptions = {
headers: new HttpHeaders({
'Content-Type' : 'application/x-www-form-urlencoded',
'Accept' : 'application/json',
'Authorization' : 'Basic blablabla_base64_encoded'
})
};
const body = 'username=' + username + '&password=' + password + '&grant_type=password';
console.log(body);
console.log(httpOptions.headers);
console.log(this.http.post<any>('http://localhost:8080/oauth/token', body, httpOptions));
return this.http.post<any>('http://localhost:8080/oauth/token', body, httpOptions)
.map(
user => {
// login successful if there's a jwt token in the response
if (user && user.token) {
// store user details and jwt token in local storage to keep user logged in between page refreshes
localStorage.setItem('currentUser', JSON.stringify(user));
}
return user;
});
}
logout() {
// remove user from local storage to log user out
localStorage.removeItem('currentUser');
}
}
Filtering the request with Wireshark I got:
OPTIONS /oauth/token HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Access-Control-Request-Method: POST
Origin: http://localhost:4200
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML,
like Gecko) Chrome/67.0.3396.79 Safari/537.36
DNT: 1
Access-Control-Request-Headers: authorization
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
HTTP/1.1 401 Unauthorized
Server: Apache-Coyote/1.1
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
WWW-Authenticate: Basic realm="oauth2/client"
Access-Control-Allow-Origin: http://localhost:4200
Vary: Origin
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: authorization
Access-Control-Allow-Credentials: true
Allow: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
Content-Length: 0
Date: Sat, 09 Jun 2018 14:19:36 GMT
For some reason I have no Authorization header and my request from POST become OPTIONS. I searched for some hour other solutions over the web but none of them helped me.
Some advice?
Found the answer to this question.
The Spring server has to manage also pre-flighted requests according to the paradigm explained in the documentation here:
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Preflighted_requests
To add the CORS management is possible to add a CORSFilter before all other filters implemented in the Spring Boot App, with a good code example here:
https://gist.github.com/malike/f8a98b498368932e6d7511886a167848

ng2-stomp-service does not connect to Spring server in https

I have a client Angular 2 application, and a Spring boot (1.4.1.RELEASE) server application. I wrote the client code to send a connection request to the server using the angular library ng2-stomp-service, and the server code to manage the connection request.
All works fine when using http, but when using https I see a connection error (HTTP/1.1 400) in the javascript console:
GET https://tomcatunisvid.lunagest.com/unisvid/workflow/046/mmesis4u/websocket [HTTP/1.1 400 358ms]
Firefox non può stabilire una connessione con il server wss://tomcatunisvid.lunagest.com/unisvid/workflow/046/mmesis4u/websocket.
In Firefox I found the request and the response headers:
-- REQUEST HEADERS
Accept text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encodinggzip, deflate, br
Accept-Language it-IT,it;q=0.8,en-US;q=0.5,en;q=0.3
Cache-Control no-cache
Connection keep-alive, Upgrade
Host tomcatunisvid.lunagest.com
Origin https://tomcatunisvid.lunagest.com
Pragma no-cache
Sec-WebSocket-Extensions permessage-deflate
Sec-WebSocket-Key J2Aqm3n2r6vcE7F8SrPr3w==
Sec-WebSocket-Version 13
Upgrade websocket
User-AgentMozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:56.0) Gecko/20100101 Firefox/56.0
-- RESPONSE HEADERS
Access-Control-Allow-Credentials true
Access-Control-Allow-Originh ttps://tomcatunisvid.lunagest.com
Cache-Control no-cache, no-store, max-age=0, must-revalidate
Connection close
Date Mon, 06 Nov 2017 11:50:42 GMT
Expires 0
Pragma no-cache
Server Apache/2.4.18 (Ubuntu)Transfer-Encoding chunked
Vary Origin
X-Content-Type-Options nosniff
X-Frame-Options DENY
X-XSS-Protection1; mode=block
and the response is empty.
Here is my relevant client code:
// In the constructor, where stomp is a StompService:
stomp.configure({
host: this.url,
debug: true,
queue: { 'init': false, '/user/queue/user/reply': false },
// headers: { 'upgrade': 'WebSocket' }
});
// in another method:
this.stomp.startConnect().
then(() => this.openSocket_OK_Callback(this)).
catch((error) => this.openSocket_KO_Callback(this, error));
Here is my relevant server code:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends
AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.setApplicationDestinationPrefixes("/wfm");
config.enableSimpleBroker("/topic", "/queue");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
SessionDisconnectionListener sdl = new SessionDisconnectionListener();
String endpoint = "/workflow";
registry.addEndpoint(endpoint).
setAllowedOrigins("*").withSockJS();
registry.addEndpoint(endpoint).setHandshakeHandler(new DefaultHandshakeHandler(new TomcatRequestUpgradeStrategy())).
setAllowedOrigins("*").withSockJS();
}
}
Any hints how to solve this issue?
I put here the answer I found after some debugging, in case it is useful to others. In the release environment Tomcat was proxied by Apache Http Server. The solution in this case was to remove Apache Http Server and configure Https directly on Tomcat.

rxjs5 Observable.ajax ignores explicitly set HTTP headers

I'm getting my feet wet with redux-observable and OAuth2 authentication. I'm stuck at the point where I have to POST adding Authorization header to my HTTP request. The header is has not been added. Instead, I see any custom-set header names as values of Access-Control-Request-Headers, and that's it.
This is a redux-observable 'epic':
const epicAuth = function(action$){
return action$.ofType(DO_AUTHENTICATE)
.mergeMap(
action => Rx.Observable.ajax( authRequest(action.username, action.password))
.map( response => renewTokens(response))
.catch(error => Rx.Observable.of({
type: AJAX_ERROR,
payload: error,
error: true,
}))
)
}
This is my request object:
const authRequest = function(username, password){
return {
url: TOKEN_PROVIDER + '?grant_type=password&username=' + username + '&password=' + password,
method: 'POST',
responseType: 'json',
crossDomain: true,
withCredentials: true,
headers: {
'Authorization': 'Basic <base64-encoded-user#password>',
}
}
}
The HTTP headers captured:
http://localhost:8082/api/oauth/token?grant_type=password&username=xxx&password=yyy
OPTIONS /api/oauth/token?grant_type=password&username=xxx&password=yyy HTTP/1.1
Host: localhost:8082
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization
Origin: http://localhost:3000
DNT: 1
Connection: keep-alive
HTTP/1.1 401
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
WWW-Authenticate: Basic realm="MY_REALM/client"
Content-Type: text/html;charset=utf-8
Content-Language: en
Content-Length: 1098
Date: Wed, 01 Nov 2017 17:57:38 GMT
It all ends up with 401 response, since the Authorization header was not sent. I have tested the Oauth2 endpoint manually with Postman tool, and all went well: I've got a valid access token, could renew it, etc. CORS is enabled on server side.
What am I missing here?
The client code is working correctly.
You've captured the OPTIONS cors request, which is asking the server if it is OK to POST the Authorization header (see the Access-Control-Request-Headers: authorization).
Make sure that you've configured CORS correctly on your server. It shouldn't be trying to authenticate OPTIONS calls. It should instead be sending a proper response which tells the browser if it is allowed to make the POST call.

Resources