Skip to content

Instantly share code, notes, and snippets.

@james-gardner
Last active June 1, 2018 09:02
Show Gist options
  • Save james-gardner/b6e19dc54eef8dabaf04794af45c5e93 to your computer and use it in GitHub Desktop.
Save james-gardner/b6e19dc54eef8dabaf04794af45c5e93 to your computer and use it in GitHub Desktop.
Custom passport strategy
const passport = require('passport-strategy');
const url = require('url');
const querystring = require('querystring');
const util = require('util');
const axios = require('axios');
/**/
function TenantStrategy(options, verify) {
this.name = 'tenant';
/* Apply passport strategy. */
passport.Strategy.call(this);
this.options = {
...options,
authorizeURL: `https://${options.domain}/authorize`,
tokenURL: `https://${options.domain}/oauth/token`,
userInfoURL: `https://${options.domain}/userinfo`,
apiUrl: `https://${options.domain}/api`
};
/* Reference to passport callback */
this.verify = verify;
};
/**/
TenantStrategy.prototype.userProfile = function (accessToken, cb) {
return axios.get(this.options.userInfoURL, {
headers: {
'Authorization': `Bearer ${accessToken}`
}
})
.then(res => {
return cb(null, res.data);
})
.catch(err => cb(err))
};
/**/
TenantStrategy.prototype.getOAuthAccessToken = function (code, params, cb) {
const type = (params.grant_type === 'refresh_token') ? 'refresh_token' : 'code';
params[type]= code;
return axios.post(this.options.tokenURL, querystring.stringify(params), {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
})
.then(res => {
const { access_token, refresh_token, ...params } = res.data;
return cb(null, access_token, refresh_token, params);
})
.catch(err => cb(err));
};
/**/
TenantStrategy.prototype.authenticate = function(req, options = {}) {
const self = this;
/* Process an auth code. */
if (req.query && req.query.code) {
const params = {
redirect_uri: options.tenant.callbackURL,
client_id: options.tenant.clientId,
client_secret: options.tenant.clientSecret,
grant_type: 'authorization_code'
};
/* Exchange code for token */
return self.getOAuthAccessToken(req.query.code, params,
(err, accessToken, refreshToken, params) => {
/* TODO: Properly handle error(s) */
if (err) {
throw new Error(err);
}
/* Based on existing oauth strategy. */
const verified = (err, user, info) => {
if (err) {
return self.error(err);
}
if (!user) {
return self.fail(info);
}
return self.success(user, info);
};
/* Fetch user profile */
self.userProfile(accessToken, (err, profile) => {
/* Determine how many arguments the passport callback expects. */
if (self.verify.length == 5) {
return self.verify(accessToken, refreshToken, params, profile, verified);
}
return self.verify(accessToken, refreshToken, profile, verified);
});
}
);
}
/* Re-direct to Auth0 with parameters from tenant */
const parsed = url.parse(self.options.authorizeURL, true);
parsed.query = {
audience: self.options.audience,
redirect_uri: options.tenant.callbackURL,
response_type: 'code',
client_id: options.tenant.clientId,
};
return self.redirect(url.format(parsed));
};
/* Concatenate prototype with passport.Strategy */
util.inherits(TenantStrategy, passport.Strategy);
module.exports = TenantStrategy;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment