Multiple audiences in same route
andresilva-cc opened this issue · 2 comments
Hello everyone,
Imagine this scenario:
In my API there are 2 types of users, those who access from a mobile application (who I'm gonna call app), and those who access from a web application (who I'm gonna call web). They have different responsibilities and rights so that why I need to separate their audience (in the JWT).
Now, there's one specific route where both can access it. Fine, in my callback of JWTStrategy I could see what's the type of user by the audience string and then do the right query. But, how can I specify in my route (maybe with a parameter) what's the audience (or audiences) I'm targeting and pass it to inside the strategy callback?
Well, I could solve it in an ugly way, check it out:
import Passport from 'passport';
/**
* Passport JWT Strategy Middleware
*
* @export
* @param {(string|string[])} audience Audience to verify against
* @returns {function} Handler function
*/
export default function jwtStrategyMiddleware(audience) {
/**
* Handler function
*
* @param {Request} req Request
* @param {Response} res Response
* @param {NextFunction} next Next function
* @returns {Response} Response
*/
return (req, res, next) => {
// If single audience
if (typeof audience === 'string') {
// If app audience
if (audience === 'app') {
return Passport.authenticate('studentJWT', { session: false })(req, res, next);
}
// If web audience
if (audience === 'web') {
return Passport.authenticate('administratorJWT', { session: false })(req, res, next);
}
}
// Else, if multiple audiences
// First, try to authenticate with administratorJWT
return Passport.authenticate('administratorJWT', { session: false }, (_info, _user, err) => {
// If failed, try to authenticate with studentJWT
if (err) {
return Passport.authenticate('studentJWT', { session: false })(req, res, next);
}
// If successful, call next handler
return next();
})(req, res, next);
};
}
Then you use it like this:
// If single audience
app.get('/example', jwtStrategy('web'), ExampleController.example);
// If multiple audiences
app.get('/example', jwtStrategy(['app', 'web']), ExampleController.example);
If anyone knows a more elegant way, please let me know.
Ok, I discovered that Passport.authenticate also accepts an array of strings (strategy names). For some reason, I didn't find this yesterday.
So now my middleware is much more simple and elegant, thank's to that:
import Passport from 'passport';
/**
* Map audience to strategy name
*
* @param {(string|string[])} audience Audience to map
* @returns {(string|string[])} Strategy or strategies names
*/
function mapAudience(audience) {
const map = {
app: 'studentJWT',
web: 'administratorJWT',
};
if (typeof audience === 'string') {
return map[audience];
}
return audience.map((v) => map[v]);
}
/**
* Passport JWT Strategy Middleware
*
* @export
* @param {(string|string[])} audience Audience to verify against
* @returns {function} Handler function
*/
export default function jwtStrategyMiddleware(audience) {
/**
* Handler function
*
* @param {Request} req Request
* @param {Response} res Response
* @param {NextFunction} next Next function
* @returns {Response} Response
*/
return (req, res, next) => {
Passport.authenticate(mapAudience(audience), { session: false })(req, res, next);
};
}