rxjs5 Observable.ajax ignores explicitly set HTTP headers - ajax

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.

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

Certain cookies are blocked cross domain when using an ajax request

I'm using a react app running on localhost:3000 which makes ajax requests to our website. We recently switched our authentification system from using WordPress authentification to https://github.com/delight-im/PHP-Auth.
Since then, using the same settings inside ajax and on our web server, our authentification cookies are not sent cross domain. However, it's working when requesting them from the same domain.
Our request:
fetchLoginStatus = () => {
const ajax = new XMLHttpRequest();
ajax.withCredentials = true;
ajax.open("POST", "https://our-website.com/src/php/checkLoggedIn.php");
ajax.onload = () => {
const response = JSON.parse(ajax.responseText);
};
ajax.send();
};
Our request headers (from localhost:3000):
:authority: my-website.com
:method: POST
:path: /src/php/checkLoggedIn.php
:scheme: https
accept: */*
accept-encoding: gzip, deflate, br
accept-language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7
content-length: 0
cookie: plesk-items-per-page; plesk-sort-field, phpMyAdmin; databases-active-list-state-collapsed; plesk-list-type; io=R_dL3fjUEYe64ykHAAAp; isAsyncProgressBarCollapsed=true; PLESKSESSID; plesk-sort-dir;
origin: https://localhost:3000
referer: https://localhost:3000/
Our response headers (we are running an nginx server):
access-control-allow-credentials: true
access-control-allow-headers: origin, x-requested-with, content-type
access-control-allow-methods: PUT, GET, POST, DELETE, OPTIONS access-`
control-allow-origin: https://localhost:3000
cache-control: no-store, no-cache, must-revalidate
content-encoding: br
content-type: text/html; charset=UTF-8
date: Sun, 10 Mar 2019 15:26:08 GMT
expires: Thu, 19 Nov 1981 08:52:00 GMT pragma:
no-cache server: nginx
set-cookie: PHPSESSID=someId;
path=/; SameSite=Lax status: 200
vary: Accept-Encoding
x-powered-by: PleskLin`
When I don't send the request cross-domain PHPSESSID is inside the cookies of my request headers. However when I send the request from localhost:3000 it's not there.
Does somebody know how I can send the PHPSESSID from localhost too?
Thanks for any help in advance!
Asked the same question inside the github repository and the owner solved it.
https://github.com/delight-im/PHP-Auth/issues/154
Solution:
vendor/delight-im/auth/src/UserManager.php
Replace Session::regenerate(true); with Session::regenerate(true, null);
vendor/delight-im/auth/src/Auth.php
Replace #Session::start(); with #Session::start(null);
Replace Session::regenerate(true); with Session::regenerate(true, null);
After $cookie->setSecureOnly($params['secure']); append $cookie-
>setSameSiteRestriction(null); in all three (!) occurrences

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);
});

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

OPTIONS request while adding Authorization header

Here's my problem, I'm trying to add the following Header to my GET ajax request :
Authorization: Basic XXXXXXX
So I wrote this :
function() {
var storage = window.localStorage;
var currentUsername = storage.getItem("username");
var currentPassword = storage.getItem("password");
var auth = makeBasicAuth(currentUsername, currentPassword);
$.ajaxSetup ({
beforeSend: function(request) {
request.setRequestHeader( "Authorization", auth );
}
});
I already have check the value of auth which is correct. And when I try to send my request, I get that :
OPTIONS <myurl> Error 401
Here are the headers of the request and of its answer :
Request :
Accept text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding gzip, deflate
Accept-Language fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3
Access-Control-Request-He... authorization
Access-Control-Request-Me... GET
Connection keep-alive
Host <myhost>
Origin null
User-Agent Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:22.0) Gecko/20100101 Firefox/22.0
Answer :
Access-Control-Allow-Head... Authorization
Access-Control-Allow-Meth... *
Access-Control-Allow-Orig... *
Access-Control-Max-Age 3628800
Connection Keep-Alive
Content-Encoding gzip
Content-Length 440
Content-Type text/html;charset=utf-8
Date Tue, 23 Jul 2013 13:00:58 GMT
Keep-Alive timeout=15, max=100
Server Apache-Coyote/1.1
Set-Cookie JSESSIONID=03A48D00EA04DE16396824E804E914AC; Path=/...
Vary Accept-Encoding
WWW-Authenticate Basic realm="Name Of Your Realm"
Note : I get this result whatever is the header I try to add.
Where is the problem ?
Thanks ^^.

Resources