MERN Stack - User Authentication with OAuth 2.0 Print

  • 2

The digital age brings an immense convenience to everyday life, but this convenience also raises security concerns. Every application you interact with likely requires some form of authentication, assuring the application that you are you. To tackle this challenge, many developers turn to OAuth 2.0, a protocol that allows applications to grant limited access to user accounts on an HTTP service. In this article, we will dive into how to develop an API using the MERN (MongoDB, Express, React, Node.js) stack that incorporates OAuth 2.0 for user authentication.

Having explored the fundamentals of Authentication in our article on "Navigating the Evolution of API Authentication", it's clear how vital this protocol is for secure authorization in various applications. 

1. Understanding OAuth 2.0

OAuth 2.0 is a protocol that allows third-party services to access user information without directly dealing with user credentials (username, password). This protocol is commonly used by Google, Facebook, GitHub, and many other services to manage user authentication.

By using OAuth 2.0, you can:

  • Let users grant your application access to their data with other service providers (e.g., show their Google calendar events on your application).
  • Allow users to sign in to your application using their accounts with major service providers.

2. Setting Up the MERN Environment

Before we dive into the code, make sure your development environment is set up. The MERN stack includes MongoDB, Express.js, React.js, and Node.js. Ensure you have the latest stable versions of Node.js and npm (Node Package Manager) installed. Install MongoDB locally or set up a MongoDB Atlas account for a cloud-based solution. Lastly, ensure you have a text editor, like VS Code, ready to go.

3. Setting Up the Express Server

We'll start by setting up our Express.js server. Express is a minimalistic web application framework for Node.js, and it simplifies the process of writing server code.

In a new directory, initialize a Node.js project:

npm init -y

Install Express:

npm install express

Now, let's create our server:

const express = require('express');
const app = express();
const port = 3000;

app.get('/', (req, res) => {
res.send('Hello World!');
});

app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});

4. User Authentication using OAuth 2.0

4.1 Setting Up Passport.js

Passport.js is a popular, flexible authentication middleware for Node.js that can be used with Express.js. It supports multiple strategies, including OAuth, OpenID, and many others.

Install Passport.js and Passport Google OAuth strategy:

npm install passport passport-google-oauth20

Set up Passport.js in your application:

const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;

passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: "http://localhost:3000/auth/google/callback"
},
function(accessToken, refreshToken, profile, cb) {
// Here you can find the user profile information
console.log(profile);

// Find or create a user in your database and return
return cb(null, profile);
}
));

app.use(passport.initialize());

5. Setting Up the Routes

Now let's set up our routes for OAuth:

app.get('/auth/google',
passport.authenticate('google', { scope: ['profile', 'email'] }));

app.get('/auth/google/callback',
passport.authenticate('google', { failureRedirect: '/login

' }),
function(req, res) {
// Successful authentication, redirect home.
res.redirect('/');
});

In the first route, we initialize the OAuth process. The scope specifies what information we want to access (in this case, the user's profile info and email). The second route is the callback route that Google will redirect to after the user grants/denies permission. Upon successful authentication, we redirect the user back to our application's homepage.

6. MongoDB Integration

MongoDB is our chosen database for this project, due to its seamless integration with Node.js and its ability to handle flexible, JSON-like documents which make it easy to store user profile data.

Install MongoDB driver:

npm install mongodb
 
Establish a connection with MongoDB:

const MongoClient = require('mongodb').MongoClient;
const uri = process.env.MONGODB_URI;

MongoClient.connect(uri, { useUnifiedTopology: true })
.then(client => {
console.log('Connected to Database');
const db = client.db('userDB');
const userCollection = db.collection('users');

// Make userCollection available for routes
app.locals.userCollection = userCollection;
})
.catch(error => console.error(error));

7. Handling User Data

Upon successful authentication, we receive a user profile object. We should check our database for an existing user record before creating a new one.

Modify the Passport.js setup to handle user data:

passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: "http://localhost:3000/auth/google/callback"
},
function(accessToken, refreshToken, profile, cb) {
// Get the user collection
const userCollection = app.locals.userCollection;

// Find or create a user
userCollection.findOneAndUpdate(
{ googleId: profile.id },
{
$setOnInsert: {
googleId: profile.id,
name: profile.displayName,
email: profile.emails[0].value,
picture: profile.photos[0].value
}
},
{ upsert: true, returnNewDocument: true },
function(err, user) {
return cb(err, user.value);
});
}
));

8. Serialization and Deserialization

To maintain user data across sessions, we need to serialize (store) and deserialize (retrieve) user data. Passport.js provides serializeUser and deserializeUser methods for this purpose.

passport.serializeUser(function(user, done) {
done(null, user._id);
});

passport.deserializeUser(function(id, done) {
const userCollection = app.locals.userCollection;
userCollection.findOne({ _id: new ObjectId(id) }, function(err, user) {
done(err, user);
});
});

app.use(passport.session());

Here, the user ID is stored in the session and used to fetch the user object on each subsequent request.

9. Ensuring User Authentication

For routes that require user authentication, you can use a custom middleware to ensure that the user is authenticated:

function ensureAuthenticated(req, res, next) {
if (req.isAuthenticated()) {
return next();
}
res.redirect('/login');
}

app.get('/profile', ensureAuthenticated, function(req, res){
res.send(req.user);
});

10. Conclusion

There you have it - you've just built an API in MERN Stack that uses OAuth 2.0 for user authentication! This user-friendly and secure method of authentication ensures that your users can trust your application with their data.


Was this answer helpful?

« Back