OAuth (Open Authorization) is the open standard for token-based authentication and authorization on the Internet. It can allow third-party services to exchange information without exposing the user credentials. In this article, we will guide you on how to implement the OAuth in the MERN stack application.
What is OAuth?OAuth can be defined as Open Authorization. It is an open standard for the access delegation commonly used to grant websites or applications limited access to the user information without exposing the passwords. It can allow third-party services to exchange information on behalf of the user and enable seamless integration between the different services.
Key Components - Resource Owner: The person who authorizes the application to access their account.
- Client: The application requesting access to the user account.
- Authorization Server: The server that can authenticate the user and issue the access tokens.
- Resource Server: The server that holds the user’s resources and accepts the token for the resource requests.
1. OAuth 1.0aIt is the protocal that enables the applications to access the resources on behalf of the users. It can be known for its complexity and the need for the cryptographic methods of the application.
Use Case: It can rarely used due to its complexity.
2. OAuth 2.0It can be simplifies the authentication process and widely adapted. It can allows for the secure API authorization and enables the access token for resources.
Use Case: Most commonly used for the integrating third party authentication providers like Google, Facebook etc.
3. OpenID ConnectThe layer built on the top of OAuth 2.o for handling the user authentication. It can providers the user information through the ID tokens.
Use Case: It can be used for the both authentication and authorization.
Implementing the Authentication and Authorization with OAuth in MERNStep 1: Initialize the ProjectWe can use the below command to initialize the MERN project.
npm init -y Step 2: Install the Required Backend DependenciesWe can install the following required dependencies into the project.
- Express
- Mongoose
- dotenv
- passport
- passport-google-oauth20
- jsonwebtoken
- bcryptjs
We can use the below command to installed the dependencies.
npm install express mongoose dotenv passport passport-google-oauth20 jsonwebtoken bcryptjs Dev Dependencies
npm install --save-dev nodemon Backend Folder Structure Backend Folder Structure Step 3: Backend Configuration1. Create the .env file
MONGO_URI=mongodb://localhost:27017/authdatabase JWT_SECRET=your_jwt_secret GOOGLE_CLIENT_ID=66985898397-qaatut41pc4fqk931dsvmnaj7om2o2v3.apps.googleusercontent.com GOOGLE_CLIENT_SECRET=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 2. Dependencies
"dependencies": { "bcryptjs": "^2.4.3", "connect-mongo": "^5.1.0", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.19.2", "express-session": "^1.18.0", "jsonwebtoken": "^9.0.2", "mongoose": "^8.4.4", "passport": "^0.7.0", "passport-google-oauth20": "^2.0.0" }, "devDependencies": { "nodemon": "^3.1.4" } Backend Code Implementation
JavaScript
//server.js
const express = require("express");
const mongoose = require("mongoose");
const dotenv = require("dotenv");
const passport = require("passport");
const session = require("express-session");
const MongoStore = require("connect-mongo");
const cors = require("cors");
const path = require("path");
const authRoutes = require("./routes/auth");
// Load environment variables
dotenv.config();
// Passport configuration
require("./config/passport");
// Initialize the app
const app = express();
// Middleware
app.use(express.json());
app.use(cors({ origin: "http://localhost:3000", credentials: true }));
app.use(
session({
secret:
"9e961769c4d12f1e3e9f354250ba1a6ce430bbd7d041312356caaa9c1a91a7ad4c6166219c8d06e48ed4825a3f7f065ab6fcdeaab1f35e489e9a1ea7389fdca2",
resave: false,
saveUninitialized: false,
store: MongoStore.create({ mongoUrl: process.env.MONGO_URI }),
})
);
// Passport middleware
app.use(passport.initialize());
app.use(passport.session());
// MongoDB connection
mongoose
.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => console.log("MongoDB connected"))
.catch((err) => console.log(err));
// Routes
app.use("/auth", authRoutes);
// Serve static files from the React frontend app
app.use(express.static(path.join(__dirname, "client/build")));
// Anything that doesn't match the above routes, send back index.html
app.get("*", (req, res) => {
res.sendFile(path.join(__dirname + "/client/build/index.html"));
});
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
JavaScript
// config/passport.js
const passport = require("passport");
const GoogleStrategy = require("passport-google-oauth20").Strategy;
const mongoose = require("mongoose");
const User = require("../models/User");
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser(async (id, done) => {
try {
const user = await User.findById(id);
done(null, user);
} catch (err) {
done(err, null);
}
});
passport.use(
new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: "http://localhost:5000/auth/google/callback",
},
async (accessToken, refreshToken, profile, done) => {
try {
const existingUser = await User.findOne({ googleId: profile.id });
if (existingUser) {
return done(null, existingUser);
}
const user = new User({ googleId: profile.id });
await user.save();
done(null, user);
} catch (err) {
done(err, null);
}
}
)
);
JavaScript
// models/user.js
const mongoose = require('mongoose');
const { Schema } = mongoose;
const userSchema = new Schema({
googleId: String
});
module.exports = mongoose.model('User', userSchema);
JavaScript
// routes/auth.js
const express = require("express");
const passport = require("passport");
const router = express.Router();
// Google OAuth routes
router.get(
"/google",
passport.authenticate("google", {
scope: ["profile", "email"],
})
);
router.get(
"/google/callback",
passport.authenticate("google", { failureRedirect: "/login" }),
(req, res) => {
res.redirect("/home");
}
);
// Check authentication status
router.get("/current_user", (req, res) => {
res.send(req.user);
});
// Logout
router.get("/logout", (req, res) => {
req.logout((err) => {
if (err) {
return next(err);
}
res.redirect("/");
});
});
module.exports = router;
Steps for Frontend React ProjectWe can use the below command to create the react project and also required dependencies.
npx create-react-app frontend cd frontend npm install axios react-router-dom Folder Structure Frontend Folder Structire Step 4: React Frontend Configuration1. Create the .env File
REACT_APP_API_URL=http://localhost:5000 2. Dependencies Package.json
"dependencies": { "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "axios": "^1.7.2", "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^6.24.1", "react-scripts": "5.0.1", "styled-components": "^6.1.11", "web-vitals": "^2.1.4" } 3. Frontend Code Implementation
JavaScript
//context/AuthContext.js
import React, { createContext, useState, useEffect } from "react";
import axios from "axios";
const AuthContext = createContext();
const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
useEffect(() => {
const fetchUser = async () => {
try {
const res = await axios.get(
`${process.env.REACT_APP_API_URL}/auth/current_user`,
{ withCredentials: true }
);
setUser(res.data);
} catch (err) {
console.log("Not authenticated");
}
};
fetchUser();
}, []);
return (
<AuthContext.Provider value={{ user, setUser }}>
{children}
</AuthContext.Provider>
);
};
export { AuthProvider, AuthContext };
JavaScript
// components/Home.js
import React from "react";
import Logout from "./Logout";
const Home = () => {
return (
<div>
<h1>Welcome to the Home Page</h1>
<Logout />
</div>
);
};
export default Home;
JavaScript
//component/Login.js
import React, { useContext, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { AuthContext } from "../context/AuthContext";
const Login = () => {
const navigate = useNavigate();
const { user } = useContext(AuthContext);
useEffect(() => {
if (user) {
navigate("/home");
}
}, [user, navigate]);
const googleLogin = () => {
window.open(`${process.env.REACT_APP_API_URL}/auth/google`, "_self");
};
return (
<div>
<h1>MERN OAuth Example Project</h1>
<button onClick={googleLogin}>Login with Google</button>
</div>
);
};
export default Login;
JavaScript
//component/Logout.js
import React, { useContext } from "react";
import { AuthContext } from "../context/AuthContext";
import axios from "axios";
const Logout = () => {
const { setUser } = useContext(AuthContext);
const handleLogout = async () => {
await axios.get(`${process.env.REACT_APP_API_URL}/auth/logout`, {
withCredentials: true,
});
setUser(null);
};
return <button onClick={handleLogout}>Logout</button>;
};
export default Logout;
JavaScript
// components/ProtectedRoute.js
import React, { useContext } from "react";
import { Navigate } from "react-router-dom";
import { AuthContext } from "../context/AuthContext";
const ProtectedRoute = ({ children }) => {
const { user } = useContext(AuthContext);
if (!user) {
return <Navigate to="/login" />;
}
return children;
};
export default ProtectedRoute;
JavaScript
//App.js
import React from "react";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import { AuthProvider } from "./context/AuthContext";
import Login from "./components/Login";
import Logout from "./components/Logout";
import ProtectedRoute from "./components/ProtectedRoute";
import Home from "./components/Home";
const App = () => {
return (
<AuthProvider>
<Router>
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/logout" element={<Logout />} />
<Route
path="/home"
element={
<ProtectedRoute>
<Home />
</ProtectedRoute>
}
/>
</Routes>
</Router>
</AuthProvider>
);
};
export default App;
Step 5: Step Run the Application1. Run the Backend Server
cd backend npm run dev 2. Run the Frontend Client
cd frontend npm start Output
|