how to send the cookie from Apollo Studo - graphql

my environment:
Apollo server version 3.8.1
Apollo express version 3.8.1
I allowed the cors
server.applyMiddleware({
app,
cors: {
credentials: true,
origin: [
`http://localhost:${config.gql.port}/graphql`,
'https://studio.apollographql.com'
]
},
onHealthCheck: () => healthCheck()
});
I set the trust proxy
export const app = express();
if (process.env.NODE_ENV !== 'production') {
app.set("trust proxy", true);
console.log("set trust proxy");
}
I opened the Including cookie from the Apollo studio.
I set the header
"x-forwarded-proto": "https"
But when I use ctx.req.cookies, I got undefined
I try from this blog.
https://blog.devgenius.io/graphql-apollo-studio-and-cookies-5d8519d0ca7e

Related

Graphql subscription in playground during local development throwing "Could not connect to websocket endpoint" in basic nestjs project

This is happening on a simple project during local development, so cloud infrastructure isn't an issue.
This is also happening in the application playground.
My module registration:
GraphQLModule.forRootAsync<ApolloDriverConfig>({
driver: ApolloDriver,
imports: [YeoConfigModule],
useFactory: (configService: YeoConfigService<AppConfig>) => {
const config: ApolloDriverConfig = {
debug: true,
subscriptions: {
'graphql-ws': true,
},
playground: true,
autoSchemaFile: './apps/event-service/schema.gql',
sortSchema: true,
context: ({ req, res }) => ({ req, res }),
};
const origins = configService.get('CORS_ORIGINS')();
config.cors = { origin: origins, credentials: true };
// config.path = '/apis/event-service/graphql';
return config;
},
inject: [YeoConfigService],
My app startup:
async function bootstrap(): Promise<void> {
const app = await getApp();
await app.listen(process.env.PORT ?? 3600);
}
bootstrap();
My versions:
"graphql-ws": "5.11.2",
"graphql-redis-subscriptions": "2.5.0"
"#apollo/gateway": "2.1.3",
"#nestjs/graphql": "10.1.3",
"graphql": "16.5.0",
Result:
{
"error": "Could not connect to websocket endpoint ws://localhost:3600/graphql. Please check if the endpoint url is correct."
}
Any ideas why this isn't working as expected? I've been reading the nestjs docs up at https://docs.nestjs.com/graphql/subscriptions but there's nothing that I can find about extra setup required other than adding
subscriptions: {
'graphql-ws': true,
},
when registering the graphql module.
For anyone else stumbling upon this, I have started using altair which allows me to specify the ws endpoint as well as the type of connection, among which there is a graphql-ws option.
So I went with it.
If anyone knows how to achieve this using the playground referred in the original answer, happy to mark that one as the right answer over my own.

Apollo Express Server on Heroku and Refresh Token Cookie on Mobile Browser

Upon visiting/refresh, the app checks for a refresh token in the cookie. If there is a valid one, an access token will be given by the Apollo Express Server. This works fine on my desktop but when using Chrome or Safari on the iPhone, the user gets sent to the login page on every refresh.
React App with Apollo Client
useEffect(() => {
fetchUser();
}, []);
const fetchUser = async () => {
const res = await fetch('https://website.com/token', {
method: 'POST',
credentials: 'include',
});
const { accessToken } = await res.json();
if (accessToken === '') {
setIsLoggedIn(false);
}
setAccessToken(accessToken);
setLoading(false);
};
Apollo Client also checks if whether the access token is valid
const authLink = setContext((_, { headers }) => {
const token = getAccessToken();
if (token) {
const { exp } = jwtDecode(token);
if (Date.now() <= exp * 1000) {
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : '',
},
};
}
}
fetch('https://website.com/token', {
method: 'POST',
credentials: 'include',
}).then(async (res) => {
const { accessToken } = await res.json();
setAccessToken(accessToken);
return {
headers: {
...headers,
authorization: accessToken ? `Bearer ${accessToken}` : '',
},
};
});
});
const client = new ApolloClient({
link: from([authLink.concat(httpLink)]),
cache: new InMemoryCache(),
connectToDevTools: true,
});
This handles the token link on the Express server
app.use('/token', cookieParser());
app.post('/token', async (req, res) => {
const token = req.cookies.rt;
if (!token) {
return res.send({ ok: false, accessToken: '' });
}
const user = await getUser(token);
if (!user) {
return res.send({ ok: false, accessToken: '' });
}
sendRefreshToken(res, createRefreshToken(user));
return res.send({ ok: true, accessToken: createAccessToken(user) });
});
And setting of the cookie
export const sendRefreshToken = (res, token) => {
res.cookie('rt', token, {
httpOnly: true,
path: '/token',
sameSite: 'none',
secure: true,
});
};
Same site is 'none' as the front end is on Netlify.
After a day of fiddling and researching, I have found the issue, and one solution when using a custom domain.
The issue is that iOS treats sameSite 'none' as sameSite 'strict'. I thought iOS Chrome would be different than Safari but it appears not.
If you use your front-end, hosted on Netlify, you will naturally have a different domain than your Heroku app back-end. Since I am using a custom domain, and Netlify provides free SSL, half of the work is done.
The only way to set a httpOnly cookie is to set the cookie to secure. The next step would be to set sameSite to 'none' but as mentioned above, this does not work with iOS.
Setting the domain property of the cookie will also not work because the domain property concerns the scope of the cookie and not the cookie origin. If the cookie came from a different domain (Heroku backend), then the frontend (on Netlify) will not be able to use it.
By default, on Heroku, the free dyno will give you a domain like 'your-app.herokuapp.com', which is great because it also includes free SSL. However, for the cookie to work, I added my custom domain that I use with Netlify. To be clear, Netlify already uses my apex custom domain, so I am adding a subdomain to Heroku (api.domain.com). Cookies do work for across the same domain and subdomains with sameSite 'strict'.
The final issue with this is that the custom domain with Heroku will not get SSL automatically, which is why I think it is worth it to upgrade to a $7/month hobby dyno to avoid managing the SSL manually. This I think is the only solution when using a custom domain.
On the other hand, for those who have the same issue and would like a free solution, you can forgo using a custom domain and host your static front-end with the back-end on Heroku.
Hopefully this will save some time for anyone deploying the back-end and front-end separately.

