Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to get accesstoken client-side #67

Closed
BjoernRave opened this issue Jan 15, 2020 · 18 comments
Closed

How to get accesstoken client-side #67

BjoernRave opened this issue Jan 15, 2020 · 18 comments

Comments

@BjoernRave
Copy link

So I am using graphql and instantiating apollo-client in a hoc wrapped around app.js. Here I am passing the accessToken I get from const 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

@vgrafe
Copy link
Contributor

vgrafe commented Jan 15, 2020

I wish I had a better solution, but could not find one and ended up storing the idToken in a cookie.

@matthieuh
Copy link

matthieuh commented Jan 27, 2020

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.

@vgrafe
Copy link
Contributor

vgrafe commented Jan 27, 2020

That's a good suggestion. I hope I can put some time in fixing my public repo soon. Thanks!

@matthieuh
Copy link

@vgrafe if you struggle on this feel free to ask :) I have some code samples

@vgrafe
Copy link
Contributor

vgrafe commented Jan 29, 2020

@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.

@matthieuh
Copy link

matthieuh commented Feb 3, 2020

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;`

@shansmith01
Copy link

Thanks for the above code @matthieuh. Any chance you can provide the client side code?

@vgrafe
Copy link
Contributor

vgrafe commented Feb 4, 2020

Wow! Thank you. I'll update my repo sometime this week.

@Pytal
Copy link

Pytal commented May 14, 2020

@matthieuh does this work with the Next.js serverless target?

@jagyas
Copy link

jagyas commented Jul 7, 2020

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

@Widcket
Copy link
Contributor

Widcket commented Jan 15, 2021

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.

@Widcket Widcket closed this as completed Jan 15, 2021
@mathiasfc
Copy link

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

@adamjmcgrath
Copy link
Contributor

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)

@oliverlevay
Copy link

oliverlevay commented Nov 11, 2021

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

@thatisuday
Copy link

You don't need a complicated config. Just follow this answer: vercel/next.js#11036 (reply in thread)

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,
  },
};

@dariosky
Copy link

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?

@guven8
Copy link

guven8 commented Mar 16, 2023

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 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' }) );

@liamlows
Copy link

liamlows commented May 7, 2024

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 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' }) );

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests