I'm using apollo-server-express with apollo-rest-datasources. I'm trying to return multiple set-cookie header in response header to my client from formatResponse method
set-cookie: key1=value1
set-cookie: key2=value2
But the whenever I pass in array it just stringifies it and makes it comma separated
This is my formatResponse method
formatResponse: (response, requestContext) => {
Object.entries(requestContext.context.response.headers).forEach(([key, value]) => {
requestContext.response.http.headers.set(key, value);
});
This is my context object
context: ({ req, res }) => {
return {
response: {
headers: {
'set-cookie': ['key1=value1', 'key2=value2']
},
},
res,
};
},
the final response I get is of this form
set-cookie: key1=value1, key2=value2 and the browser can't recognise that there are 2 cookies to set and it just sets key1=value1 in cookies.
I use res of express which passed into apollo server context to set multiple cookies. E.g.
import { ApolloServer, gql } from 'apollo-server-express';
import express from 'express';
const typeDefs = gql`
type Query {
dummy: String!
}
`;
const resolvers = {
Query: {
dummy: () => 'hello world',
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req, res }) => {
return {
response: {
headers: {
'set-cookie': ['key1=value1', 'key2=value2'],
},
},
res,
};
},
formatResponse: (response, requestContext: any) => {
// not working
// requestContext.response!.http!.headers.set('set-cookie', 'key1=value1');
// requestContext.response!.http!.headers.set('set-cookie', 'key2=value2');
// works fine
requestContext.context.res.set('set-cookie', ['key1=value1', 'key2=value2']);
return response!;
},
});
const app = express();
server.applyMiddleware({ app });
app.get('/', (req, res) => {
res.sendFile('index.html', { root: __dirname });
});
app.listen({ port: 4000 }, () => console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`));
Get the response header from browser:
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
set-cookie: key1=value1
set-cookie: key2=value2
Content-Type: application/json; charset=utf-8
Content-Length: 33
ETag: W/"21-wOMEaw/ExA+LOH3PCuP0vilR+4w"
Date: Wed, 22 Apr 2020 05:30:20 GMT
Connection: keep-alive
Get cookies by document.cookie:
document.cookie
"key1=value1; key2=value2"
source code: https://github.com/mrdulin/apollo-graphql-tutorial/tree/master/src/stackoverflow/61183199
Related
I have build a React Redux app. It works fine in localhost in every browser(chrome,edge,firefox). But the Heroku deployed app doesn't works in Edge, Firefox , although it worked in chrome perfectly.
My app doesn't seems to send request and receive response ,because i noticed that the login button spinner keeps on spinning because it waits for response. So i think that no response is received from backend.
Following are the conclusion i made after test in different browser:
it works perfectly in every browser in localhost development mode.
After Deploying , herokuapp works perfectly fine in chrome only.
Herokuapp doesn't work in edge , Firefox.
Same is the issue with other system(Friends PC).
It doesn't work in any browser except chrome in Mobile device
After console logging and banging my head for hours i got the following error in microsoft edge:
Access to XMLHttpRequest at 'https://ecrypt.herokuapp.com/user/login' from origin 'http://ecrypt.herokuapp.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The 'Access-Control-Allow-Origin' header has a value 'https://ecrypt.herokuapp.com' that is not equal to the supplied origin.
2.2c59d01c.chunk.js:2 POST https://ecrypt.herokuapp.com/user/login net::ERR_FAILED
Okay, so i figured out that there is some problem with CORS.
Following is my code
Frontend:
import axios from "axios";
const API = axios.create({
baseURL: "https://ecrypt.herokuapp.com",
// withCredentials: false,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET,PUT,POST,DELETE,PATCH,OPTIONS",
"Access-Control-Allow-Headers": "Origin, Content-Type, X-Auth-Token",
},
});
// const API = axios.create({ baseURL: "http://localhost:9000" });
const cloudinaryAPI = axios.create({
baseURL: "https://api.cloudinary.com/v1_1/ecryptimgdb",
});
//register new user
export const registerNewUser = (formData) =>
API.post("/user/register", formData);
//Account Activation through Email
export const activation = (activation_token) =>
API.post("/user/activation", {
data: {
activation_token,
},
});
//Login
export const login = (formData) =>
API.post("/user/login", formData, { withCredentials: true });
//get Token
export const getToken = () =>
API.post("/user/refresh_token", null, { withCredentials: true });
//Logout
export const logout = () => API.get("/user/logout", { withCredentials: true });
//get User
export const getUser = (token) =>
API.get("/user/info", {
headers: { Authorization: `${token}` },
});
//PROFILE SETTINGS__________________________________________________________________________________________
export const editProfile = (token, profileData) =>
API.post(
"/user/updateProfile",
{ profileData },
{
headers: { Authorization: `${token}` },
}
);
//forgot password____
export const forgotPass = (email) =>
API.post("/user/forgotPassword", { email });
//reset password_____
export const resetPass = (token, password) =>
API.post(
"/user/resetPassword",
{ password },
{
headers: { Authorization: `${token}` },
}
);
//change password____
export const changePass = (oldPassword, newPassword, token) =>
API.post(
"/user/changePassword",
{ oldPassword, newPassword },
{
headers: { Authorization: `${token}` },
}
);
BACKEND:
//IMPORTS
require("dotenv").config();
const express = require("express");
const bodyParser = require("body-parser");
const mongoose = require("mongoose");
const cors = require("cors");
const cookiesParser = require("cookie-parser");
const path = require("path");
const app = express();
// app.use(cors({ credentials: true, origin: "http://localhost:3000" }));
app.use(cors({ credentials: true, origin: "https://ecrypt.herokuapp.com" }));
app.use(cookiesParser());
// app.use(bodyParser.json({ limit: "30mb", extended: true }));
// app.use(bodyParser.urlencoded({ limit: "30mb", extended: true }));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
const CONNECTION_URL = process.env.MONGODB_URL;
// const CONNECTION_URL = process.env.MONGODB_LOCAL_URL;
const PORT = process.env.PORT || 9000;
//MONGODB CLOUD DATABASE CONNECTION________________________
mongoose
.connect(CONNECTION_URL, {
useCreateIndex: true,
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: false,
})
.then(() => console.log("Connected to Database :: MongoDB Cloud"))
.catch((err) => console.log(err.message));
// app.use("/", routesIndex);
app.use("/", require("./routes/index"));
if (process.env.NODE_ENV === "production") {
app.use(express.static("client/build"));
app.get("*", (req, res) => {
res.sendFile(path.join(__dirname, "client", "build", "index.html"));
});
}
//SERVER LISTENING
app.listen(PORT, (err) => {
if (err) {
console.log(err.message);
} else {
console.log(`Listening on localhost:${PORT}`);
}
});
NOTE
I am using cookies to store token so i needed withCredentials and Authorization headers.
The following headers should be sent by the server (instead of the front)
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET,PUT,POST,DELETE,PATCH,OPTIONS",
"Access-Control-Allow-Headers": "Origin, Content-Type, X-Auth-Token",
},
Try removing headers from your axios request. I don't think those headers are allowed.
Ok, so i figured it out ,in my case i was setting headers as
{
headers: { Authorization: `${token}` },
}
Instead of setting it like above a slight change just worked for me:
{
headers: { Authorization: `Bearer ${token}` },
}
and at backend side in index.js or server.js whatever your file name is ,use cors middle ware like this:
const cors = require("cors");
app.use(
cors({
origin: ["https://blahblah.herokuapp.com", "http://localhost:****"],
credentials: true,
})
);
Note: credentials true if you want to pass cookies and access them at server side.
In my case i wanted to access the HttpOnly cookies at server side.
I'm using Nock to intercept a http request.
test.js:
const nock = require('nock');
const server = require('../server');
const request = require('request');
describe('My test', () =>{
it('Should returns the customized header', () => {
nock('url')
.get('/test')
.reply(302, {
'user-agent': 'Mozilla'
})
const { headers } = await request(server).get('/test');
expect(headers['user-agent']).to.include('Mozilla');
}
})
When I run the test, it fails and the log of headers received by request is like that:
{
'user-agent': 'node-superagent/3.8.3',
location: 'undefined',
vary: 'Accept, Accept-Encoding',
'content-type': 'text/plain; charset=utf-8',
'content-length': '31',
date: 'Fri, 24 May 2019 09:15:46 GMT',
connection: 'close'
}
Do I missed something or it's the normal behaviour of Nock?
The issue is with the way you're passing the headers to the reply function. Headers are the third argument for that method, but you're providing them as the second arg which means the object with the the user-agent key is being used as the body. Since it's a 302 and you probably want an empty body, you should pass an empty string as the second arg.
nock('url')
.get('/test')
.reply(302, '', {
'user-agent': 'Mozilla'
})
Related docs.
I have this response with httpie when an user is logged:
chat-api$ http :3000/signup username=tomatito password=123
HTTP/1.1 201 Created
Cache-Control: max-age=0, private, must-revalidate
Content-Type: application/json; charset=utf-8
ETag: W/"01dfe24bd7415e252b5aee50e12198a3"
Transfer-Encoding: chunked
Vary: Origin
X-Request-Id: a095148b-592a-4347-820f-63e1efa0e409
X-Runtime: 0.347726
{
"auth_token": "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjo1LCJleHAiOjE1MjEzMTg4NDV9.45JDA7vk-K8gUzCB1xABKMifi-IWGoVESedKykGiqGo",
"message": "Account created successfully"
}
The object is persisted in my database.
However when i make this request with axios from my vue.js form I get nothing in localStorage
this is my axios.js code:
import axios from 'axios'
const API_URL = process.env.API_URL || 'http://localhost:3000/'
export default axios.create({
baseURL: API_URL,
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + localStorage.auth_token
}
})
the object is persisted in database right but i get Authorization:Bearer undefined
these are my headers:
Response:
Access-Control-Allow-Methods:GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD
Access-Control-Allow-Origin:http://localhost:8081
Access-Control-Expose-Headers:
Access-Control-Max-Age:1728000
Cache-Control:max-age=0, private, must-revalidate
Content-Type:application/json; charset=utf-8
ETag:W/"fdac439f3ada9e343d0815bb49dff277"
Transfer-Encoding:chunked
Vary:Origin
X-Request-Id:9e318050-ceca-480c-a847-d59f9ebb18b7
X-Runtime:0.447976
Request:
Accept:application/json, text/plain, */*
Accept-Encoding:gzip, deflate, br
Accept-Language:en-US,en;q=0.9
Authorization:Bearer undefined
Connection:keep-alive
Content-Length:44
Content-Type:application/json
Host:localhost:3000
Origin:http://localhost:8081
Referer:http://localhost:8081/
User-Agent:Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.
Request payload
{username: "tomatito", password: "123456"}
password:"123456"username:"tomatito"
This is my vue script component:
<script>
export default {
name: 'SignUp',
data () {
return {
username: '',
password: '',
error: false
}
},
methods: {
signup () {
this.$http.post('/signup', { username: this.username, password: this.password })
.then(request => this.signupSuccessful(request))
.catch(() => this.signupFailed())
},
signupSuccessful (req) {
if (!req.data.token) {
this.signupFailed()
return
}
localStorage.token = req.data.token
this.error = false
this.$router.replace(this.$route.query.redirect || '/rooms')
},
signupFailed () {
this.error = 'Sign up failed!'
delete localStorage.token
}
}
}
</script>
I'm getting Sign up failed, However the object is persisted in database. My back-end is ruby on rails. How can i receive in my data.token in payload?
This is my main.js file
import Vue from 'vue'
import App from './App'
import router from './router'
import axios from './backend/vue-axios'
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
components: { App },
axios,
template: '<App/>'
})
This is my vue-axios/index.js file:
import Vue from 'vue'
import VueAxios from 'vue-axios'
import axios from './axios'
Vue.use(VueAxios, axios)
Updated The problem was in req. it's res to receive the token instead of req
signup() {
this.$http
.post("/signup", { username: this.username, password: this.password })
.then(res => this.signupSuccessful(res))
.catch(() => this.signupFailed());
},
signupSuccessful(res) {
if (!res.data.auth_token) {
this.signupFailed();
return;
}
this.error = false;
localStorage.token = res.data.auth_token;
this.$store.dispatch("login");
this.$router.replace(this.$route.query.redirect || "/rooms");
},
.
.
.
.
Thank you
Try to set the token on every request with axios.interceptors
Put this on the main.js file so everywhere you import axios will have the config
axios.interceptors.request.use(config => {
const token= localStorage.getItem('my-token-key') // or where you have the token
config.headers.common['Authorization'] = 'Bearer ' + token
// you must return the config or it will not work
return config
})
I think that the problem is that axios.create instance is executed (created) just 1 time (then reference it), and not every time you import it, so if there was no token when the instance was created it will not work
Updated The problem was in req. it's res to receive the token instead of req
signup() {
this.$http
.post("/signup", { username: this.username, password: this.password })
.then(res => this.signupSuccessful(res))
.catch(() => this.signupFailed());
},
signupSuccessful(res) {
if (!res.data.auth_token) {
this.signupFailed();
return;
}
this.error = false;
localStorage.token = res.data.auth_token;
this.$store.dispatch("login");
this.$router.replace(this.$route.query.redirect || "/rooms");
},
.
.
.
.
I am using express js in backend and doing fetch in frontend. My frontend is hosted by webpack-dev-sever which is running locally on port 3000. My express js server is running locally on port 3001. I need to send some custom headers along-with my request.
My express js server is using 'morgan' for logging on terminal. The code of my server looks like this:
const express = require('express')
const bodyParser = require('body-parser')
const morgan = require('morgan')
const fs = require('fs')
const path = require('path')
const mime = require('mime')
const http = require('http')
const cors = require('cors')
const server = express();
let whitelist = [
'http://localhost:3000',
];
var corsOptions = {
origin: function(origin, callback){
var originIsWhitelisted = whitelist.indexOf(origin) !== -1;
callback(null, originIsWhitelisted);
},
credentials: true
};
server.use(cors(corsOptions));
server.use(bodyParser());
server.use(morgan('dev'));
server.use(function (req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Content-Type, x-imei, X-API-Key, requestId, Authorization');
res.header('Access-Control-Allow-Methods', '*');
next()
})
server.post('/verify/identifier', function(req, res, next) {
console.log(req.body)
console.log(req.headers)
res.send({"responseCode":"0012"});
});
server.listen(port, function() {
console.log(`server hosted on port ${port}`)
})
My frontend code is this:
export function fetchMerchantApi(url, body) {
let reqObj = {
method: 'POST',
headers: {
"Content-Type": "application/json",
"X-API-Key": secret_key,
"requestId": getUniqueId()
},
cache: 'default',
mode: 'cors'
}
try {
return new Promise((res, rej) => {
fetch(url, reqObj).then(result => {
return result.json()
}).then(data => {
res(data)
});
})
} catch(e) {
throw new Error(e);
}
}
fetchMerchantApi("http://localhost:3001/verify/identifier", reqBody).then(res => {
console.log(res);
})
All imports, syntax, etc are correct.
The chrome debugger tool's network tab is this:
The logs in terminal of server:
body
[0] { customField1: 'Y',
[0] imei: '358967064854480',
[0] isMerchantExist: 'Y',
[0] mobileNo: '9999999999',
[0] serialNumber: 'ZY22285JKV' }
headers:
[0] { host: 'localhost:3001',
[0] connection: 'keep-alive',
[0] 'content-length': '119',
[0] 'x-imei': '358967064854480',
[0] origin: 'http://localhost:3000',
[0] requestid: '9999999999300513303519185',
[0] 'content-type': 'application/json',
[0] 'user-agent': 'Mozilla/5.0 (Linux; Android
6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Mobile Safari/537.36',
[0] 'x-api-key': 'l7xx5a9a4eea46054ef38b18b5e6fdbd2c5a',
[0] accept: '/',
[0] referer: 'http://localhost:3000/auth/verifyNo',
[0] 'accept-encoding': 'gzip, deflate, br',
[0] 'accept-language': 'en-IN,en-GB;q=0.8,en-US;q=0.6,en;q=0.4' }
morgain log:
[0] POST /cr/v2/merchant/verify/identifier 200 2.432 ms - 23
After all this, I am getting no error and also no response data from backend.
Funny thing is that my rest client(Postman) works fine. I get data on postman.
This is a tricky one . It is an options request, that is a preflight request from the browser. The options request contains two http headers Access-Control-Request-Headers' andAccess-Control-Method` .
But it looks like you allowing every method in cors options.
you can enable pre flight requests like
app.options('*', cors())
I have an application build with koa and koa-router. When testing routes with supertest I face a problem, that content-type response header is always application/json; charset=utf-8.
const app = koa();
router
.get('/img', function *(next) {
this.type = 'image/png';
// this.set('Content-Type', 'image/png');
// this.set('content-type', 'image/png');
this.body = renderImage();
});
app
.use(router.routes())
.use(router.allowedMethods());
describe('Routes', () => {
it('should handle /tiles/*/*/*/* requests', (done) => {
request(http.createServer(app.callback()))
.get('/img')
.expect(200)
.expect('Content-Type', 'image/png')
.end(function (err, res) {
console.log(res.res.headers);
if (err) return done(err);
expect(renderImage).to.be.called;
done();
});
});
How test fails:
Error: expected "Content-Type" of "image/png", got "application/json; charset=utf-8"
at Test._assertHeader (node_modules/supertest/lib/test.js:215:12)
at Test._assertFunction (node_modules/supertest/lib/test.js:247:11)
at Test.assert (node_modules/supertest/lib/test.js:148:18)
at Server.assert (node_modules/supertest/lib/test.js:127:12)
at emitCloseNT (net.js:1525:8)
What is logged via console.log(res.res.headers):
{ 'content-type': 'application/json; charset=utf-8',
'content-length': '2',
date: 'Wed, 09 Mar 2016 10:15:37 GMT',
connection: 'close' }
Yet, if I do a request from the browser to the provided route, content-type header is changed correctly:
Connection:keep-alive
Content-Length:334
Content-Type:image/png
Date:Wed, 09 Mar 2016 10:15:01 GMT
Neither this.set('Content-Type', 'image/png'); nor this.set('content-type', 'image/png'); changes the situation.
Is it a bug? Has anyone faced the same issue?
A couple things to try:
Is this.body = renderImage() actually setting the body to null or undefined?
When looking through the Koa.js code for the response object, it looks like koa is removing the content-type header if the body is set to null or undefined.
Is the return value of renderImage() an object? If so, is it a buffer or stream? When the body is set koa tries to detect what the content-typeof the response should be. If it is not a string, Buffer, or stream, koa forces the content-type header to be application/json.