Requesting & Utilizing User Information To Authenticate
Introduction:
Last time on blog post, we took a dive into the subject of user authentication and explored what I like to think is its outermost layer of the process. We briefly defined what the “user auth” model entails and how we can utilize the Passport.js library to accomplish these goals. While we did not cover all the different authentication strategies provided via Passport.js, we did describe the most traditional user auth method the “Local” strategy. If you recall the local strategy involves setting in place a process in which the user can create a username and password, that will be persisted, and later used to grant them access to restricted aspects of your application. My goal for this post is to dive into the second layer of the user authentication control flow, unpacking the events that occur between the first redirect away, the user requesting authentication, and the final redirect back to our application as a trusted guest.
What happens between redirects and the auth request?
After the user lands on our home page and is prompted to log in, they are then redirected to the OAuth provider where they click an authorized link, and now what? Well, now we are inside of the OAuth process, where the user will grant authorization to our application to utilize their profile information in order to prove they are, without exposing their password. The next question should be along the lines of if they do not give out a password then what will our application use to grant them access? The OAuth provider, upon successful user verification, will grant our application, the consumer, a temporary pass also known as an OAuth or access token. The way I like to think of these tokens is like a limited access pass to the user's information, in which exactly what information our application is authorized to use is designated on the token, and any other sensitive information pertaining to the user is never exposed.
Code Example: access token
{
"access_token":"RsT5OjbzRn430zqMLgV3Ia",
"expires_in":3600
}
The above code snippet is an example of the access token that will be returned to our application via the OAuth provider. As you can see, the token contains an id string and an expiration time.
What happens after we get the token?
Now that our application has a verified access token, it can make HTTP requests to the provider API for information for as long as that token is valid. But we are more concerned with user profile information returned along with the token from the Passport.js strategy we configured and incorporated into our application. The strategy configuration contains a function called the “Verify Callback”, which is used to locate user information with this matching profile information. If we have never encountered this user then their information will not be persisted in our database and we must now make an entry for them, but if they are located the corresponding information is returned. The information is now passed to the done() function of our verified callback and saved as user on the request object.
Code Example: Passport.js OAuth strategy configuration
passport.use(new GoogleStrategy({
// define the options to use with google strategy
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: process.env.GOOGLE_CALLBACK_URL,
},
((accessToken, refreshToken, profile, done) => {
// deconstruct variables from profile object
const { id, displayName } = profile;
const userObj = {
idDiscord: id,
username: displayName,
profilePhotoUrl: profile.photos[0].value,
};
getUser(userObj)
.then((gotUser) => {
if (gotUser) {
done(null, gotUser);
} else {
addUser(userObj)
.then((newUser) => {
done(null, newUser);
});
}
})
.catch((error) => {
console.log(error);
});
}
In the above code snippet, you can follow the control flow of the code block contained in the “Verify Callback”. The user id, display name, and photo URL are deconstructed from the returned google profile information as userObj. The variable userObj is then passed as an argument to the getUser()
function that will query the database for a matching entry. If a successful match is located that information is returned and then passed onto the done(null, gotUser)
function, but if no match is found in the database a sperate function adduser()
is called to create an entry for that user. The newly persisted users information is now returned and also passed to the done(null, newUser)
function. The part that isn’t shown in the code but rather handled backstage by Passport.js is the invoking of req.login()
.
Code Example: Passport.js login function
req.login(user, function(err) {
if (err) { return next(err); }
return res.redirect('/users/' + req.user.username);
});
The Passport.js library has an exposed login function on the request object, that will add the returned user information passed to done()
as req.user. This information can be utilized in different ways within our application, usually for authorization, but most commonly to establish a new user session. The reason we haven’t seen it in our set up of authentication middleware is that Passport.js will call req.login()
automatically when you are utilizing its middleware. Now, isn't that a great little bit of information to store under your cap for next time?
Conclusion:
Much to my surprise, during my research I discovered that user authentication and user sessions are not within the same process and so I’m stopping our dive into level 2 of the OAuth flow right here. Next time we will finish our exploration of OAuth by unpacking what a user session is and all the Passport.js functions involved in that process. Hope you learned a little something new about user authentication, and until next time:
Happy Coding!!!
Sources:
What is OAuth? Definition and How it Works (www.varonis.com)
Wikipedia.org (www.en.wikipedia.org/wiki/OAuth#OAuth_2.0)
Passport.js docs (passportjs.org)
A peep beneath the hood of PassportJS' OAuth flow (dev.to)
Top comments (0)