Any additional settings for express-session secure cookies on Heroku besides trust proxy?

I'm trying to set-up secure cookies on an express app running on Heroku. I've tried both app.set('trust proxy', true) and setting proxy: true in the session config. In both cases, the cookie fails to be modified once deployed to Heroku using an SSL cert.
Other things: I never implemented resave = true in the session since mongo-connect has a touch method.
userSessionStore = new MongoDBStore({
url: 'foo',
collection: 'bar',
touchAfter: 3600,
});
const sess = {
secret: process.env.SESSION_SECRET,
name: 'name',
resave: false,
saveUninitialized: true,
cookie: {
maxAge: 1 * 24 * 60 * 60 * 1000
},
store: userSessionStore
};
if (process.env.NODE_ENV === 'production') {
sess.cookie = {
secure: true,
httpOnly: true,
domain: baseURL
};
}
app.use(session(sess));
if (process.env.NODE_ENV === 'production') {
app.set('trust proxy', true);
}
This problem was due to the baseURL variable not matching both the host and the subdomain on heroku. Since heroku hosts many apps at foo.herokuapp.com, matching the subdomain is required.

Nuxt Proxy changes POST to GET when proxy is set to true

In my universal nuxt app, I have setted proxy at true and rewritte my url to avoid CORS issue.
But when I'm setting proxy to true, all my post requests are changed to get request. Don't understand why and how to configure it no to have this transformation.
Here is my nuxt.config.js :
/*
** Axios module configuration
*/
axios: {
proxy: true
},
proxy: {
'/apicore/': { target: 'http://blablabla.fr', pathRewrite: { '^/apicore/': '' }, changeOrigin: true }
}
My call:
async createJoueur({ state, dispatch, commit }, data) {
const URL = '/apicore/joueur'
await this.$axios
.post(
URL,
data, {
headers: {
'Content-Type': 'application/json'
}
}
)
.then((response) => {
console.log('JOUEUR LOGGED : ')
if (response.status === 200) {
} else {
console.log('Login failed / Not found')
}
}
)
.catch((error) => {
console.log('ERROR')
})
With this proxy set to true, my post-call becomes a get one.
Do I have forgotten something in my configuration?
Thanks for your help.
I was with the same problem! i solved it using changeOrigin: false.
I know that it must be the default value (Look at changeOrigin session ),
but it seems like in nuxtjs proxy implementation this value default is true (Look at Options session) .
I had the same issue and after some logging using the onProxyReq option I found out that the issue was the Cloudflare proxy, not the nuxt proxy. Cloudflare was forwarding HTTPS requests to HTTP and this forces POST requests to become GET requests as is common with 301/302 redirects.
As far as I know, it's not possible to configure Cloudflare to do 308 redirects, which would not alter the HTTP method/body.

Ajax With ReactJS to NodeJS

I'm using webpack-dev-server while developing in ReactJS.
I also want to add a backend which will be written in NodeJS.
When I run the webpack-dev-server it binds to port 8080.
When I run node, it can't bind to the same port.
Therefore, I'm unable to perform $.ajax requests due to the SOP.
How do I get over this issue?
NodeJS:
const express = require('express');
const app = express();
app.get('/messages', function(req, res){
res.send('hello world!');
});
let server = app.listen(8081, function() {
const host = server.address().address;
const port = server.address().port;
console.log('Listening at http://%s:%s', host, port);
});
React/JS/Ajax:
$.getJSON('/messages', function(data) {
this.setState({
messages: data
});
}.bind(this));
And I'm running webpack-dev-server without any parameters.
the port of app is different from the portof your server. if you want to keep save port, you can try to use webpack proxy
module.exports = {
// the other config of your webpack
devServer: {
hot: true,
historyApiFallBack: true,
proxy: {
'/message': {
target: 'http://localhost:8081',
secure: false,
changeOrigin: true
},
},
},
}
when you fetch http://localhost:8080/messages, webpack-dev-server will proxy to http://localhost:8080/messages.
Your app is on port 8080.
Your server is on 8081.
If you want to request from the server, you need to specify the port to the server. If not, it will request to the port your app is running to, which is 8080.
$.getJSON('https://localhost:8081/messages', function(data) {
this.setState({
messages: data
});
}.bind(this));

Resources