React Client Code - Using request promises to send username and password in Header
var password = values.password;
var email = values.email;
request
.head(
"https://us-central1-simplineet-754e8.cloudfunctions.net/CreateUserAuth"
)
.set('Content-Type', 'application/x-www-form-urlencoded')
.auth(email, password, false)
.query(dataobj)
.then(res => {
console.log(res);
if (res.statusCode === 200) {
console.log("statusText",res.body);
} else {
console.log("statusText",res.statusText);
}
})
.catch(err => {});
Backend - Google Cloud Function to Handle Basic Auth Requests from Client
const express = require('express');
const app = express();
const cors = require('cors');
app.use(cors({origin: true}));
exports.CreateUserAuth = functions.https.onRequest((request, response) => {
var corsFn = cors();
corsFn(request, response, function () {
// Request Header
response.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
response.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
response.setHeader('Access-Control-Allow-Credentials', true);
response.setHeader('Access-Control-Allow-Origin', '*');
var auth = require('basic-auth') // basic-auth NPM package to extract username and password from header
var user = auth(request)
var email = user.name; // Getting username from Auth
var password = user.pass; // Getting password from Auth
var username = request.query.username;
response.send('Hello from Firebase!'); // Not getting this response in Client
});
});
Response Getting in Client :
Response {req: Request, xhr: XMLHttpRequest, text: null, statusText: "", statusCode: 200, …}
As per MDN docs, HEAD responses should not have a body:
The HTTP HEAD method requests the headers that are returned if the specified resource would be requested with an HTTP GET method. Such a request can be done before deciding to download a large resource to save bandwidth, for example.
A response to a HEAD method should not have a body. If so, it must be ignored. Even so, entity headers describing the content of the body, like Content-Length may be included in the response. They don't relate to the body of the HEAD response, which should be empty, but to the body of similar request using the GET method would have returned as a response.
My guess is that GCP is handling it as a GET and stripping out the body before returning a response.
However, keep in mind that Google Cloud Functions HTTP trigger docs don't explicitly say that HEAD is a supported method:
You can invoke Cloud Functions with an HTTP request using the POST, PUT, GET, DELETE, and OPTIONS HTTP methods.
It looks like you are making a HEAD request instead of a POST request. Change to request.post() and it should work
Related
In my Postman collection, I have a pre-request script that ensures I have a valid JWT token available for authentication. It looks similar to the following (I have removed the logic of checking expiration and only fetching a new token if needed):
function get_and_set_jwt() {
let base_url = pm.environment.get("BASE_URL")
pm.sendRequest({
url: base_url + '/api/auth/',
method: 'POST',
header: {
'content-type': 'application/json',
'cookie': ''
},
body: {
mode: 'raw',
raw: JSON.stringify({ email: pm.environment.get("USER_EMAIL_ADDRESS"), password: pm.environment.get("USER_PASSWORD") })
}
}, function (err, res) {
let jwt = res.json().token
postman.setEnvironmentVariable("JWT", jwt)
});
}
get_and_set_jwt();
I am attempting to set 'cookie': '' so that the request from this script will be made with no cookies. The backend I am working with sets a session cookie in addition to returning the JWT, but I want to force all future requests (when I need to renew the JWT) to not include that session information.
Unfortunately, if I check the Postman console, I see that the requests are still being sent with the cookie header, and the session cookie that was set by the earlier response. I have even tried overriding it by setting 'cookie': 'sessionid=""', but that just yields a request that includes two session ids in the cookie header (it looks like sessionid=""; sessionid=fasdflkjawew123sdf123;)
How can I send a request with pm.sendRequest with either a completely blank cookie header, or without the header at all?
Okay, I have the bad feeling that I'm missing a key concept in what I'm doing. Hope someone can help me out with a hint.
I'm using Nuxt and Vuex Store Modules. Every fetch a Module Action does is wrapped in a helper Function (saveFetch) that I imported to decrease repetitive code, like this:
export const actions = {
async sampleAction(context, data){
...
await saveFetch(context, 'POST', '/pages', data)
...
}
}
The helper simple checks if the users accessToken is still valid, refreshes it if not and then sends the request:
export const saveFetch = async (context, method = 'POST', path, data) => {
const accessTokenExpiry = context.rootGetters['auth/getAccessTokenExpiry']
let accessToken = context.rootGetters['auth/getAccessToken']
// If the client provides an accessToken and the accessToken is expired,
// refresh the token before making the "real" fetch.
if (accessToken && accessTokenExpiry < new Date() && path !== '/auth/refresh-token') {
if (process.client) {
// Works fine
await window.$nuxt.$store.dispatch('auth/refreshToken')
} else {
// This is where the trouble starts
await context.dispatch('auth/refreshToken', null, { root: true })
}
accessToken = rootGetters['auth/getAccessToken']
}
return fetch(path, {
method,
headers: { ... },
body: JSON.stringify(data),
}
}
If the accessToken is expired the helper function dispatches a Vuex Action to refresh it. This works well on the client side, but not if that process happens on the server side.
The Problem that's coming up on the server side is, that the user has to provide a refreshToken to get a refreshed accessToken from the API. This refreshToken is stored as a HttpOnly Cookie in the Client. When logging the Nuxt request details on the API side of things I noticed, that Nuxt is not sending that cookie.
My current workaround looks like this:
export const actions = {
async refreshToken(context){
...
let refreshToken
if (process?.server && this?.app?.context?.req?.headers?.cookie) {
const parsedCookies = cookie.parse(
this.app.context.req.headers.cookie
)
refreshToken = parsedCookies?.refreshToken
}
const response = await saveFetch(context, 'POST', '/auth/refresh-token', {
refreshToken,
})
...
}
...
}
If on server side, access the req object, get the cookies from it and send the refreshToken Cookie Content in the requests body.
This looks clearly bad to me and I would love to get some feedback on how to do this better. Did I maybe miss some key concepts that would help me not get into this problem in the first place?
I'm trying to crawl our local Confluence installation with the PuppeteerCrawler. My strategy is to login first, then extracting the session cookies and using them in the header of the start url. The code is as follows:
First, I login 'by foot' to extract the relevant credentials:
const Apify = require("apify");
const browser = await Apify.launchPuppeteer({sloMo: 500});
const page = await browser.newPage();
await page.goto('https://mycompany/confluence/login.action');
await page.focus('input#os_username');
await page.keyboard.type('myusername');
await page.focus('input#os_password');
await page.keyboard.type('mypasswd');
await page.keyboard.press('Enter');
await page.waitForNavigation();
// Get cookies and close the login session
const cookies = await page.cookies();
browser.close();
const cookie_jsession = cookies.filter( cookie => {
return cookie.name === "JSESSIONID"
})[0];
const cookie_crowdtoken = cookies.filter( cookie => {
return cookie.name === "crowd.token_key"
})[0];
Then I'm building up the crawler structure with the prepared request header:
const startURL = {
url: 'https://mycompany/confluence/index.action',
method: 'GET',
headers:
{
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7',
Cookie: `${cookie_jsession.name}=${cookie_jsession.value}; ${cookie_crowdtoken.name}=${cookie_crowdtoken.value}`,
}
}
const requestQueue = await Apify.openRequestQueue();
await requestQueue.addRequest(new Apify.Request(startURL));
const pseudoUrls = [ new Apify.PseudoUrl('https://mycompany/confluence/[.*]')];
const crawler = new Apify.PuppeteerCrawler({
launchPuppeteerOptions: {headless: false, sloMo: 500 },
requestQueue,
handlePageFunction: async ({ request, page }) => {
const title = await page.title();
console.log(`Title of ${request.url}: ${title}`);
console.log(page.content());
await Apify.utils.enqueueLinks({
page,
selector: 'a:not(.like-button)',
pseudoUrls,
requestQueue
});
},
maxRequestsPerCrawl: 3,
maxConcurrency: 10,
});
await crawler.run();
The by-foot-login and cookie extraction seems to be ok (the "curlified" request works perfectly), but Confluence doesn't accept the login via puppeteer / headless chromium. It seems like the headers are getting lost somehow..
What am I doing wrong?
Without first going into the details of why the headers don't work, I would suggest defining a custom gotoFunction in the PuppeteerCrawler options, such as:
{
// ...
gotoFunction: async ({ request, page }) => {
await page.setCookie(...cookies); // From page.cookies() earlier.
return page.goto(request.url, { timeout: 60000 })
}
}
This way, you don't need to do the parsing and the cookies will automatically be injected into the browser before each page load.
As a note, modifying default request headers when using a headless browser is not a good practice, because it may lead to blocking on some sites that match received headers against a list of known browser fingerprints.
Update:
The below section is no longer relevant, because you can now use the Request class to override headers as expected.
The headers problem is a complex one involving request interception in Puppeteer. Here's the related GitHub issue in Apify SDK. Unfortunately, the method of overriding headers via a Request object currently does not work in PuppeteerCrawler, so that's why you were unsuccessful.
I did cors configuration in response header of my api correctly.
when I tested my api via postman with a validated token
attached in header('Authorization'), it returned 200.
i checked my frontend fetch code to request that api, it seems that there is no error or fault.
how can it happen? does anyone who suffered from same as what I'm struggling now.
Added:
my front end fetch code looks like this.
export const getDoc = async (docId, token) => {
const path = `${apiGateway.URL}`;
const body = {
docId: docId
};
const headers = {
Authorization: token,
'Content-Type': 'application/json'
};
const result = await fetch(path, {
body,
headers,
});
if (result.status !== 200) {
throw new Error('failed to get doc');
}
return result.json();
};
you should just enter "Authorization" into the "Token Source" field, NOT 'method.request.headers.Authorization'. Otherwise, you will get a 401 error.
I am trying out the new Fetch API but is having trouble with Cookies. Specifically, after a successful login, there is a Cookie header in future requests, but Fetch seems to ignore that headers, and all my requests made with Fetch is unauthorized.
Is it because Fetch is still not ready or Fetch does not work with Cookies?
I build my app with Webpack. I also use Fetch in React Native, which does not have the same issue.
Fetch does not use cookie by default. To enable cookie, do this:
fetch(url, {
credentials: "same-origin"
}).then(...).catch(...);
In addition to #Khanetor's answer, for those who are working with cross-origin requests: credentials: 'include'
Sample JSON fetch request:
fetch(url, {
method: 'GET',
credentials: 'include'
})
.then((response) => response.json())
.then((json) => {
console.log('Gotcha');
}).catch((err) => {
console.log(err);
});
https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials
Have just solved. Just two f. days of brutforce
For me the secret was in following:
I called POST /api/auth and see that cookies were successfully received.
Then calling GET /api/users/ with credentials: 'include' and got 401 unauth, because of no cookies were sent with the request.
The KEY is to set credentials: 'include' for the first /api/auth call too.
If you are reading this in 2019, credentials: "same-origin" is the default value.
fetch(url).then
Programmatically overwriting Cookie header in browser side won't work.
In fetch documentation, Note that some names are forbidden. is mentioned. And Cookie happens to be one of the forbidden header names, which cannot be modified programmatically. Take the following code for example:
Executed in the Chrome DevTools console of page https://httpbin.org/, Cookie: 'xxx=yyy' will be ignored, and the browser will always send the value of document.cookie as the cookie if there is one.
If executed on a different origin, no cookie is sent.
fetch('https://httpbin.org/cookies', {
headers: {
Cookie: 'xxx=yyy'
}
}).then(response => response.json())
.then(data => console.log(JSON.stringify(data, null, 2)));
P.S. You can create a sample cookie foo=bar by opening https://httpbin.org/cookies/set/foo/bar in the chrome browser.
See Forbidden header name for details.
Just adding to the correct answers here for .net webapi2 users.
If you are using cors because your client site is served from a different address as your webapi then you need to also include SupportsCredentials=true on the server side configuration.
// Access-Control-Allow-Origin
// https://learn.microsoft.com/en-us/aspnet/web-api/overview/security/enabling-cross-origin-requests-in-web-api
var cors = new EnableCorsAttribute(Settings.CORSSites,"*", "*");
cors.SupportsCredentials = true;
config.EnableCors(cors);
This works for me:
import Cookies from 'universal-cookie';
const cookies = new Cookies();
function headers(set_cookie=false) {
let headers = {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
};
if (set_cookie) {
headers['Authorization'] = "Bearer " + cookies.get('remember_user_token');
}
return headers;
}
Then build your call:
export function fetchTests(user_id) {
return function (dispatch) {
let data = {
method: 'POST',
credentials: 'same-origin',
mode: 'same-origin',
body: JSON.stringify({
user_id: user_id
}),
headers: headers(true)
};
return fetch('/api/v1/tests/listing/', data)
.then(response => response.json())
.then(json => dispatch(receiveTests(json)));
};
}
My issue was my cookie was set on a specific URL path (e.g., /auth), but I was fetching to a different path. I needed to set my cookie's path to /.
If it still doesn't work for you after fixing the credentials.
I also was using the :
credentials: "same-origin"
and it used to work, then it didn't anymore suddenly, after digging much I realized that I had change my website url to http://192.168.1.100 to test it in LAN, and that was the url which was being used to send the request, even though I was on http://localhost:3000.
So in conclusion, be sure that the domain of the page matches the domain of the fetch url.