OAuth Authentication System- Behind the scenes

All you need to know about OAuth authentication. Understand the OAuth operational flow by implementing it in the Node.js application.

Vijay KMS
Geek Culture

--

Photo by Franck on Unsplash

Implementing the OAuth authentication system in our application and understanding the technical workflow of the OAuth method is quite challenging.

In this article, let us figure out the operational workflow of OAuth in terms of simple English along with its implementation in the Express.js framework. Here we are going to use Google OAuth for the demonstration.

When the user(from the client) sends the Sign-in with Google request to the API server, it forwards the request to Google. The google prompt for the user’s permission in a separate window. Once the user grants permission, Google responds to the Server with the access token.
When the Server gets the access token, it usually won’t log the user immediately into the application. It again sends a request to Google to get the user’s details with the access code. By seeing the access code, Google provides the Server with the requested user profile. Now the Server stores that user’s details in the database and then allows the user to log into the application with a session token. These are the basic high-level flow of OAuth. Let us start implementing it now in the Node.js application by taking help of the Express.js framework.

This article assumes that you have a basic knowledge of Node.js and Express.

The entire source code can be found here.

Create the node.js application by doing npm init in the terminal window and add the following code in the index.js file in the root directory.

/* index.js */const express = require('express');const app = express();const PORT = process.env.PORT || 5000
app.listen(PORT);

Before we proceed further, let us first setup our project in the console.google for accessing Google OAuth API. We need client ID and client secret in our application.

Please follow the instructions from here to setup the project in google console.

After complete setting up our project in google console, create a keys.js file to store our google client ID and client secret.

/* ./config/keys.js */module.exports = {
googleClientID: 'your-client-id1234.apps.googleusercontent.com',
googleClientSecret: 'your-secret-key'
}

Now we are going to use a helper library called Passport.js to help us creating this authentication flow. Passport.js will take care of a couple of different steps here, that’s going to make our lives easier.

Install Passport.js in our application. To know more about it, visit the documentation.

$ npm install passport

When we make use of Passport.js, we are installing at least two separate libraries. The first one is Passport.js itself which is the core Passport module that contains general functions, objects and helpers that makes the authentication works nicely inside of Express. The other libraries which are called Passport Strategy, are used to implement the auth flow with a very specific provider. Here the providers are like Google, Facebook, Github, etc.

Here we are going to use the passport-google-oauth strategy. Install the passport-google-oauth20 in our application.

$ npm install passport-google-oauth20

To know more about Passport strategies, do visit here.

At this time, you may feel like why should I do this Passport stuff and all. But trust me, this is going to be super helpful in reducing a lot of our efforts.

As passport functionality is a kind of service, create a passport.js file under the ./services directory and add the following code.

/* ./services/passport.js */const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const keys = require('./../config/keys');
passport.use(
new GoogleStrategy({
clientID: keys.googleClientID,
clientSecret: keys.googleClientSecret,
callbackURL: '/auth/google/callback'
},
(accessToken, refreshToken, profile, done) => {
console.log(profile.id);
})
);

Here, we are importing the Passport library and the passport-google-oauth library which is called a Strategy. If we use other OAuth services in our application such as Facebook, GitHub, we have to install and import the respective strategies.
Then we have to provide three mandatory properties such as clientID, clientSecret and callbackURL to GoogleStrategy. Remember, callbackURL is where google OAuth API redirects with access code.

Now create the authRoutes.js file in the ./routes directory and add the following.

/* ./routes/authRoutes.js */const passport = require('passport');module.exports = (app) => {
app.get('/auth/google',
passport.authenticate('google', { scope: ['profile', 'email'] }));
app.get(
'/auth/google/callback',
passport.authenticate('google'),
(req, res) => {
res.redirect('/')
}
);
}

Here we are setting up a route handler for authentication. Once the URL hits “/auth/google”, we use the Passport library which automatically visits Google OAuth API, thus performing the authentication. After the user grants the permission, Google API will redirect to “/auth/google/callback” with the access code.
Now the “/auth/google/callback” route handler will take part. There we specified passport.authenticate(‘google’). So if the Passport sees the URL with access code, it again sends it to google API and asks for the user’s profile. Now by seeing the access token, Google API will return back the user’s profile details to the Passport(accessToken, refreshToken, profile). Refer below diagram.

Now update the index.js file as below to execute the passport.js file and authRoutes.js file while the app starts.

/* index.js */const express = require('express');
const passport = require('passport');
const keys = require('./config/keys');
require('./services/passport');
const authRoutes = require('./routes/authRoutes');
const app = express();authRoutes(app);const PORT = process.env.PORT || 5000
app.listen(PORT);

Yeah, we have successfully set up the google OAuth in our application. Now, if you visit “http://localhost:5000/auth/google" you should see the google OAuth window asking the user’s permission. Once you grant permission you should see the profile id in the console window(terminal). Remember, we have printed the profile.id in the passport.js file in the callback function of googleStrategy.

Session Token

