Disable redirect in fetch request using React native - redirect

I'm trying to crawl a web using React Native which has no API. It's written in PHP.
To log an user, a POST request must be sent. The response returns a cookie with a PHPSessid cookie which I must capture to use in subsequent requests.
I would like to capture the cookie value, buy the POST response is a 302 and the redirection is followed automatically, so I can't see the cookie. In node I was able to do it with redirect:manual, but it does not work in react native.
The cookie is sent automatically in subsequent requests, buy I'm trying to manage cookies by hand with react-native-cookie and I'd like to know if it's possible.
Do you know a way to stop the redirection?

I've been checking the code and what I did was the following:
Clear all cookies
Launch an empty login request
Capture the PHPSessID coookie
Launch a login request with that PHPSessID
After that, the subsequent fetch requests would have automatically a PHPSessID cookie with a valid logged in user, so we can use the site with simple fetchs
Here is some code, but the important thing is that you do a first empty login request, capture the PHPSessid and launch the real login request with that PHPSessid.
This would be the main function:
import Cookie from 'react-native-cookie';
// I think this is used only to clear the cookies
function login(user, pass){
// clear all cookies for all domains
// We need to start withouth authorization token
Cookie.clear();
const makeLoginRequest = (sessid) =>
makeLoginRequestForUserAndPass(user,pass,sessid);
return makeInitialRequest()
.then(getSessionIDFromResponse)
.then(makeLoginRequest)
.then(checkIfLoggedAndGetSessionID);
}
The initial request is a request to the login script. Note that I used GET because it worked with my site, perhaps an empty post would be necessary:
function makeInitialRequest() {
const INIT_PATH = '/index.php?r=site/login';
const INIT_URL = site + INIT_PATH;
const request = new Request(INIT_URL, options....);
return fetch(request);
}
We have the session ID in the response. I used a simple regex to extract it. Note that we are not logged in; PHP has created a session and that's what we have here:
function getSessionIDFromResponse(response) {
return getPHPSessIdFromCookie(response.headers.get('set-cookie'));
}
function getPHPSessIdFromCookie(header) {
const regex = /PHPSESSID=(\w*)/;
const match = regex.exec(header);
return match ? match[1] : '';
}
Now the login request. Note that I can't stop redirection here, but I't have to do it because we can have PHPSessid later. Redirection must be set to manual in POST request:
function makeLoginRequestForUserAndPass(user, pass, sessid) {
const request = buildLoginRequest(user, pass, sessid);
return fetch(request);
}
// This is where we build the real login request
function buildLoginRequest(user, pass, sessid) {
const LOGIN_PATH = '/index.php?r=site/login';
const LOGIN_URL = site + LOGIN_PATH;
const fields = [
{name: 'LoginForm[username]', value: user},
{name: 'LoginForm[password]', value: pass},
etc...
];
const data = translateFieldsToURLEncodedData(fields);
const headers = {
'Content-type': 'application/x-www-form-urlencoded',
Cookie: `PHPSESSID=${sessid}`, // HERE is where you put the data
};
const options = { method: 'POST',
headers: headers,
mode: 'cors',
cache: 'default',
agent: proxy,
body: data,
redirect: 'manual' // VERY IMPORTANT: if you don't do it, the cookie is lost
};
return new Request(LOGIN_URL, options);
}
// Simple utility function
function translateFieldsToURLEncodedData(fields){
let pairs = fields.map( (field) => {
return encodeURIComponent(field.name) + '=' + encodeURIComponent(field.value);
});
return pairs.join('&');
}
This is the last part. To see if I was logged in I checked if the response had text belonging to login error's page. I also got the PHPSessid (I think it changed after login, not sure, it was a year ago) but I don't know if I used it, I believe it was included automatically in subsequent requests. I think this part could be simplified an improved:
function checkIfLoggedAndGetSessionID(response) {
return (
checkIfLoggedOK(response)
.then(() => getSessionIDFromResponse(response))
);
}
function checkIfLoggedOK(response){
return getTextFromResponse(response)
.then(throwErrorIfNotLogedOk);
}
function getTextFromResponse(response) {
return response.text();
}
function throwErrorIfNotLogedOk(page) {
if(isErrorPage(page)) throw new Error("Login failed");
}
function isErrorPage(text) {
const ERROR_MESSAGE = 'Something that appears in login failed page of your site';
let n = text.search(ERROR_MESSAGE);
return n !== -1;
}
Hope this can be useful.

