Implement Multiple Local User Authentication Strategies in Passport.js
Updated: June 9, 2020
Introduction
User authentication is complicated.
Writing user authentication from scratch is even more complicated. You’ll need to make complex security considerations that can delay progress on your Node.js application.
The good news is that the npm package Passport.js abstracts a good amount of this complication away from us, empowering us to create solutions for user authentication that are modular, maintainable, and extensible — however the Passport documentation is less than helpful. It gives great explanation of the API, but skips over very useful module extensibility features.
This article serves to demonstrate one capability offered by Passport.js which is not explicitly outlined in the documentation: Authenticate multiple local user types with multiple local strategies. Each strategy will be using different user models with different user roles, while at the same time utilizing Passport's native serialization methods to authenticate and authorize user sessions.
Creating Local Strategies for Two User Types
Although it isn't required, Passport.js's passport.use();
method does take one optional
parameter that is not mentioned in the documentation:
The passport.use
method is similar to Express's app.use
in that it mounts a
specified strategy to the Passport object, which is called when the route handler callback invokes
the authenticate();
method (e.g., app.get( "/path", passport.authenticate('strategyName'));
).
So, you can add many different named strategies to your app's Passport object to be used in other
parts of your application, and Passport is programmed to validate requests using those strategies
anytime you want via the authenticate();
method.
In a typical application that only needs one type of user authentication, you would see something like:
In the app.js
file, we are importing the passport object, and then passing it as an
argument into our config/passport.js
file which will initialize the specified strategies
in the app's Passport object.
In order to change the code above so that you can use multiple local strategies, is to add a
passport.use
method with names of the strategies we want to include. I
have included an example below:
If you'd like more detail and examples than what is provided above, issue #50 from the Passport.js repository on GitHub shows Jared Hanson explaining the same concept.
Extending passport.serializeUser();
and passport.deserializeUser();
Once you've completed the steps above, you won't yet be able to use the named local authentication strategies from the config/passport.js file that you created above, and your application is likely crashing silently.
The next step is to extend the Passport serialization methods with a function that both checks which Passport strategy was used to generate that client, and then generates a unique code to serialize that user with so that they can be deserialized with each request thereafter.
Before moving on, take a look at the serialization methods in the
documentation. The user ID (you provide as the second
argument of the "done" function in the serializeUser
method) is saved in the session and
is later used to retrieve the whole object via the deserializeUser function. serializeUser
determines, which data of the user object should be stored in the session. The result of the
serializeUser
method is attached to the session as
req.session.passport.user = {}
. The first argument of deserializeUser
corresponds to the key of the user object that was given to the done
function (see 1.). So your whole object is retrieved with help of that key.
If you do not make your user ID unique across each user Model, your serializeUser
function will not know which database to query to retrieve the user you are trying to deserialize.
There are a few ways to fix this, but I included a session ID constructor below: