Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Auth0 LoopbackJS API access token using 3rd party login

I currently have the loopbackJS api hosted on a domain (e.g. http://backend.com), with third party authentication setup via Auth0. I have a front-end hosted as a SPA on another domain (e.g. http://frontend.com)

loopback-component-passport seems to work fine when the front-end is on the same domain as the API, and it sets the userId and access_token cookies accordingly. However, my front-end in production is on a different domain to the API, for example the API auth link would be something like:

"http://backend.com/auth/auth0?returnTo=" + encodeURIComponent("http://frontend.com")

The backend has used the same auth pattern as in the loopback-passport-example, where a providers.json file specifies the connection details for Auth0 (although I have also tried other social providers such as Facebook).

  "auth0-login": {
    "provider": "auth0",
    "module": "passport-auth0",
    "clientID": "AUTH0_CLIENT_ID",
    "clientSecret": "AUTH0_CLIENT_SECRET",
    "callbackURL": "/auth/auth0/callback",
    "authPath": "/auth/auth0",
    "callbackPath": "/auth/auth0/callback",
    "successRedirect": "/",
    "failureRedirect": "/login",
    "scope": ["email"],
    "failureFlash": true
  }

The front-end (http://frontend.com) has a link on the page to redirect to the API authentication:

<a href="http://backend.com/auth/auth0">Login</a>

Clicking on this link redirects to Auth0 properly, and I can login. It then redirects to the specified target (http://backend.com or http://frontend.com, whichever is specified). The returnTo query parameter also seems to work as expected.

Is there a way to capture the access_token just before redirecting back to the front-end, and somehow communicate it (e.g. query parameters, unless that would be too insecure).

like image 494
J3Y Avatar asked Jun 30 '26 07:06

J3Y


1 Answers

After some more investigation, I settled on this method to use for passing the access token and userId from loopbackjs backend, to a separate front-end. This was documented on a github pull-request, using a customCallback of passport-configurator.

Other places that have referenced this are this fork, issue #102, issue #14 and pull request #155.

There are 2 options here, either use a fork of loopback-component-passport (e.g. the one referenced above) as your npm dependency, or provide a customCallback as a passport configuration option as documented.

I wanted a little more control on the format of the URL, so ended up with the customCallback method. In loopback-example-passport, inside /server/server.js there is some basic code for passing providers.json to the passport configurator:

var config = {};
try {
  config = require('../providers.json');
} catch (err) {
  console.trace(err);
  process.exit(1); // fatal
}
passportConfigurator.init();
for (var s in config) {
  var c = config[s];
  c.session = c.session !== false;
  passportConfigurator.configureProvider(s, c);
}

This can be essentially replaced with the documented customCallback code, with the passport variable being assigned by passportConfigurator.init():

var providers = {};
try {
  providers = require('../providers.json');
} catch (err) {
  console.trace(err);
  process.exit(1); // fatal
}

const passport = passportConfigurator.init();
Object.keys(providers).forEach(function(strategy) {

  var options = providers[strategy];
  options.session = options.session !== false;

  var successRedirect = function(req) {
    if (!!req && req.session && req.session.returnTo) {
      var returnTo = req.session.returnTo;
      delete req.session.returnTo;
      return returnTo;
    }
    return options.successRedirect || '';
  };

  options.customCallback = !options.redirectWithToken 
    ? null 
    : function (req, res, next) {
      var url = require('url');
      passport.authenticate(
        strategy,
        {session: false},
        function(err, user, info) {
          if (err) {
            return next(err);
          }

          if (!user) {
            return res.redirect(options.failureRedirect);
          }
          var redirect = url.parse(successRedirect(req), true);

          delete redirect.search;

          redirect.query = {
            'access_token': info.accessToken.id,
            'userId': user.id.toString()
          };
          redirect = url.format(redirect);
          return res.redirect(redirect);
        }
      )(req, res, next);
  };

  passportConfigurator.configureProvider(strategy, options);
});

In the above example, I have essentially copied the successRedirect function used in passport-configurator.js, to use the same returnTo query parameter. An option within providers.json can be set e.g. "redirectWithToken": true, which results in redirect only for the auth strategies that need external redirect.

One more final bit of code in case the returnTo redirect is required. If it exists as a query parameter, it should be added at a session level:

app.use(function(req, res, next) {
  var returnTo = req.query.returnTo;
  if (returnTo) {
    req.session = req.session || {};
    req.session.returnTo = require('querystring').unescape(returnTo);
  }
  next();
});

Now, if the backend api is at a URL such as http://api.com, and the front-end is hosted at another domain e.g. http://gui.com, an authentication link can be placed on the front-end:

<a href="http://api.com/auth/facebook?returnTo=http%3A%2F%2Fgui.com">Login!</a>

This will result in an API auth call, then redirect back to the returnTo link with the access token and userId in the query parameters.

Potentially in the future, one of the issues or other pull requests will be merged that could provide a more ideal method for 3rd party domain redirection, but until then this method work well.

like image 174
J3Y Avatar answered Jul 03 '26 09:07

J3Y



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!