No Authorization Header sending POST from Angular to Spring - 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

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

Vue + Spring: Does not pass the CORS test

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/**")

Username and password have been treated as anonymous in Spring-security-oauth2 password mode

I'm using Spring Boot and Spring Security OAuth2 to issue tokens to the front-end.
Postman
When I use postman to test, everything works fine.
.
Browser
But when I sent a same request on browser using vue.js and axios, it didn't work as expected. The status code was 401.
Gerneral:
Request URL: http://localhost:8080/oauth/token
Request Method: POST
Status Code: 401
Remote Address: [::1]:8080
Referrer Policy: no-referrer-when-downgrade
Response Headers:
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: http://localhost:8081
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Type: application/json;charset=UTF-8
Date: Sun, 17 Mar 2019 02:20:54 GMT
Expires: 0
Pragma: no-cache
Transfer-Encoding: chunked
Vary: Origin, Access-Control-Request-Method, Access-Control-Request-Headers
WWW-Authenticate: Basic realm="oauth2/client"
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Request Headers:
Provisional headers are shown
Accept: application/json, text/plain, */*
Content-Type: application/x-www-form-urlencoded
Origin: http://localhost:8081
Referer: http://localhost:8081/login
User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36
Form Data:
{"grant_type":"password","scope":"all","username":"admin","password":"888","client_id":"wellcell","client_secret":"wellcell"}:
Difference in server console log
I made a picture of side-by-side:
On the left side is the server console log of postman request.
And server console log of browser request is on the right.
After "ClientCredentialsTokenEndpointFilter", postman request went to "DaoAuthenticationProvider" to be authenticated.
But the browser request went to "BasicAuthencationFilter" and the "username" and "password" was ignored and an anonymous user was returned. Then, access is denied with an anonymous user.
Anybody had this kind of problem before?
I think problem with Content-Type: application/x-www-form-urlencoded. If you send json, you need to use Content-Type: application/json.
Simple use of axios with post of json:
axios.post("http://localhost:8080/oauth/token", {
"grant_type": "password",
"scope": "all",
"username": "admin",
"password": "888",
"client_id": "wellcell",
"client_secret": "wellcell"
}).then((response) => {
console.log(response.data);
});

CORS Between Spring Boot(Spring Security) and Enterprise Authentication applications

I am getting CORS issue even after setting required headers, like Access-Control-Allow-Origin to http://localhost:4200, Access-Control-Allow-Credentials to true and so on.
Response from previous request:
HTTP/1.1 302 Found
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Special-Response-Header, Header2
Access-Control-Allow-Origin: http://localhost:4200
Access-Control-Expose-Headers: Special-Response-Header, Header2
Access-Control-Max-Age: 3600
Access-Control-Request-Headers: Special-Response-Header, Header2
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Length: 0 Date: Mon, 18 Jun 2018 13:03:46 GMT
Expires: 0
Location: <<Replace with Org Authentication URL>>
Origin: http://localhost:4200
Pragma: no-cache
Set-Cookie: dtCookie=8$C38554BBC14802D7BCFE9A5E047AA962;domain=rbc.com;path=/ Set-Cookie: JSESSIONID=26A7EB71BD36D31EFE6A701320DFA0C3;path=/;Secure;HttpOnly Set-Cookie: __VCAP_ID__=0a838736-ad8b-40b9-4aa5-e972; Path=/; HttpOnly; Secure Strict-Transport-Security: max-age=31536000 ; includeSubDomains X-Content-Type-Options: nosniff X-Frame-Options: DENY X-Vcap-Request-Id: 61cc9d4e-29ba-4b11-7f0b-82ec01cbc0a6 X-Xss-Protection: 1; mode=block
Current Request :
GET <<Replace with Org Authentication Get method params>>
HTTP/1.1
Host: mrkdlvaiaas493.devfg.rbc.com:9443
Connection: keep-alive
Accept: application/json, text/plain, */*
Origin: null
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.119 Safari/537.36 Referer: http://localhost:4200/startBatch/2018-04-27 Accept-Encoding: gzip, deflate, br Accept-Language: en-US,en;q=0.9
Error :
Failed to <<Replace with Org Authentication URL>>: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access.
Try declare a bean like this: (to enable cors)
#Configuration
public class SecurityBasicConfigurations {
#Bean
public WebMvcConfigurer corsOriginConfigurer() {
return new WebMvcConfigurer() {
#Override
public void addCorsMappings( CorsRegistry registry ) {
registry.addMapping( "/*" )
.allowedMethods( "*" )
.allowedOrigins( "http://localhost:4200" )
.allowedHeaders( "*" );
}
};
}
}

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