we are halfway through. Now let us enter into the session token part. Another important aspect of authentication.

Once the Passport GoogleStrategy got the response from google OAuth API, we can implement the procedure(logic) for storing the user profile into the database and start to proceed with the session token.

Update the passport.js file as below to work with the database(check with the user’s profile).

/* ./services/passport.js */const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const keys = require('./../config/keys');
passport.use(
new GoogleStrategy({
clientID: keys.googleClientID,
clientSecret: keys.googleClientSecret,
callbackURL: '/auth/google/callback'
},
(accessToken, refreshToken, profile, done) => {
console.log(profile.id);
// Write logic here to find the user profile with id in //the database
// Once done, the DB will return the response

let existingUser = "some_id_123" // Assume this variable value //was return by database by finding existing record
if(existingUser) {
done(null, existingUser)
}
else {
// Write logic here to create the record for the user with the ID
// Once done, the DB will return the response

let newUser = "new_id_456" // Assume this variable value was //return by database after creating new record
done(null, newUser)
}

})
);

Then we will move to Serialize and Deserialize section where we will generate a session token for response and retrieve the session token from a request respectively.

Now add the serializeUser and deserializeUser method as below.

/* ./services/passport.js */const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const keys = require('./../config/keys');
passport.serializeUser((userID, done) => {
done(null, userID)
});
passport.deserializeUser((userID, done) => {
// perform logic here to verify the userID with the database
// Once verifyed call done(),so that the request pass to route //handler
done(null, userID)
});
passport.use(
new GoogleStrategy({
clientID: keys.googleClientID,
clientSecret: keys.googleClientSecret,
callbackURL: '/auth/google/callback'
},
(accessToken, refreshToken, profile, done) => {
console.log(profile.id);
// Write logic here to find the user profile with id in //the database
// Once done, the DB will return the response

let existingUser = "some_id_123" // Assume this variable value //was return by database by finding existing record
if(existingUser) {
done(null, existingUser)
}
else {
// Write logic here to create the record for the user with the ID
// Once done, the DB will return the response

let newUser = "new_id_456" // Assume this variable value was //return by database after creating new record
done(null, newUser)
}
})
);

The done(null, user) method inside GoogleStrategy() callback method will call the passport.serializeUser(userID) method by passing the user as an argument. The serializeUser method will take care of generating the session token with userID and sends it with the response.

No let us install one more module cookie-session to create a cookie to work with session token.

$ npm install cookie-session

Note: There are other methods also available like JWT. Here we are using cookie for session token.

Now update index.js file as below to add cookie-session middleware and passport middleware to handle session.

/* index.js */const express = require('express');
const cookieSession = require('cookie-session');
const passport = require('passport');
const keys = require('./config/keys');
require('./services/passport');
const authRoutes = require('./routes/authRoutes');
const app = express();app.use(
cookieSession({
maxAge: 30 * 24 * 60 * 60 * 1000,
keys: [keys.cookieKey]
})
);
app.use(passport.initialize());
app.use(passport.session());
authRoutes(app);const PORT = process.env.PORT || 5000
app.listen(PORT);

Now update ./config/keys.js file to set the cookie id as below. This id will be labelled over the session token as an act of encryption.

module.exports = {
googleClientID: 'your-client-id123456789.apps.googleusercontent.com',
googleClientSecret: 'your-secret-key',
cookieKey: 'mckdjcnhljinuyvrhbelbvcnaljskbvnskvnr'
}

These are middlewares. By implementing cookieSession, the session token created by passport.serializeUser function will be encrypted whenever a response sends to the client. And also the cookie will be decrypted whenever we get the request from the client.

As the cookieSession middleware decrypt the cookie token, it will then send to deserializeUser where we can perform our own logic to work with the userID like we can verify the userID with the database and then the request passed to the route handler.

To verify this add the following routes in the authRoutes.js file.

/* ./routes/authRoutes.js */const passport = require('passport');module.exports = (app) => {
app.get('/auth/google',
passport.authenticate('google', { scope: ['profile', 'email'] }));
app.get(
'/auth/google/callback',
passport.authenticate('google'),
(req, res) => {
res.redirect('/')
}
);

app.get('/api/logout', (req, res) => {
req.logout();
res.send(req.user);
});
app.get('/api/current_user', (req, res) => {
if (req.user) {
res.send({ here_is_user_profile: user })
}
else {
res.send('Please login by this URL:
http://localhost:5000/auth/google')
}
});

}

If you visit http://5000/api/current_user after logged in, you will get the user id. And if you visit http://5000/api/logout, by seeing req.logout(), the passport will take care of deleting the cookie from the request and you will be automatically logged out.

Conclusion

OAuth is the morden authentication system which makes user to sign up/ sign in easily as they don’t need to remember username and password for so many applications. The working principle and the implementation which we discussed above can be applicable to other OAuth APIs as well like Facebook, twitter, GitHub, etc. Give it a try. Good luck with the OAuth system.

--

--