Related

Facebook webhook verification response structure

This is my first time posting the question so please feel to provide feedback to improve the question.
Facebook webhook mentions that the endpoint should be first verified before the webhook endpoint can receive any event notifications.
The docs for Verification Request does not provide a response structure for the API. It simply tells us to send back the hub.challenge parameter.
As I am using NodeJS, I am trying with the code below. However, it does not verify the webhook from facebook dashboard.
How should we send back the response to the verify the webhook?
app.get('/webhook', (req, res) => {
const challenge = req.query['hub.challenge'];
const verify_token = req.query['hub.verify_token'];
if (verify_token === process.env.FACEBOOK_VERIFICATION_TOKEN) {
return res.status(200).send({message: "Success", challenge: challenge});
}
return res.status(400).send({message: "Bad request!"});
})
The verification endpoint of Facebook requires the response Content-Type to be text/html. This is not mentioned on the docs;they should have provided a structure. You can set the header to use text/html explicitly.
However, when you are using express, you can directly return just the challenge value.
app.get('/webhook', (req, res) => {
const challenge = req.query['hub.challenge'];
const verify_token = req.query['hub.verify_token'];
if (verify_token === process.env.FACEBOOK_VERIFICATION_TOKEN) {
return res.status(200).send(challenge); // Just the challenge
}
return res.status(400).send({message: "Bad request!"});
})
If you are using fastify set:
res.header('Content-Type', 'text/html; charset=utf-8');
return res.send('' + challenge);

Flask JWT Extended- Different locations for access_token (header) & refresh_token(httpOnly cookie)

How to configure flask app with flask-jwt-extended for which we need something like below.
AccessToken/Bearer must sent as a Header (and not cookie)
RefreshToken must sent as httpOnlyCookie for /api/refreshtoken path only
How to set two different token one in header and one in cookie? We are able to set either both as cookie or both as a header.
Any help?
Thanks
Raxit
I wanted to do the same while building a React + Flask single page application after days of headache trying to understand authorization and authentication as I am a beginner.
Anyways, I managed to do it this way:
In Flask, config:
app.config['JWT_TOKEN_LOCATION'] = ['headers', 'cookies']
app.config['JWT_REFRESH_COOKIE_PATH'] = '/auth/refresh'
And what I return in my login function:
resp = jsonify({'access_token': access_token})
set_refresh_cookies(resp, refresh_token)
return resp, 200
And in my refresh function:
# Refresh access token
#app.route('/auth/refresh', methods=['POST'])
#jwt_refresh_token_required
def refresh():
user = get_jwt_identity()
resp = {
'access_token': create_access_token(
identity={
'username': user['username'],
'role': user['role']
},
expires_delta=timedelta(seconds=600),
user_claims=user['role']
)
}
return jsonify(resp), 200
And on the front side, I collect the JSON access_token and set it in memory and use withCredentials to send the refresh_token with my API calls.
axios.defaults.withCredentials = true;
axios.defaults.headers.common['Authorization'] = `Bearer ${access_token}`;
more precisely:
.then(({ data: { access_token } }) => {
axiosHttp.defaults.headers.common['Authorization'] = `Bearer ${access_token}`;
return jwt_decode(access_token);
})
then I use the data from my decoded access_token in a React Context Component to authenticate access to pages depending on roles.
logout is simply setting to null my context and calling the api to unset the refresh cookie
#app.route('/auth/logout', methods=['DELETE'])
#jwt_required
def logout():
resp = jsonify({"msg": "Successfully logged out"})
unset_jwt_cookies(resp)
return resp, 200
it's quite simple in the end but it took me quite a while to figure out!

How to specify redirectUrl after logout for Ambassador OAuth2 Filter with Keycloak?

