Angular 2 not passing JSESSIONID on subsequent requests - javascript

Using Angular 6.1.3.
The login request is sending the JSESSIONID cookie on the response. However, any subsequent request is not including this cookie. In searching around most mentioned the use of use of withCredentials: true. However I am using this and still not successful. Have tried on both Chrome and Firefox.
Any help would be greatly appreciated.
At one time another cookie was present and withCredentials was causing THAT cookie to be included, but NOT JSESSIONID. Very frustrating.
I have also tried to access the JSESSIONID manually to include it myself even though that should not be required. However even "Observe: 'response'" is not allowing me to see the JSESSIONID cookie's value. So if I have to go this route, please advise on how to access this.
Is there any restriction to the type of request being sent? Even though I tried a GET without success, when the answer is supplied, will it work with any type of subsequent request (i.e. POST, PUT, PATCH)?
Login reqeust:
const params: HttpParams = new HttpParams().set('token.name', username).append('token.value', password);
const headers: HttpHeaders = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded');
return this.httpClient
.post<any>(this.loginUrl, params, { headers: headers, params: params, withCredentials: true })
.map(adjudicator => {
return adjudicator.adjudicator as Adjudicator;
})
.catch(this.errorHelperService.handleError);
Login Response
login Response Cookie tab
Subsequent Request:
// test request
const headers: HttpHeaders = new HttpHeaders().set('Content-Type', 'application/json');
return this.httpClient
.get<any>(this.configService.getManagerUrl()+'1/bada8257-e7d9-45d3-a8a1-83a67f863260', { headers: headers, withCredentials: true })
.map(response => {
return response.match as Match;
})
.catch(this.errorHelperService.handleError);
Subsequent Test Request

The cookie has the "secure" directive and the site you are trying to access is not secure.
A secure cookie is only sent to the server with an encrypted request over the HTTPS protocol.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies

Related

Set and get session variables in PHP through fetch API

I am trying to set a session variable using fetch -
const response = await fetch('http://locahost/index.php/session/set', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({token:"token"})
});
The PHP function (inside a class) that does this -
public function setSession($arr){
try{
session_start();
$_SESSION['token'] = $arr['token'];
$responseData = json_encode("SESSION token has been set to ".$_SESSION['token']);
/// sendresponsedata() -> send response back with Access-Control-Allow-Credentials: true
} catch (Error $e) {
/// Some error
}
}
The PHP function is not on the same page as the page making the fetch request. When I console.log the response on the page that sent the request, it correctly shows SESSION token has been set to token.
But if then I try to retrieve the session variable using a different request and a different function -
fetch('http://localhost/index.php/session/get',{
credentials: 'include'
})
The response I get from this is always No ongoing session
public function getSession(){
try {
session_start();
// print json_encode($_SESSION); <---- printing this shows an empty array
$responseData = json_encode((isset($_SESSION["token"])) ? $_SESSION["token"]:"No ongoing session");
/// sendresponsedata() -> send response back with Access-Control-Allow-Credentials: true
} catch (Error $e) {
/// Some error
}
}
I looked at other questions like mine but as far as I could understand, the error was because of not allowing credentials. I couldn't really understand why credentials are needed in this case reading this, but I added them anyway to check first, but that didn't change anything. As far as I could understand fetch request creates a new session everytime so this could be impossible, but this might be possible if I made an AJAX request. I am not sure I understood that correctly however.
The sendresponsedata() function works well as I have made many other fetch requests with more headers, like allowing cross-origin requests and returning required headers on preflight handshakes which all worked (and it is not really a complicated function).
What am I doing wrong and how can I achieve what I need?
Edit: Since posting I have also tried xhr requests and they don't work either.

Axios retry request forgets headers

I have an axios request interceptor that adds bearer authorization via an access token. I also have a response interceptor that catches 'token expired' responses, gets a new token via a refresh mechanism, and retries the original request. This seems like it should work, except when the original request is retried, it seems to have lost all its headers. This confuses my API backend as it expects a Content-Type (which was there in the original request).
Note: I'm familiar with the large number of questions about why axios doesn't respect a custom Content-Type header. That's not my problem -- I'm not setting one -- axios does a fine job determining that on its own. I'm just confused at why the Content-Type header that axios sets itself is getting removed when resending the original request.
Relevant code:
const api = axios.create({
baseURL: "https://www.mycoolapi.com",
timeout: 5000,
withCredentials: true,
});
api.interceptors.request.use((config) => {
// Add the auth header (not messing with any other headers)
config.headers!.Authorization = `Bearer ${accessToken}`;
return config;
}, (error) => Promise.reject(error));
api.interceptors.response.use((response) => response,
async (error) => {
if (error.response.status == 401 && refreshToken && !error.config._isRetry) {
// Token expired? Refresh, then retry the original request
error.config._isRetry = true;
await refreshLogIn();
return api(error.config);
}
throw error;
});
async function refreshLogIn() {
// Use the refresh token to get a new token pair
const response = await api.post( "/token",
new URLSearchParams({
grant_type: "refresh_token",
refresh_token: refreshToken,
})
);
accessToken = response.data["access_token"];
refreshToken = response.data["refresh_token"];
}
If I put a breakpoint on return api(error.config) in the response interceptor, and inspect the original request (which is in error.config), I get the following headers (reminder, I added only the Authorization header, the rest are axios defaults or calculated from the requeset):
If I then step through to the request interceptor where the original response is being retried, I get these headers instead:
Content-Type has been removed, along with what was in Symbol(defaults) (whatever that is).
What am I missing here?
As #Phil noted in the comments, this is a bug in axios >= 1.0.0. I suppose one could downgrade to 0.27.2, but I just rewrote using good old fetch. My use case was simple enough anyway, and one less dependency is never a bad thing.

