Welcome to likhalikhi! This is a blog application project currently under development.
- MongoDB
- Express.js
- React.js
- Node.js
- Frontend UI in progress.
- JWT Authentication in progress.
JSON Web Token (JWT) is an open standard (RFC 7519) for securely transmitting information between parties as a JSON object. It is commonly used for authentication and information exchange.
JSON Web Token (JWT) is a long string . which is divided by three parts [header.information.verifyable-singature]. It is a encoded string looks like
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
header, information, verifyable signature are separated by ' . '(dot)
we can store JWT
in local-storage, session, and cookies for client side. but in loca-storage is not safe because there could be js
attack and js
can easily access localStorage .
so we can keep JWT in our session or cookies. and also we can add expirery time for JWT.
we can invalidate a JWT token by adding expirary time. There is refresh token
concept to handle the situation of if jwt expired and you make a request in server .
JWT (JSON Web Token) and sessions are both methods for handling user authentication, but they have some key differences in their design and usage: JWT process:
client | Server | database |
---|---|---|
login (username , password)---> | ||
validate user credential ---> | ||
<--- credential valid | ||
<---issue JWT token | ||
API Request (with JWT token)---> | ||
Validation Token (Stateless) {checking in Server } |
||
Token Valid<--- |
||
<---Fetch/Store Data---> |
||
if Token Invalid |
||
<--invalid token response |
Session process:
client | Server | database |
---|---|---|
login (username , password)---> | ||
validate user credential ---> | ||
<--- credential valid | ||
<---set Session ID Cookie | ||
API Request (with Session ID)---> | ||
validate Session ID (Stateful)---> | ||
Token Valid<--- |
||
<---Fetch/Store Data---> |
||
if Token Invalid |
||
<--invalid token response |
JWT: JWTs are stateless. Once a JWT is issued, the server does not need to store any session data. The JWT itself contains all the information needed for authentication and is verified using a secret key.A JWT contains all the user information needed for authentication (such as user ID, roles, expiration time) within the token itself. This data is encoded as a JSON object and then signed using a secret key or a public/private key pair.
Session: Sessions are stateful. When a user logs in, the server creates a session and stores session data on the server (usually in memory, a database, or a distributed cache). The server sends a session identifier (session ID) to the client, typically stored in a cookie.All session data is stored on the server, and the session ID is used to retrieve the session data on subsequent requests.
Choosing between JWT and sessions depends on the specific requirements of your application, such as scalability, security, and the architecture of your system.
-
User Registration/Login:
- Users register or log in with their credentials.
- Upon successful authentication, a JWT is generated and sent to the client.
-
Token Storage:
- The client stores the JWT (typically in localStorage or cookies).
-
Authenticated Requests:
- The client includes the JWT in the Authorization header for subsequent requests to protected endpoints.
- The server validates the JWT and grants access if the token is valid.
-
Dependencies:
- Install the necessary packages:
npm install jsonwebtoken bcryptjs
- Install the necessary packages:
-
User Model:
- Create a User model to handle user data and authentication logic.
-
Auth Middleware:
-
Create middleware to protect routes and validate JWTs.
const jwt = require("jsonwebtoken"); const verifyToken = (req, res, next) => { const token = req.header("Authorization").replace("Bearer ", ""); if (!token) return res.status(401).send("Access Denied"); try { const verified = jwt.verify(token, process.env.JWT_SECRET); req.user = verified; next(); } catch (err) { res.status(400).send("Invalid Token"); } }; module.exports = verifyToken;
-
-
Auth Routes:
- Set up routes for user registration and login.
-
Environment Variables:
- Add a
.env
file with your JWT secret:JWT_SECRET=your_jwt_secret
- Add a
As i am configure my database in Mongodb
we need mongoose
for create our models and more easily.
in this model we add bcrypt
for hash our password and jsonwebtoken
for geting access token and refresh token so that we can save it on our cookie or some places. so that we can easily get our necessary information.
import bcrypt from "bcrypt";
import jwt from "jsonwebtoken";
import mongoose from "mongoose";
const userSchema = new mongoose.Schema(
{
username: {
type: String,
required: true,
unique: true,
lowercase: true,
trim: true,
index: true,
min: [3, "Username must be at least 3, got {VALUE}"],
},
email: {
type: String,
required: true,
unique: true,
lowercase: true,
trim: true,
},
username: {
type: String,
required: true,
trim: true,
index: true,
},
password: {
type: String,
required: [true, "Password is required"], //for custom message
},
avater: {
type: String, //Cloudinary URL
required: true,
},
refreshToken: {
type: String,
},
},
{ timestamps: true }
);
//this pre is a middleware it works like before the data save/or whatever i do in this schema.
// we can run a function . there . "save" is for ... before save it will call not update or other things
userSchema.pre("save", async function (next) {
if (!this.isModified("password")) {
return next();
}
this.password = bcrypt.hash(this.password, 10);
next();
});
//creating custom method [we named it "isPasswordCorrect"]
userSchema.methods.isPasswordCorrect = async function (password) {
//logic for checking
return await bcrypt.compare(password, this.password);
};
userSchema.methods.generateAccessToken = function (password) {
return jwt.sign(
{
_id: this._id,
email: this.email,
username: this.username,
fullname: this.fullname,
},
process.env.ACCESS_TOKEN_SECRET,
{ expiresIn: process.env.ACCESS_TOKEN_EXPIRY }
);
};
userSchema.methods.generateRefreshToken = function (password) {
return jwt.sign(
{
_id: this._id,
},
process.env.REFRESH_TOKEN_SECRET,
{ expiresIn: process.env.REFRESH_TOKEN_EXPIRY }
);
};
export const User = mongoose.model("User", userSchema);
in this post model we add mongoose-aggregate-paginate-v2
to do some advance command operation in mongodb database.
import mongoose from "mongoose";
import mongooseAggregatePaginate from "mongoose-aggregate-paginate-v2";
const postSchema = new mongoose.Schema(
{
title: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
category: {
type: String,
required: true,
},
postImage: {
type: String, //cloudinary url
},
isPublished: {
type: Boolean,
default: true,
},
createdBy: {
//who created this post
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
},
{ timestamps: true }
);
postSchema.plugin(mongooseAggregatePaginate);
export const Post = mongoose.model("Post", postSchema);
Feel free to explore the code and reach out if you have any questions or ideas!