I'm using the Ambassador OAuth2 Filter to perform OAuth2 authorization against Keycloak.
For the logout I use the the RP-initiated logout as described in the Docs of Ambassador
The logout works fine. However I could not figure out how to provide the redirect url needed for Keycloak to redirect to the Login page after successfully logged out. As a result the user stays on the blank logout page of keycloak.
The RP-initiated logout looks as follows
const form = document.createElement('form');
form.method = 'post';
form.action = '/.ambassador/oauth2/logout?realm='+realm;
const xsrfInput = document.createElement('input');
xsrfInput.type = 'hidden';
xsrfInput.name = '_xsrf';
xsrfInput.value = getCookie("ambassador_xsrf."+realm);
form.appendChild(xsrfInput);
document.body.appendChild(form);
form.submit();
I expected that Ambassador provides a way to add the redirect url as a query param or something, but I couldn't find a solution.
Are there any suggestions or workarounds?
I found this in the Ambassador documentation that could be overlooked as I did several times:
Ambassador OAuth2 Settings
protectedOrigins: (You determine these, and must register them with your identity provider) Identifies hostnames that can appropriately set cookies for the application. Only the scheme (https://) and authority (example.com:1234) parts are used; the path part of the URL is ignored.
You will need to register each origin in protectedOrigins as an authorized callback endpoint with your identity provider. The URL will look like {{ORIGIN}}/.ambassador/oauth2/redirection-endpoint.
So it looks like ambassador hard codes the redirection-endpoint (redirect_uri) that you need add to your OAuth2 client in Keycloak.
I found a solution for that, is not the best solution but you will logout using a button.
async function logout() {
const data = new URLSearchParams("realm=keycloak-oauth2-filter.ambassador")
data.append('_xsrf', getCookie("ambassador_xsrf.keycloak-oauth2-filter.ambassador"));
fetch('/.ambassador/oauth2/logout', {
method: 'POST',
body: data
})
.then(function (response) {
if (response.ok) {
return response.text()
} else {
throw "err";
}
})
.then(function (text) {
console.log(text);
})
.catch(function (err) {
console.log(err);
});
}

How to fetch access token from OAuth used in google action for smart home

For my smart home action I used fake auth as shown in codelab- smartwasher application. (For testing purpose ). The app is working fine. I have build my own code to work with my devices(Switches). Now When I am implementing OAuth which uses my own custom OAuth server. I am not able to figure out how to implement it in my code. The OAuth is working as needed when I tested. But I want help in integrating it with google action. I am facing problem fetching access token.
The code is as follows:
exports.fakeauth = functions.https.onRequest((request, response) => {
const responseurl = util.format('%s?code=%s&state=%s',
decodeURIComponent(request.query.redirect_uri), request.query.code,
request.query.state);
console.log('*********'+responseurl);
return response.redirect(responseurl);
});
exports.faketoken = functions.https.onRequest((request, response) => {
const grantType = request.query.grant_type
? request.query.grant_type : request.body.grant_type;
const secondsInDay = 86400; // 60 * 60 * 24
const HTTP_STATUS_OK = 200;
console.log(`Grant type ${grantType}`);
let obj;
if (grantType === 'authorization_code') {
obj = {
token_type: 'bearer',
access_token: '123access',
refresh_token: '123refresh',
expires_in: secondsInDay,
};
} else if (grantType === 'refresh_token') {
obj = {
token_type: 'bearer',
access_token: '123access',
expires_in: secondsInDay,
};
}
response.status(HTTP_STATUS_OK)
.json(obj);
console.log('********** TOKEN **********',response);
});
The above code executes with fake auth.
Why is is not executing when I am implmenting custom OAuth?
Do I need to do any changes for clienID and secret in firebase?
How to fetch access token returned by OAuth?
Kindly help. I am new to node.js.
The authorization code that will come back in requests will be in the header, as an Authorization field. Here's a way to pull it out using Node.js.
function getToken(headers) {
// Authorization: "Bearer 123ABC"
return headers.authorization.substr(7);
}

Passport and SailsJS, how to get isAuthenticated() test to pass

My code keeps failing here when the user tries to login:
isAuthenticated: function (req, res) {
if (req.isAuthenticated()) { return res.json(req.user); }
else { return res.send(401); }
},
It FAILS and I get GET http://localhost:1337/user/authenticated 401 (Unauthorized) in the console, even though the user has entered in a correct email and password.
Where in the code makes that test pass?
I have the related StackOverflow question with more info HERE.
The problem was that my frontend application has a different origin than my backend application, so the AJAX requests will not include the session cookie and req.isAuthenticated() will never return true.
Use the withCredentials options to force it.
$http({ withCredentials: true, ... })