Request header field authorization is not allowed by Access-Control-Allow-Headers in preflight response when using http get req from JS to SlackAPI

I understand that there are many similar questions, but I am posting this because I feel it is slightly different.
I am trying to send a GET request to the Slack API using an HTTP request.
Specifically, the code looks like the following.
import useSWR from "swr";
const useSlackSearch = (query: string) => {
const token = process.env.NEXT_PUBLIC_SLACK_API_USER_TOKEN;
const myHeaders = new Headers();
myHeaders.append("Authorization", "Bearer " + token);
const slackURL = `https://slack.com/api/search.messages?query=${query}`;
const fetcher = async (url: string) => {
const response = await fetch(url, {
headers: myHeaders,
}).then((res) => res.json());
return response;
};
const { data, error } = useSWR(slackURL, fetcher, {
revalidateOnFocus: true,
revalidateOnReconnect: true,
});
if (error) {
return console.log(`Failed to load: ${error}`);
} else if (!data) {
return console.log("Loading...");
} else {
console.log(data);
return data;
}
};
export default useSlackSearch;
The environments I'm using are as follows.
Device: MacBook Air
OS: macOS
Browser: Chrome
From: localhost:3000
To: Slack API html page (https://slack.com/api/search.messages)
After reading the MDN articles like below, I understood that
There is such a thing as a simple HTTP request as defined by MDN
If the request you want to send does not correspond to this simple request, the browser will send a preflight request
In the response to that preflight request, there is a header called Access-Control-Allow-Headers.
Only headers set to the value of this Access-Control-Allow-Headers header can be used as headers in the main request after preflighting.
In this case, I tried to use the Authorization header, but it was trapped by the above restriction.
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests
https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request
That's all I understand.
However, on the official Slack API page for the method in question, it says to specify the token in the Authorization header, so I'm having trouble.
I also don't understand how to specify the Access-Control-Request-Headers in the preflight header, as described in another questioner's thread. The reason is that the only thing that communicates to the Slack API is the browser in this case, and the only relevant source is JavaScript (React / Next.js to be exact)!
After that, I found preflight response from Slack API as follows;
access-control-allow-headers: slack-route, x-slack-version-ts, x-b3-traceid, x-b3-spanid, x-b3-parentspanid, x-b3-sampled, x-b3-flags
As I thought, I understand that Authorization is not allowed because it is not included as a value. So the question is how to solve it.
Furthermore, I found out later that the preflight request from the browser properly declared that it wanted to use Authorization as an actual request header. However, the preflight response did not contain the value.
Following CBroe's advice, I was able to contact the Slack help center directly, so I asked this problem. What I found out as a result is that HTTP requests from browsers are not supported as of the end of February 2022. Of course, they have received quite a lot of requests regarding this, so they hope to address it at some point.
This time, the browser sent Access-Control-Request-Headers:Authorization in the preflight request. But the Slack API server side did not allow the Authorization header in the request from the browser. Therefore, Authorization was not set in the Access-Control-Allow-Headers in the preflight response from the Slack API side.
As a result, the response from the Slack API side returned Invalid Auth, even though Authorization was added as a header when making an actual request from the browser.
Through this error, I gained a deeper understanding of HTTP requests such as CORS and preflighting, but since it is not explicitly written on the official Slack website, I left it here.
What is Preflight: https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request
What is Access-Control-Allow-Header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers
What is CORS simple request: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests
I could not get the Authorization header to work either. However, Slack provided this example for adding token authentication to the Post body following the deprecation of the query parameters method.
This worked for me to make Web API calls to Slack from the browser (for testing) so that Slack would read the token for authentication. Note, according to Slack's best practices for security, user and bot tokens should be stored with care and not used in client-side Javascript:
try {
const res = await fetch("https://slack.com/api/conversations.list", {
method: "POST",
body: `token=${TOKEN}`, // body data type must match "Content-Type" header
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
}).catch((error) => {
console.log(error);
});
if (!res.ok) {
throw new Error(`Server error ${res.status}`);
} else {
const data = await res.json();
console.log(data);
}
} catch (error) {
console.log(error);
}
using token in request body instead of Authorization header worked for me.
axios({
method: 'post',
url: 'https://slack.com/api/chat.postMessage',
data: `text=Hi&channel=D048GGYTJUK&token=${process.env.TOKEN}`
})

How to redirect to new page with Authorization Headers after login?

I have a login page. On click of login button, the details are sent to server and are validated. A token is received in return after successful validation. I need to know how to redirect to a new page(/dashboard) using this token set as authorization header. Note that I am using vanilla Js
function login(){
var userEmail = document.getElementById("email_field").value;
var userPass = document.getElementById("password_field").value;
axios
.post('http://localhost:5000/login',{
username: userEmail,
password: userPass,
})
.then( (response) => {
if(response.data.success){
let token = response.data.token;
localStorage.setItem("SavedToken", token);
axios.defaults.headers.common['Authorization'] = token;
//some code here to redirect to localhost:5000/dashboard with authorization header
}
alert(response.data.message);
console.log(response)
})
}
I had the same issue when I was writing vanilla authorization app.
From my experience there is no easy way to achieve this.
The browser sends req and without us being able to meddle with the headers, without using http intercepting tool to catch outgoing browser http requests.
So, one way to allow to send http req and utilize the token we have is to use this work-around: the server serves HTML pages without any issue, but the body is empty. For each page the is a built in "onload" function. This function sends anther http req via "fetch" (or any other tools), and gets back the accual page after the token is validated by the server.
Then you render the accual page into the body.
For Example:
fetch("http://localhost:5454/signup")
.then((res) => res.json())
.then((res) => {
const token = res.token;
localStorage.setItem("token", token);
fetch("http://localhost:5454/", {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
})
.then((res) => res.text())
.then((rawHtml) => {
document.write(rawHtml);
document.close;
});
Another way to do it without sending multiple http request to each endpoint is the single page approuch:
You get only the main page of the application that has a nav bar in it. Each navbar click sends a req with the token and gets the HTML, and render is to a specific location within the main page.
As I said, this is a very crud way.
Another option is in the server, after the login and in the generation on the token - add the token to a cookie, that the browser will store, and will add to every outgoing request.
I will be glad to hear from anyone that has a better solution...
ANyway, hope this helps.

How do I fetch cookie data with React?

I have a MERN + Passport.js application that is using fetch to make a call to my express API in order to retrieve data stored in a cookie. I have my React frontend routed to localhost:3000, and my backend routed to localhost:3001. My cookies are not being saved, and thus my session is not persisting within my browser. It is not an issue with the express-sessions or passport middleware, as when I use POSTMAN to execute the necessary calls to my API, the cookie is stored and the session persists.
It is only when I attempt to pass this information through/to my front end that things go wrong. I have been stumped for a while and can't seem to find an answer anywhere.
This is the line that I am using to save the cookie:
handleLogin(event) {
event.preventDefault();
fetch("http://localhost:3001/users/login", {
// credentials: 'include',
credentials: 'same-origin',
method: "post",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: this.state.username,
password: this.state.password
})
})
// .then( (response) => response.json())
.then( (response )=> {
if(response.message){
alert(response.message);
}
})
Which correctly calls my API, which logs the current session, user data, and cookie.
Upon refreshing and making another request, I lose the cookie (it was never properly stored in the first place I think), and all session data.
This is the get request that I make whenever I navigate to a new page:
componentDidMount(){
var current_user = "";
fetch("http://localhost:3001/users/", {
// credentials: 'include',
credentials: 'same-origin',
method: "get",
headers: {
'Accept':'application/json',
'Content-Type': 'application/json'
}
})
// .then( (response)=> response.json())
.then( (response)=> {
if(response.user){
current_user = response.user;
this.setState({
user: current_user
}), ()=> console.log(response);
}
})
}
In response, I get an undefined user and no other response, and the cookie is never stored in my browser. But again, if I do this in POSTMAN, strictly doing the POST request followed by that GET request, the proper user data is returned and the cookie is shown in POSTMAN as well.
Any idea as to why fetch is not passing the cookie information back to my front end? I have tried both credentials: 'include' and credentials: same-origin.
Thank you!
It seems like the problem, or at least part of it, is your use of same-origin. From Mozilla docs (italics my own):
omit: Never send cookies.
same-origin: Send user credentials (cookies, basic http auth, etc..) if the URL is on the same origin as the calling script. This is the default value.
include: Always send user credentials (cookies, basic http auth, etc..), even for cross-origin calls.
The definition of "same origin" does not include different ports.
If you change same-origin to include, fetch should start managing your cookies correctly.
If not - do you know for sure that the cookie is "never stored in the browser"? With Chrome, you can check chrome://settings/siteData.

Categories