Kritim Yantra
Aug 08, 2025
Let me break down the entire authentication microservice into clear, blog-style sections with complete code for each file.
auth-microservice/
├── src/
│ ├── config/
│ │ ├── db.js
│ │ └── passport.js
│ ├── models/
│ │ └── User.js
│ ├── routes/
│ │ ├── auth.js
│ │ └── oauth.js
│ ├── middleware/
│ │ └── auth.js
│ ├── utils/
│ │ ├── sendEmail.js
│ │ └── generateTokens.js
│ └── server.js
├── .env
└── package.json
.env
MONGO_URI=mongodb://localhost:27017/auth_microservice
JWT_SECRET=supersecretkey123
JWT_REFRESH_SECRET=supersecretrefreshkey456
EMAIL_USER=your-email@gmail.com
EMAIL_PASS=your-email-password
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-secret
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-secret
BASE_URL=http://localhost:5000
FRONTEND_URL=http://localhost:3000
PORT=5000
src/config/db.js
import mongoose from "mongoose";
export const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log("✅ MongoDB Connected");
} catch (error) {
console.error("❌ DB Connection Failed", error);
process.exit(1);
}
};
src/models/User.js
import mongoose from "mongoose";
const userSchema = new mongoose.Schema({
name: String,
email: {
type: String,
unique: true,
required: true,
match: [/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/, 'Please fill a valid email address']
},
password: String,
role: {
type: String,
enum: ["admin", "user"],
default: "user"
},
isVerified: {
type: Boolean,
default: false
},
verificationToken: String,
resetOTP: String,
resetOTPExpires: Date,
provider: {
type: String,
enum: ["local", "google", "github"],
default: "local"
},
googleId: String,
githubId: String
}, {
timestamps: true
});
// Password hashing middleware
userSchema.pre('save', async function(next) {
if (!this.isModified('password')) return next();
try {
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
next();
} catch (error) {
next(error);
}
});
// Method to compare passwords
userSchema.methods.comparePassword = async function(candidatePassword) {
return await bcrypt.compare(candidatePassword, this.password);
};
export default mongoose.model("User", userSchema);
src/utils/generateTokens.js
import jwt from "jsonwebtoken";
export const generateAccessToken = (user) => {
return jwt.sign(
{
id: user._id,
role: user.role,
email: user.email
},
process.env.JWT_SECRET,
{ expiresIn: "15m" }
);
};
export const generateRefreshToken = (user) => {
return jwt.sign(
{ id: user._id },
process.env.JWT_REFRESH_SECRET,
{ expiresIn: "7d" }
);
};
export const verifyToken = (token, isRefresh = false) => {
try {
return jwt.verify(
token,
isRefresh ? process.env.JWT_REFRESH_SECRET : process.env.JWT_SECRET
);
} catch (error) {
return null;
}
};
src/utils/sendEmail.js
import nodemailer from "nodemailer";
const transporter = nodemailer.createTransport({
service: "gmail",
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS,
},
});
export const sendVerificationEmail = async (email, token) => {
const verificationUrl = `${process.env.BASE_URL}/api/auth/verify/${token}`;
await transporter.sendMail({
from: `"Auth Service" <${process.env.EMAIL_USER}>`,
to: email,
subject: "Verify Your Account",
html: `
<h1>Email Verification</h1>
<p>Please click the link below to verify your email address:</p>
<a href="${verificationUrl}">Verify Email</a>
<p>This link will expire in 24 hours.</p>
`,
});
};
export const sendPasswordResetEmail = async (email, otp) => {
await transporter.sendMail({
from: `"Auth Service" <${process.env.EMAIL_USER}>`,
to: email,
subject: "Password Reset OTP",
html: `
<h1>Password Reset Request</h1>
<p>Your OTP code is: <strong>${otp}</strong></p>
<p>This code will expire in 15 minutes.</p>
`,
});
};
src/config/passport.js
import passport from "passport";
import { Strategy as GoogleStrategy } from "passport-google-oauth20";
import { Strategy as GitHubStrategy } from "passport-github2";
import User from "../models/User.js";
// Serialization
passport.serializeUser((user, done) => {
done(null, user.id);
});
// Deserialization
passport.deserializeUser(async (id, done) => {
try {
const user = await User.findById(id);
done(null, user);
} catch (error) {
done(error, null);
}
});
// Google Strategy
passport.use(
new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: `${process.env.BASE_URL}/api/oauth/google/callback`,
scope: ["profile", "email"],
},
async (accessToken, refreshToken, profile, done) => {
try {
const email = profile.emails[0].value;
let user = await User.findOne({ email });
if (!user) {
user = await User.create({
name: profile.displayName,
email,
isVerified: true,
provider: "google",
googleId: profile.id,
});
} else if (!user.googleId) {
user.googleId = profile.id;
await user.save();
}
return done(null, user);
} catch (error) {
return done(error, null);
}
}
)
);
// GitHub Strategy
passport.use(
new GitHubStrategy(
{
clientID: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
callbackURL: `${process.env.BASE_URL}/api/oauth/github/callback`,
scope: ["user:email"],
},
async (accessToken, refreshToken, profile, done) => {
try {
const email = profile.emails?.[0]?.value;
if (!email) return done(new Error("No email found with GitHub account"), null);
let user = await User.findOne({ email });
if (!user) {
user = await User.create({
name: profile.displayName || profile.username,
email,
isVerified: true,
provider: "github",
githubId: profile.id,
});
} else if (!user.githubId) {
user.githubId = profile.id;
await user.save();
}
return done(null, user);
} catch (error) {
return done(error, null);
}
}
)
);
src/routes/auth.js
import express from "express";
import bcrypt from "bcryptjs";
import { v4 as uuidv4 } from "uuid";
import User from "../models/User.js";
import {
generateAccessToken,
generateRefreshToken,
verifyToken,
} from "../utils/generateTokens.js";
import { sendVerificationEmail, sendPasswordResetEmail } from "../utils/sendEmail.js";
import { authMiddleware } from "../middleware/auth.js";
const router = express.Router();
// Register
router.post("/register", async (req, res) => {
try {
const { name, email, password } = req.body;
// Validate input
if (!name || !email || !password) {
return res.status(400).json({ error: "All fields are required" });
}
// Check if user exists
const existingUser = await User.findOne({ email });
if (existingUser) {
return res.status(400).json({ error: "Email already registered" });
}
// Create user
const verificationToken = uuidv4();
const user = new User({
name,
email,
password,
verificationToken,
});
await user.save();
// Send verification email
await sendVerificationEmail(email, verificationToken);
res.status(201).json({
message: "Registration successful. Please check your email to verify your account.",
});
} catch (error) {
console.error("Registration error:", error);
res.status(500).json({ error: "Internal server error" });
}
});
// Verify Email
router.get("/verify/:token", async (req, res) => {
try {
const user = await User.findOne({ verificationToken: req.params.token });
if (!user) {
return res.status(400).json({ error: "Invalid or expired verification token" });
}
user.isVerified = true;
user.verificationToken = undefined;
await user.save();
res.json({ message: "Email verified successfully" });
} catch (error) {
console.error("Verification error:", error);
res.status(500).json({ error: "Internal server error" });
}
});
// Login
router.post("/login", async (req, res) => {
try {
const { email, password } = req.body;
// Validate input
if (!email || !password) {
return res.status(400).json({ error: "Email and password are required" });
}
// Find user
const user = await User.findOne({ email });
if (!user) {
return res.status(401).json({ error: "Invalid credentials" });
}
// Check password
const isMatch = await user.comparePassword(password);
if (!isMatch) {
return res.status(401).json({ error: "Invalid credentials" });
}
// Check if email is verified
if (!user.isVerified) {
return res.status(403).json({ error: "Please verify your email first" });
}
// Generate tokens
const accessToken = generateAccessToken(user);
const refreshToken = generateRefreshToken(user);
// Set refresh token in cookie
res.cookie("refreshToken", refreshToken, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "strict",
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
});
// Return access token
res.json({
accessToken,
user: {
id: user._id,
name: user.name,
email: user.email,
role: user.role,
},
});
} catch (error) {
console.error("Login error:", error);
res.status(500).json({ error: "Internal server error" });
}
});
// Refresh Token
router.post("/refresh", (req, res) => {
try {
const refreshToken = req.cookies.refreshToken;
if (!refreshToken) {
return res.status(401).json({ error: "No refresh token provided" });
}
const decoded = verifyToken(refreshToken, true);
if (!decoded) {
return res.status(403).json({ error: "Invalid refresh token" });
}
const accessToken = generateAccessToken({ _id: decoded.id });
res.json({ accessToken });
} catch (error) {
console.error("Refresh token error:", error);
res.status(500).json({ error: "Internal server error" });
}
});
// Forgot Password
router.post("/forgot-password", async (req, res) => {
try {
const { email } = req.body;
if (!email) {
return res.status(400).json({ error: "Email is required" });
}
const user = await User.findOne({ email });
if (!user) {
return res.status(404).json({ error: "User not found" });
}
const otp = Math.floor(100000 + Math.random() * 900000).toString();
user.resetOTP = otp;
user.resetOTPExpires = Date.now() + 15 * 60 * 1000; // 15 minutes
await user.save();
await sendPasswordResetEmail(email, otp);
res.json({ message: "OTP sent to your email" });
} catch (error) {
console.error("Forgot password error:", error);
res.status(500).json({ error: "Internal server error" });
}
});
// Reset Password
router.post("/reset-password", async (req, res) => {
try {
const { email, otp, newPassword } = req.body;
if (!email || !otp || !newPassword) {
return res.status(400).json({ error: "All fields are required" });
}
const user = await User.findOne({
email,
resetOTP: otp,
resetOTPExpires: { $gt: Date.now() },
});
if (!user) {
return res.status(400).json({ error: "Invalid or expired OTP" });
}
user.password = newPassword;
user.resetOTP = undefined;
user.resetOTPExpires = undefined;
await user.save();
res.json({ message: "Password reset successful" });
} catch (error) {
console.error("Reset password error:", error);
res.status(500).json({ error: "Internal server error" });
}
});
// Protected route example
router.get("/me", authMiddleware(), async (req, res) => {
try {
const user = await User.findById(req.user.id).select("-password");
if (!user) {
return res.status(404).json({ error: "User not found" });
}
res.json(user);
} catch (error) {
console.error("Get user error:", error);
res.status(500).json({ error: "Internal server error" });
}
});
// Admin route example
router.get("/admin", authMiddleware(["admin"]), (req, res) => {
res.json({ message: "Welcome admin" });
});
// Logout
router.post("/logout", (req, res) => {
res.clearCookie("refreshToken");
res.json({ message: "Logged out successfully" });
});
export default router;
src/routes/oauth.js
import express from "express";
import passport from "passport";
import {
generateAccessToken,
generateRefreshToken,
} from "../utils/generateTokens.js";
const router = express.Router();
// Google OAuth
router.get(
"/google",
passport.authenticate("google", { scope: ["profile", "email"] })
);
router.get(
"/google/callback",
passport.authenticate("google", { failureRedirect: "/login" }),
(req, res) => {
const accessToken = generateAccessToken(req.user);
const refreshToken = generateRefreshToken(req.user);
res.cookie("refreshToken", refreshToken, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "strict",
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
});
res.redirect(
`${process.env.FRONTEND_URL}/oauth-success?token=${accessToken}`
);
}
);
// GitHub OAuth
router.get(
"/github",
passport.authenticate("github", { scope: ["user:email"] })
);
router.get(
"/github/callback",
passport.authenticate("github", { failureRedirect: "/login" }),
(req, res) => {
const accessToken = generateAccessToken(req.user);
const refreshToken = generateRefreshToken(req.user);
res.cookie("refreshToken", refreshToken, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "strict",
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
});
res.redirect(
`${process.env.FRONTEND_URL}/oauth-success?token=${accessToken}`
);
}
);
export default router;
src/middleware/auth.js
import { verifyToken } from "../utils/generateTokens.js";
import User from "../models/User.js";
export const authMiddleware = (roles = []) => {
return async (req, res, next) => {
try {
// Get token from header
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith("Bearer ")) {
return res.status(401).json({ error: "No token provided" });
}
const token = authHeader.split(" ")[1];
if (!token) {
return res.status(401).json({ error: "No token provided" });
}
// Verify token
const decoded = verifyToken(token);
if (!decoded) {
return res.status(403).json({ error: "Invalid token" });
}
// Check if user still exists
const user = await User.findById(decoded.id);
if (!user) {
return res.status(403).json({ error: "User no longer exists" });
}
// Check roles
if (roles.length > 0 && !roles.includes(user.role)) {
return res.status(403).json({ error: "Unauthorized access" });
}
// Attach user to request
req.user = user;
next();
} catch (error) {
console.error("Authentication error:", error);
res.status(500).json({ error: "Internal server error" });
}
};
};
src/server.js
import express from "express";
import cors from "cors";
import cookieParser from "cookie-parser";
import dotenv from "dotenv";
import { connectDB } from "./config/db.js";
import authRoutes from "./routes/auth.js";
import oauthRoutes from "./routes/oauth.js";
import "./config/passport.js";
dotenv.config();
const app = express();
// Middleware
app.use(
cors({
origin: process.env.FRONTEND_URL,
credentials: true,
})
);
app.use(express.json());
app.use(cookieParser());
// Database connection
connectDB();
// Routes
app.use("/api/auth", authRoutes);
app.use("/api/oauth", oauthRoutes);
// Health check
app.get("/api/health", (req, res) => {
res.json({ status: "OK", message: "Auth service is running" });
});
// Error handling middleware
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: "Something went wrong!" });
});
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`🚀 Auth microservice running on port ${PORT}`);
});
This complete authentication microservice provides:
No comments yet. Be the first to comment!
Please log in to post a comment:
Sign in with Google