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.