2

I would like an elegant way to dynamically set a Authentication header on all requests. My current solution is using a superagent-defaults package but it dooesn't handle dynamic headers

Take the sample code below

superagentWrapper.js

import defaults from 'superagent-defaults';
import localStorage from 'localStorage';

const superagent = defaults();

superagent
  .set('Authorization', `Bearer ${localStorage.getItem('access_token')}`);

export default superagent;

api.js

  import request from '../../../utils/superagentWrapper';

  ExampleAuthenticatedCall: (x) => {
    return new Promise((resolve, reject) => {
      request
     .post(sources.exampleCall())
     .accept('json')
     .set('Content-Type', 'application/json')
     .timeout(40000)
     .send({ exampleCall: "xxx"})
     .end((error, res) => {
        res.body.error ? reject(res.body.error) : resolve(res.body);
     });
    });
  },

So the issue is that my superAgentWrapper is required by all my api files at the time of page load. This means that the example works fine as long as the access_token never changes ( Which it does do potentially multiple times before a page refresh ).

I've found a solution to this issue here https://www.crowdsync.io/blog/2017/10/16/setting-defaults-for-all-your-superagent-requests/.

Whilst this could potentially work an ideal solution would be to dynamically mirror all the methods the request library has rather than manually having to define them ( new methods may be added / removed in the future so this approach is a little brittle ).

Perhaps anyone could point me in the right direction of how this might be possible perhaps by using proxies / reflection?

does_not_compute
  • 476
  • 1
  • 7
  • 20

1 Answers1

2

Use the standard superagent, and instead of returning the same request, return a function that generates a new request, and gets the current token from the localStorage:

import request from 'superagent';
import localStorage from 'localStorage';

const superagent = () => request
  .set('Authorization', `Bearer ${localStorage.getItem('access_token')}`);

export default superagent;

Usage (see request and promises):

import request from '../../../utils/superagentWrapper';

AuthenticatedCall: (x) => request() // generate a new request
  .get(sources.AuthenticatedCall(x)) // a request returns a promise by default

If you don't won't to convert the wrapper to a function, you can use a proxy with a get handler. The proxy will produce a new request, with the current token, whenever it's called:

import request from 'superagent';
import localStorage from 'localStorage';

const handler = {
  get: function(target, prop, receiver) {
    return request
      .set('Authorization', `Bearer ${localStorage.getItem('access_token')}`)[prop];
  }
};

const requestProxy = new Proxy(request, handler);

export default requestProxy;

Usage (see request and promises):

import request from '../../../utils/superagentWrapper';

AuthenticatedCall: (x) => request // generate a new request
  .get(sources.AuthenticatedCall(x)) // a request returns a promise by default
Ori Drori
  • 183,571
  • 29
  • 224
  • 209
  • I'm ideally trying to avoid changing the 200-300+ requests we have . Thats a fair point about the promise's being returned, I've just inherited a code base with many requests like that so i'll get around to spring cleaning them at some point. – does_not_compute Jul 06 '18 at 16:49
  • I've added a proxy based solution (untested), that should do what you want. Note that IE and older versions of the other browsers don't support proxy. – Ori Drori Jul 06 '18 at 22:24