Closed BjoernRave closed 3 years ago
I wish I had a better solution, but could not find one and ended up storing the idToken in a cookie.
I would suggest you to avoid using the idToken on the client side. If you need to authentify http/ws calls I would suggest you to proxify your calls putting the bearer token on server side. For example you can have a pages/api/fetch.js
page using http-proxy-middleware
.
That's a good suggestion. I hope I can put some time in fixing my public repo soon. Thanks!
@vgrafe if you struggle on this feel free to ask :) I have some code samples
@matthieuh I'd love to see that! I can't spend lots of time on this repo these days, and since it has a few stars I'd love some help providing best practices rather than something that is not recommended. Feel free to post on my repo's issue or here.
Hello @vgrafe I have a pages/api/graphql.js
file with something like this:
import proxy from 'http-proxy-middleware';
import express from 'express';
import auth0 from '../../utils/auth0';
const app = express();
app.use('*', async (req, _, next) => {
const session = = await auth0.getSession(req);
return proxy({
target: process.env.GRAPHQL_URL,
changeOrigin: true,
proxyTimeout: 5000,
secure: false,
headers: {
Connection: 'keep-alive'
},
pathRewrite: {
'^/api/graphql': ''
},
onError: (err, req, res) => {
console.log('err', err, res.data);
res.writeHead(500, {
'Content-Type': 'text/plain'
});
res.end(
'Something went wrong. And we are reporting a custom error message.'
);
},
onProxyReq: async (proxyReq, req, res) => {
if (session) {
proxyReq.setHeader('Authorization', `Bearer ${session.idToken}`);
}
if (req.body) {
let bodyData = JSON.stringify(req.body);
// incase if content-type is application/x-www-form-urlencoded -> we need to change to application/json
proxyReq.setHeader('Content-Type', 'application/json');
proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData));
// stream the content
proxyReq.write(bodyData);
}
}
})(req, _, next);
});
export default app;
Then you just need to call api/graphql
as graphql endpoint without taking care at all of the bearer token.
Something to note is that this proxy will not work with subscription which are using websocket. So to make it works I added an other file pages/api/graphql-ws.js
with
import proxy from 'http-proxy-middleware';
import express from 'express';
import auth0 from '../../utils/auth0';
const app = express();
app.use('*', async (req, _, next) => {
const session = await auth0.getSession(req);
const wsProxy = proxy({
target: process.env.GRAPHQL_URL,
changeOrigin: true,
logLevel: 'debug',
ws: true,
timeout: 30000,
proxyTimeout: 30000,
pathRewrite: {
'^/api/graphql-ws': ''
},
onProxyReqWs: proxyReq => {
if (session) {
proxyReq.setHeader('Authorization', `Bearer ${session.idToken}`);
}
}
});
app.on('upgrade', wsProxy.upgrade);
return wsProxy(req, _, next);
});
export default app;`
Thanks for the above code @matthieuh. Any chance you can provide the client side code?
Wow! Thank you. I'll update my repo sometime this week.
@matthieuh does this work with the Next.js serverless target?
Hi, I have also created one proxy for graphql. I have api/graphql.js file with below gist code:
https://gist.github.com/jagyas/a120fa57349e2f7f854f97d18c4dfd87
I have created above code mainly for hiding Graphql endpoint.
If we will fetch session in same code and then pass it to graphql endpoint, I think it will also help to hide idToken.
Let me know your views @vgrafe @matthieuh @Pytal
Hi everyone, with the new v1.0.0-beta.0 release we have included a way to use an access token from the frontend. However, keep in mind that it is less secure than proxying the requests through API routes, as the access token could be stolen via XSS. Please read Comparison with auth0-react, as auth0-react might be a better fit for your project if that's the primary way of fetching data in your application.
Hi everyone, with the new v1.0.0-beta.0 release we have included a way to use an access token from the frontend. However, keep in mind that it is less secure than proxying the requests through API routes, as the access token could be stolen via XSS. Please read Comparison with auth0-react, as auth0-react might be a better fit for your project if that's the primary way of fetching data in your application.
Sorry, I couldn't access the token on the client side, could you provide an example? ty
Hi @mathiasfc - we've removed the advice on the recommendation of our security experts, but you can still find it here https://github.com/auth0/nextjs-auth0/pull/245
Also checkout https://github.com/auth0/nextjs-auth0/issues/201#issuecomment-761106265
Hi @mathiasfc - we've removed the advice on the recommendation of our security experts, but you can still find it here #245
Also checkout #201 (comment)
Hey, I built a middleware for our graphql requests that can pickup the token and send it through (found the code when googling but I forget where):
import request from 'request';
import util from 'util';
import { getAccessToken } from '@auth0/nextjs-auth0';
const graphql = async (req, res) => {
try {
const { accessToken } = await getAccessToken(req, res, {
audience: process.env.AUTH0_AUDIENCE,
});
const headers = {
// Attach token to header
Authorization: accessToken ? `Bearer ${accessToken}` : '',
// Set content type to JSON
'Content-Type': 'application/json',
};
console.log(headers);
const asyncReqPost = util.promisify(request.post);
// Send request
const graphQLApiResponse = await asyncReqPost({
url: process.env.GRAPHQL_URL,
headers,
json: req.body,
timeout: 5000, // give queries more time to run
gzip: true,
});
// Set response header
res.setHeader('Content-Type', 'application/json');
// Send response
res.end(JSON.stringify(graphQLApiResponse.body));
} catch (error) {
console.error(error);
res.status(error.status || 500).end(error.message);
}
};
export default graphql;
But we do a lot of requests, and we get a Too Many Requests exception at /userinfo
You don't need a complicated config. Just follow this answer: https://github.com/vercel/next.js/discussions/11036#discussioncomment-16147
Also export this from your API handler: https://nextjs.org/docs/api-routes/api-middlewares#custom-config
export const config = {
api: {
bodyParser: false,
externalResolver: true,
},
};
I'm adding to this same question - I'm also trying to create a thin proxy, using next-http-proxy-middleware
to retrieve the session an add the Authorization header.
The issue is that whenever I call getSession
(or getAccessToken
) something is sent to the client, so setting a header I get:
Cannot set headers after they are sent to the client
I also tried mocking the res
parameter sent to getSession
but without luck - how can I get the session readonly without interfering with the response?
Hello @vgrafe I have a
pages/api/graphql.js
file with something like this:import proxy from 'http-proxy-middleware'; import express from 'express'; import auth0 from '../../utils/auth0'; const app = express(); app.use('*', async (req, _, next) => { const session = = await auth0.getSession(req); return proxy({ target: process.env.GRAPHQL_URL, changeOrigin: true, proxyTimeout: 5000, secure: false, headers: { Connection: 'keep-alive' }, pathRewrite: { '^/api/graphql': '' }, onError: (err, req, res) => { console.log('err', err, res.data); res.writeHead(500, { 'Content-Type': 'text/plain' }); res.end( 'Something went wrong. And we are reporting a custom error message.' ); }, onProxyReq: async (proxyReq, req, res) => { if (session) { proxyReq.setHeader('Authorization', `Bearer ${session.idToken}`); } if (req.body) { let bodyData = JSON.stringify(req.body); // incase if content-type is application/x-www-form-urlencoded -> we need to change to application/json proxyReq.setHeader('Content-Type', 'application/json'); proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData)); // stream the content proxyReq.write(bodyData); } } })(req, _, next); }); export default app;
Then you just need to call
api/graphql
as graphql endpoint without taking care at all of the bearer token.Something to note is that this proxy will not work with subscription which are using websocket. So to make it works I added an other file
pages/api/graphql-ws.js
withimport proxy from 'http-proxy-middleware'; import express from 'express'; import auth0 from '../../utils/auth0'; const app = express(); app.use('*', async (req, _, next) => { const session = await auth0.getSession(req); const wsProxy = proxy({ target: process.env.GRAPHQL_URL, changeOrigin: true, logLevel: 'debug', ws: true, timeout: 30000, proxyTimeout: 30000, pathRewrite: { '^/api/graphql-ws': '' }, onProxyReqWs: proxyReq => { if (session) { proxyReq.setHeader('Authorization', `Bearer ${session.idToken}`); } } }); app.on('upgrade', wsProxy.upgrade); return wsProxy(req, _, next); }); export default app;`
thanks for this example, could you show how you are calling your websocket proxy route graphql-ws
endpoint client side?
I'm using Apollo's GraphQLWSLink which requires a full websocket url, if I pass /api/graphql-ws is throws Failed to construct 'WebSocket': The URL '/api/graphql-ws' is invalid.
my websocket looks like this
new GraphQLWsLink( createClient({ url: '/api/graphql-ws' }) );
Hello @vgrafe I have a
pages/api/graphql.js
file with something like this:import proxy from 'http-proxy-middleware'; import express from 'express'; import auth0 from '../../utils/auth0'; const app = express(); app.use('*', async (req, _, next) => { const session = = await auth0.getSession(req); return proxy({ target: process.env.GRAPHQL_URL, changeOrigin: true, proxyTimeout: 5000, secure: false, headers: { Connection: 'keep-alive' }, pathRewrite: { '^/api/graphql': '' }, onError: (err, req, res) => { console.log('err', err, res.data); res.writeHead(500, { 'Content-Type': 'text/plain' }); res.end( 'Something went wrong. And we are reporting a custom error message.' ); }, onProxyReq: async (proxyReq, req, res) => { if (session) { proxyReq.setHeader('Authorization', `Bearer ${session.idToken}`); } if (req.body) { let bodyData = JSON.stringify(req.body); // incase if content-type is application/x-www-form-urlencoded -> we need to change to application/json proxyReq.setHeader('Content-Type', 'application/json'); proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData)); // stream the content proxyReq.write(bodyData); } } })(req, _, next); }); export default app;
Then you just need to call
api/graphql
as graphql endpoint without taking care at all of the bearer token. Something to note is that this proxy will not work with subscription which are using websocket. So to make it works I added an other filepages/api/graphql-ws.js
withimport proxy from 'http-proxy-middleware'; import express from 'express'; import auth0 from '../../utils/auth0'; const app = express(); app.use('*', async (req, _, next) => { const session = await auth0.getSession(req); const wsProxy = proxy({ target: process.env.GRAPHQL_URL, changeOrigin: true, logLevel: 'debug', ws: true, timeout: 30000, proxyTimeout: 30000, pathRewrite: { '^/api/graphql-ws': '' }, onProxyReqWs: proxyReq => { if (session) { proxyReq.setHeader('Authorization', `Bearer ${session.idToken}`); } } }); app.on('upgrade', wsProxy.upgrade); return wsProxy(req, _, next); }); export default app;`
thanks for this example, could you show how you are calling your websocket proxy route
graphql-ws
endpoint client side? I'm using Apollo's GraphQLWSLink which requires a full websocket url, if I pass /api/graphql-ws is throwsFailed to construct 'WebSocket': The URL '/api/graphql-ws' is invalid.
my websocket looks like this
new GraphQLWsLink( createClient({ url: '/api/graphql-ws' }) );
Did you ever find a way to do this proxy with the websocket url? We are facing the same issue now and due to the library only accepting websocket URLs we cannot proxy any of the traffic through nextjs api/rewrites/middleware.
So I am using graphql and instantiating apollo-client in a hoc wrapped around
app.js
. Here I am passing theaccessToken
I get fromconst session = await auth0.getSession(ctx.ctx.req)
as Header to my client so it gets send to the server and the request can be verified. Now I get the error:Error in error page
getInitialProps: Error: "The getSession method can only be used from the server side"
So I am wondering how I should be able to get the accessToken on the client side. I guess I could store the accessToken on a cookie, but that wouldnt feel right