Skip to content

iamskyy666/jwt-essentials-nodejs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

12 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

image image

JSON Web Tokens (JWT) β€” An In-Depth Explanation πŸ”

To understand JWT properly, we first need to understand the problem JWT was created to solve.


The Fundamental Problem: Identity on the Web

HTTP is a stateless protocol.

When we send a request:

GET /products

the server responds and then forgets about us.

A second later, when we send:

POST /cart

the server has no inherent memory that this request came from the same person.

From the server's perspective:

Request 1 ---> Response
(forget)

Request 2 ---> Response
(forget)

Request 3 ---> Response
(forget)

Each request is independent.


Why Is This a Problem?

Imagine an e-commerce application.

We log in as:

skyy@example.com

The server verifies our password.

Now suppose we request:

GET /profile

How does the server know this request belongs to us?

The server needs some way to answer:

Who is making this request?

This question is called:

Authentication

Authentication answers:

Who are we?

Before JWT: Session-Based Authentication

Historically, servers used sessions.


Login Process

We submit:

{
  "email": "skyy@example.com",
  "password": "secret123"
}

The server verifies credentials.

Then the server creates:

Session ID = abc123xyz

and stores:

abc123xyz ---> User #57

inside server memory or a database.


The server then sends:

Set-Cookie: sessionid=abc123xyz

to the browser.


Browser stores:

sessionid=abc123xyz

as a cookie.

Every future request automatically includes:

Cookie: sessionid=abc123xyz

Server receives:

abc123xyz

looks it up:

abc123xyz ---> User #57

and knows who we are.


Problems with Sessions

Sessions work well, but they have limitations.


Problem 1: Server Storage

The server must remember every active session.

Example:

User A ---> Session A
User B ---> Session B
User C ---> Session C

For millions of users:

Millions of sessions

must be stored somewhere.

Memory usage increases.


Problem 2: Scaling

Imagine:

Server 1
Server 2
Server 3

A user logs into Server 1.

Session exists only there:

Session A ---> User #57

Now the next request reaches Server 2.

Server 2 says:

I don't know this session.

because it wasn't stored there.


Solutions exist:

  • Redis
  • Shared databases
  • Sticky sessions

But infrastructure becomes more complex.


JWT's Core Idea

JWT takes a completely different approach.

Instead of:

Server remembers user

JWT says:

User carries proof of identity

The token itself contains the information.

This is called:

Stateless Authentication

The server does not need to remember logged-in users.


What Is JWT?

JWT stands for:

JSON Web Token

A JWT is a compact string that contains:

  1. Data (claims)
  2. A digital signature

Example:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.
eyJpZCI6IjEyMyIsIm5hbWUiOiJTa3l5In0
.
k8Jw9s9K9eL1...

Three parts separated by dots:

HEADER.PAYLOAD.SIGNATURE

Structure of a JWT


Part 1: Header

Example:

{
  "alg": "HS256",
  "typ": "JWT"
}

Meaning:

Algorithm = HS256
Token Type = JWT

Encoded as Base64.

Result:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

Part 2: Payload

Contains data.

Example:

{
  "id": "123",
  "name": "Skyy",
  "role": "admin"
}

This is called a:

Claim

A claim is information asserted by the token.


Encoded as Base64:

eyJpZCI6IjEyMyIsIm5hbWUiOiJTa3l5In0

Part 3: Signature

Most important part.

Created using:

Header
+
Payload
+
Secret Key

Example:

jwt.sign(payload, secret)

Produces:

Xk9slf83jfjf8f8...

Final token:

HEADER.PAYLOAD.SIGNATURE

The Secret Key

Consider:

const JWT_SECRET = "supersecretkey";

This value never leaves the server.

Never.

Clients never see it.


Why Do We Need the Secret?

Without a secret:

Anyone could modify:

{
  "role": "user"
}

to

{
  "role": "admin"
}

and gain admin privileges.

That would be disastrous.


The signature prevents this.


How Signing Works

Imagine:

{
  "id": 57,
  "role": "user"
}

Server signs it:

payload + secret

producing:

ABC123SIGNATURE

Token becomes:

payload.signature

Now suppose an attacker changes:

{
  "role": "admin"
}

The payload changes.

The old signature becomes invalid.

Server detects tampering.

Authentication fails.


JWT Login Flow


Step 1

We submit credentials.

{
  "email": "skyy@example.com",
  "password": "secret123"
}

Step 2

Server verifies credentials.


Step 3

Server generates JWT.

const token = jwt.sign(
  {
    userId: user._id,
  },
  process.env.JWT_SECRET
);

Step 4

Server returns:

{
  "token": "eyJhbGc..."
}

Step 5

Frontend stores token.

Often:

Local Storage

or

HttpOnly Cookie

Step 6

Future requests include:

Authorization: Bearer eyJhbGc...

What Does "Bearer" Mean?

Bearer means:

Whoever possesses this token
is treated as authenticated.

Possession grants access.


Request:

GET /profile
Authorization: Bearer eyJhbGc...

Server extracts token.

Verifies signature.

If valid:

Authenticated

If invalid:

Unauthorized

jwt.sign()

Creates a token.

Example:

const token = jwt.sign(
  {
    userId: 57
  },
  process.env.JWT_SECRET
);

Think:

Data
+
Secret
=
JWT

jwt.verify()

Checks validity.

Example:

const payload = jwt.verify(
  token,
  process.env.JWT_SECRET
);

Think:

JWT
+
Secret
=
Verified User Data

Why JWT Is Stateless

Notice:

Login
 ↓
JWT Generated
 ↓
Client Stores JWT
 ↓
Future Requests Carry JWT

The server stores nothing.

No session table.

No memory record.

Nothing.


Every request contains everything needed.


Advantages of JWT


1. Stateless

No session storage.


2. Scales Well

Works across:

Server A
Server B
Server C

All servers can verify the same token.


3. Fast

No database lookup for session information.

Only:

jwt.verify()

4. Works Well With APIs

Especially:

  • React
  • Next.js
  • React Native
  • Mobile Apps
  • Microservices

Disadvantages of JWT


1. Cannot Easily Revoke

Suppose:

JWT expires in 7 days

User gets banned.

Token still works until expiration.

With sessions:

Delete session

Immediate logout.

JWT doesn't work that way by default.


2. Larger Than Session IDs

Session:

abc123

JWT:

eyJhbGciOi...

More data transmitted.


3. Not Encrypted

A very common misconception.

JWT is:

Signed

not

Encrypted

Anyone can decode:

Header
Payload

using online tools.

Therefore:

Never store passwords, credit card numbers, or sensitive secrets inside a JWT payload.


The Biggest Misconception

Many beginners think:

JWT secures data

No.

JWT proves:

This data came from us
and wasn't modified.

It guarantees:

  • Integrity
  • Authenticity

It does NOT guarantee:

  • Secrecy
  • Encryption

Mental Model

Think of a JWT as a digitally signed passport.

The payload is:

Name: Skyy
ID: 57
Role: User

The signature is the government's seal.

Anyone can read the passport.

But nobody can forge the seal without the secret.

When our server receives the JWT, it asks:

Is the seal valid?

If yes:

Welcome back.

If no:

Access denied.

That is the essence of JWT authentication: a cryptographically signed identity document that allows stateless authentication across requests without the server having to remember who is logged in.


The Big Picture

The theory and architecture of JWT authentication πŸ”§

Before JWT existed, the fundamental problem was:

HTTP is stateless.

The server does not remember previous requests.

Imagine:

Request #1
POST /login

Response
200 OK

Five seconds later:

Request #2
GET /profile

The server has no built-in way to know:

Are these requests from the same person?

Authentication systems exist to solve this problem.


Authentication vs Authorization

Many beginners confuse these.

Authentication

Answers:

Who are we?

Example:

Username: skyy
Password: secret123

Server verifies identity.


Authorization

Answers:

What are we allowed to do?

Example:

User:
Can view products

Admin:
Can delete products

Authorization happens after authentication.


Registration Cycle

Let's begin from the very start.


Step 1: User Registers

We submit:

{
  "username": "skyy",
  "email": "skyy@gmail.com",
  "password": "secret123"
}

What Happens on the Server?

Server:

  1. Validates input
  2. Checks if email already exists
  3. Hashes password
  4. Saves user

Example database record:

{
  "_id": "123",
  "username": "skyy",
  "email": "skyy@gmail.com",
  "password":
  "$2b$10$h4jK...."
}

Notice:

Password is hashed.

Never stored in plain text.


Should We Create JWT During Registration?

Two common approaches exist.


Approach A

Register

↓

Automatically login

↓

Generate JWT

Many modern applications do this.


Approach B

Register

↓

Redirect to Login

↓

Generate JWT later

Also common.


JWT is not required for registration itself.

JWT is required when we want to authenticate future requests.


Login Cycle

This is where JWT becomes important.


Step 2: User Logs In

Request:

{
  "username": "skyy",
  "password": "secret123"
}

Server:

Find user
Compare password hash
Verify credentials

If credentials are valid:

Authentication successful

Step 3: Create JWT

Now we must solve:

How do we prove identity
on future requests?

JWT is the answer.


Server creates:

jwt.sign(
  {
    id: user._id,
    username: user.username
  },
  JWT_SECRET
)

This creates:

header.payload.signature

Why Do We Sign?

This is the most important JWT concept.


Imagine a token without a signature:

{
  "id": 123,
  "role": "user"
}

Anyone could modify it:

{
  "id": 123,
  "role": "admin"
}

and gain admin privileges.


So we digitally sign it.

Think of a signature like:

Government seal
on a passport

Anyone can read the passport.

Nobody can forge the seal.


JWT works similarly.


JWT Secret

Server owns:

JWT_SECRET="super-secret-key"

Only the server knows this.


Client never sees:

JWT_SECRET

Never.


When signing:

Payload
+
Secret
=
Signature

Later:

Payload
+
Secret
=
Expected Signature

If signatures match:

Token is authentic

Step 4: Return JWT to Client

Server responds:

{
  "token": "eyJhbGc..."
}

At this point:

Authentication complete

The user is logged in.


Step 5: Store JWT

The client must store it somewhere.

Common options:


Local Storage

localStorage.setItem("token", token)

Simple.

Popular in development.


HttpOnly Cookie

Preferred in many production systems.

More resistant to XSS attacks.


Regardless of storage:

Client now possesses proof of identity.

Bearer Tokens

Now we reach one of the most misunderstood concepts.


What Is a Bearer Token?

Suppose we have:

JWT = abc.xyz.123

Future request:

GET /dashboard

Authorization: Bearer abc.xyz.123

The word:

Bearer

means:

Whoever bears
(possesses)
this token
is considered authenticated.

Think of:

Movie ticket

The theater does not care who bought it.

The person carrying it gets entry.


JWT bearer authentication works similarly.


Possession grants access.


Step 6: Protected Request

Now we visit:

GET /dashboard

Header:

Authorization: Bearer eyJhbGc...

Server receives request.

Extracts:

const token =
authHeader.split(" ")[1]

Result:

eyJhbGc...

Step 7: Verify JWT

This is where:

jwt.verify()

enters.


Many beginners think:

verify()

means:

Ask database

It does not.


Instead:

Take token
Take secret
Recompute signature
Compare signatures

If signatures match:

Valid token

If signatures differ:

Token modified

Authentication fails.


Step 8: Decode Payload

Successful verification returns:

{
  id: 123,
  username: "skyy",
  iat: ...
  exp: ...
}

This is called:

Decoded payload

We now know:

Who made the request

without querying login credentials again.


Why Is JWT Stateless?

Traditional session systems:

Server remembers users

JWT systems:

Token remembers users

Huge difference.


Session:

Server Memory
└── Session ID

JWT:

Client carries identity

Server stores nothing.


Logout Cycle

This is where JWT behaves differently from sessions.


Traditional Sessions

Logout:

Delete session

Done.

User immediately loses access.


JWT Logout

Server does not store JWT.

Remember:

Stateless

So what can the server delete?

Nothing.


The server has no record of active JWTs.


How JWT Logout Works

Usually:

localStorage.removeItem("token")

or

Delete cookie

Client loses token.

Future requests:

GET /dashboard

contain:

No token

Authentication fails.


JWT Expiration

Tokens should never live forever.

Example:

jwt.sign(payload, secret, {
  expiresIn: "30d"
})

Adds:

{
  "exp": 1783514844
}

to payload.


During verification:

jwt.verify()

checks:

Current Time
<
Expiration Time

If expired:

TokenExpiredError

Authentication fails.


Refresh Tokens (Advanced)

Large applications often use:

Access Token

Short lifespan:

15 minutes

Refresh Token

Long lifespan:

30 days

Flow:

Access Token expires
↓
Client sends Refresh Token
↓
Server issues new Access Token

This provides:

Security
+
Convenience

Complete Lifecycle

REGISTER
   ↓
Create User
   ↓
Store Hashed Password
   ↓
LOGIN
   ↓
Verify Password
   ↓
Sign JWT
   ↓
Return JWT
   ↓
Store JWT
   ↓
Protected Request
   ↓
Authorization: Bearer <token>
   ↓
Verify JWT
   ↓
Decode Payload
   ↓
Authorize User
   ↓
Return Data
   ↓
LOGOUT
   ↓
Delete Token
   ↓
Future Requests Fail

The Most Important Mental Model

A JWT is not a session.

A JWT is not encrypted user data.

A JWT is essentially:

A digitally signed identity card

The payload contains identity claims:

User ID
Username
Role

The signature proves:

The server created this
and nobody has modified it.

Every authenticated request is simply the client presenting that signed identity card and the server checking whether the signature is still valid. That's the entire JWT authentication model in one sentence.

Excellent question.

The idea of Access Tokens and Refresh Tokens exists because we are trying to balance two competing goals:

Security
vs
Convenience

If we maximize one, we usually weaken the other.


First: What if we only use one JWT?

Suppose we issue:

jwt.sign(payload, secret, {
  expiresIn: "30d"
});

The user logs in once and stays logged in for 30 days.

Sounds great.

But consider what happens if the token is stolen.


Scenario: Token Theft

Suppose an attacker somehow obtains our JWT.

Maybe through:

  • XSS
  • Malware
  • Shared computer
  • Browser extension
  • Leaked logs

Now the attacker has:

eyJhbGc...

Remember what "Bearer" means:

Whoever possesses the token
is considered authenticated.

The attacker can now make requests as us.


And because the token expires in:

30 days

the attacker has access for:

30 days

That's a long time.


Solution #1: Make JWT Short-Lived

Instead of:

expiresIn: "30d"

we do:

expiresIn: "15m"

Now if the token is stolen:

Maximum damage:
15 minutes

Much safer.


But now we have another problem.


User Experience Becomes Terrible

Imagine:

Login
↓
15 minutes pass
↓
Token expires
↓
Login again
↓
15 minutes pass
↓
Login again

Nobody wants this.

Especially on:

  • Gmail
  • Facebook
  • Amazon
  • Netflix

We expect to stay logged in.


So we need:

Short-lived authentication
+
Long-lived login

This seems contradictory.


Enter Refresh Tokens

Instead of one token:

JWT

we create two.


Access Token

Short lifespan:

15 minutes

Contains:

{
  "id": 123,
  "role": "user"
}

Used on every API request.


Refresh Token

Long lifespan:

30 days

Used only to obtain new access tokens.


Now the flow looks like:

Login
↓
Receive:
    Access Token (15m)
    Refresh Token (30d)

Everyday Usage

When we request:

GET /profile

we send:

Authorization: Bearer ACCESS_TOKEN

Only the access token.

Never the refresh token.


After 15 minutes:

Access Token expires

Server rejects requests.


Instead of forcing login:

Client sends:

POST /refresh

with refresh token.


Server verifies refresh token.

If valid:

Issue new Access Token

without asking for username/password again.


User never notices.


Why Is This More Secure?

Imagine an attacker steals:

Access Token

Because it expires quickly:

15 minutes

attacker loses access soon.


Without refresh tokens:

Stolen token
=
30 days access

With refresh tokens:

Stolen access token
=
15 minutes access

Huge improvement.


Why Not Just Use Refresh Tokens Everywhere?

Many beginners ask:

If refresh tokens last 30 days, why not use them for normal requests?

Because refresh tokens are extremely valuable.

Think of them as:

Master Key

while access tokens are:

Temporary Visitor Pass

We want the master key exposed as little as possible.

So:

Refresh Token

is only sent occasionally.

Maybe:

Once every 15 minutes

or even less.


While:

Access Token

is sent on every request.


Real-World Analogy

Think of a hotel.


Access Token

Hotel room key.

Valid for one day.

Used constantly.


Refresh Token

Identity document at reception.

Valid for whole vacation.

When room key expires:

Show ID
↓
Receive new room key

We don't walk around all day carrying our passport.

We keep it safe.

Same idea.


Another Huge Advantage: Revocation

Remember when we discussed JWT being stateless?

One problem was:

We cannot easily revoke JWTs.

Suppose:

JWT valid for 30 days

User changes password.

Attacker still has token.

Bad.


With refresh tokens we often store them in a database.

Example:

RefreshToken
└── User 123

When user:

Changes password
Logs out
Gets banned

we delete refresh token.

Now:

No new access tokens
can be generated.

Access token may live:

15 minutes

but after that:

Session ends completely.

Typical Modern Setup

Access Token
-----------
Lifetime:
15 minutes

Used:
Every API request

Stored:
Memory or cookie

Refresh Token
------------
Lifetime:
30 days

Used:
Only to get new access tokens

Stored:
Secure HttpOnly cookie
Database-backed

The Core Idea

If we use only one token:

Long expiration
=
Convenient
=
Less secure

If we use only short-lived tokens:

Secure
=
Annoying user experience

Using Access Tokens + Refresh Tokens gives us:

Short-lived access
+
Long-lived login
+
Ability to revoke sessions
+
Better security

That's why almost every large authentication system todayβ€”whether from Google, Microsoft, GitHub, or Auth0β€”uses some variation of the access-token/refresh-token model rather than relying on a single long-lived JWT.


Now we're getting into the architecture that most real-world applications use.

To understand cookies in authentication, we first need to understand that cookies are not authentication themselves.

A cookie is simply a storage and transport mechanism.

Think of it as:

A small piece of data that the browser
automatically stores and sends back
to the server with future requests.

The cookie doesn't care whether it contains:

  • A session ID
  • A JWT
  • A theme preference
  • A shopping cart ID

It's just a container.


What Exactly Is a Cookie?

Suppose the server responds:

Set-Cookie: username=skyy

The browser stores:

username=skyy

Now every request automatically includes:

Cookie: username=skyy

Without us writing:

headers: {
  Cookie: ...
}

The browser does it automatically.


Why Were Cookies Created?

Remember:

HTTP is stateless.

The browser and server needed a way to maintain state across requests.

Cookies were invented to solve exactly that problem.


Before JWT: Sessions + Cookies

Historically:

Login

We send:

{
  "username": "skyy",
  "password": "secret"
}

Server verifies credentials.


Server creates:

Session ID:
abc123xyz

Stores:

abc123xyz -> User #57

inside memory or Redis.


Then sends:

Set-Cookie:
sessionid=abc123xyz

Browser stores it.


Future requests:

Cookie:
sessionid=abc123xyz

Server looks it up:

abc123xyz
↓
User #57

Authenticated.


JWT + LocalStorage

Most beginner docs teach:

localStorage.setItem(
  "token",
  jwtToken
);

and later:

axios.get("/dashboard", {
  headers: {
    Authorization:
      `Bearer ${token}`
  }
});

This works.

But there are drawbacks.


Problem: XSS

Imagine malicious JavaScript executes:

const token =
localStorage.getItem("token");

It can steal the JWT.

Why?

Because:

LocalStorage is accessible
to JavaScript.

Any script can read it.


JWT + Cookies

Instead of:

localStorage.setItem(...)

Server does:

Set-Cookie:
token=eyJhbGc...

Browser stores JWT inside a cookie.


Now every request automatically sends:

Cookie:
token=eyJhbGc...

Server extracts token from cookies.


No need for:

Authorization:
Bearer ...

header anymore.


Where Does HttpOnly Come In?

This is one of the most important cookie settings.

Server sends:

Set-Cookie:
token=abc123;
HttpOnly

Now:

document.cookie

cannot access it.


Even if malicious JavaScript runs:

const token =
document.cookie;

the authentication cookie remains hidden.


This is why modern applications often store JWTs in:

HttpOnly Cookies

instead of:

LocalStorage

Authentication Flow with Cookies

Let's walk through the entire lifecycle.


Login

User submits:

{
  "username": "skyy",
  "password": "secret"
}

Server verifies credentials.

Creates JWT:

const token =
jwt.sign(...)

Instead of returning:

{
  "token": "..."
}

Server sends:

Set-Cookie:
token=eyJhbGc...

Browser stores it automatically.


Future Requests

User visits:

GET /dashboard

Browser automatically includes:

Cookie:
token=eyJhbGc...

Server extracts:

req.cookies.token

verifies JWT

and authenticates user.


Logout with Cookies

Logout becomes very simple.

Server sends:

Set-Cookie:
token=;
Expires=Thu, 01 Jan 1970

Browser deletes cookie.


Future requests:

Cookie:
(nothing)

Authentication fails.


Why Access Tokens Are Often Stored in Memory

Modern systems frequently do:

Access Token
↓
Memory

Refresh Token
↓
HttpOnly Cookie

Let's understand why.


Access Token Usage

Access token is sent frequently:

GET /profile
GET /products
GET /orders
GET /cart

Possibly dozens of times.


Access token usually lives:

5-15 minutes

Very short.


If stolen:

Damage is limited.

Refresh Token Usage

Refresh token lives:

30 days

or more.

Much more valuable.


If stolen:

Attacker can generate
new access tokens.

Very dangerous.


Therefore:

Refresh Token

gets stronger protection.

Usually:

HttpOnly
Secure
SameSite

cookie.


Cookie Security Flags

These are critical.


HttpOnly

Set-Cookie:
token=abc;
HttpOnly

JavaScript cannot read it.

Protects against:

XSS

Secure

Set-Cookie:
token=abc;
Secure

Cookie only travels via:

HTTPS

Never plain HTTP.


SameSite

Protects against:

CSRF

Cross-Site Request Forgery.


Example:

SameSite=Strict

Only same-site requests get cookie.


Real Production Architecture

Today many systems use:

Login
 ↓
Access Token
(15 minutes)

Refresh Token
(30 days)

Storage:

Access Token
↓
Memory

Refresh Token
↓
HttpOnly Cookie

Flow:

Login
 ↓
Receive both tokens
 ↓
Use Access Token
 ↓
Expires
 ↓
Send Refresh Token
 ↓
Receive new Access Token
 ↓
Continue working

Why Not Put Everything in Cookies?

We could.

Many applications do.

For example:

Cookie:
accessToken=...
refreshToken=...

Both HttpOnly.

This is common in traditional web applications.


Why Not Put Everything in LocalStorage?

Because:

LocalStorage

is readable by JavaScript.

Therefore:

XSS risk increases.

Mental Model

Think of cookies as:

An automatic delivery service
between browser and server.

The browser:

  1. Stores the cookie.
  2. Sends it automatically.
  3. Updates it automatically.
  4. Deletes it when instructed.

JWTs, session IDs, refresh tokens, and access tokens are simply different kinds of data that can be placed inside that delivery service.

That's why in modern authentication:

Cookie
=
Transport & Storage Mechanism

JWT
=
Identity Document

Access Token
=
Short-lived identity document

Refresh Token
=
Long-lived renewal permit

Cookies and JWTs are not competing technologies. In most production systems, they are often used together, with cookies acting as the secure transport mechanism for JWT-based authentication.


HashingπŸ”’ Vs Encryption 🎭 - The core differences.

This is one of the most important concepts in security, and many developers confuse them.

At first glance, hashing and encryption seem similar because both transform data into something unreadable. But they solve completely different problems.


Encryption

Encryption is:

Readable Data
    ↓
Encryption Key
    ↓
Encrypted Data
    ↓
Decryption Key
    ↓
Original Data

The original data can be recovered.


Hashing

Hashing is:

Readable Data
    ↓
Hash Function
    ↓
Hash

The original data cannot be recovered.


Think of it this way:

Encryption

Locking a document in a safe.

We can unlock the safe later.


Hashing

Putting a document into a paper shredder.

We can verify what went in, but we cannot reconstruct the original document from the shredded pieces.


Why Do We Need Encryption?

Encryption protects:

Confidentiality

Meaning:

Prevent unauthorized people
from reading data.

Examples:

  • Credit card numbers
  • Medical records
  • Messages
  • Banking information

Suppose we have:

Hello Skyy

After encryption:

a8fK9s2LpQ==

Looks meaningless.

But with the correct key:

SecretKey123

we can decrypt it:

Hello Skyy

again.


Why Do We Need Hashing?

Hashing protects:

Integrity

and

Verification

not secrecy.


Suppose a user registers with:

password123

We never want to store:

password123

inside the database.

If the database gets hacked:

Attacker gets every password.

Disaster.


Instead we store:

Hash(password123)

Example:

ef92b778ba...

Database stores:

{
  "username": "skyy",
  "password":
  "ef92b778ba..."
}

Notice:

Original password is gone.

The Login Process

Many beginners ask:

If we cannot recover the password from the hash, how do we log in?

Excellent question.


During registration:

password123
    ↓
Hash Function
    ↓
ef92b778ba...

Store:

ef92b778ba...

Later during login:

User enters:

password123

Again:

password123
    ↓
Hash Function
    ↓
ef92b778ba...

Compare:

Stored Hash
=
Generated Hash

If equal:

Password is correct.

Notice:

We never decrypted anything.

We simply hashed twice and compared.


Deterministic Nature of Hashing

A hash function always produces the same output for the same input.

Example:

Hash("hello")

always becomes:

2cf24dba5...

Every time.


This property allows verification.


One-Way vs Two-Way

This is the easiest way to remember the difference.


Encryption

Two-way process:

Encrypt
↓
Decrypt

We can go both directions.


Hashing

One-way process:

Hash
↓
Done

No reverse operation.


Real Example

Suppose:

MyPassword123

Encryption

MyPassword123
↓
Encrypt
↓
X7aP91kLm2

Later:

X7aP91kLm2
↓
Decrypt
↓
MyPassword123

Hashing

MyPassword123
↓
Hash
↓
93bc6f0a...

That's it.

No:

Unhash()

function exists.


Why Not Encrypt Passwords?

A common beginner question.

Suppose we encrypt passwords:

password123
↓
Encrypted
↓
X7aP91kLm2

To verify login we must decrypt:

X7aP91kLm2
↓
Decrypt
↓
password123

This means the server must possess:

Decryption Key

somewhere.


If attackers steal:

Database
+
Decryption Key

they get every password.


Hashing avoids this.

Because:

No decryption key exists.

What Is a Salt?

Modern password hashing includes:

Salt

A random value added before hashing.

Example:

password123
+
x8Qk91

becomes:

password123x8Qk91

Then hashed.


Why?

Imagine two users choose:

password123

Without salt:

Hash(password123)

produces:

abc123

for both users.

Database:

User A -> abc123
User B -> abc123

Attackers immediately know:

Same password.

With salt:

User A:
password123 + randomSaltA

User B:
password123 + randomSaltB

Different hashes.


Where Does bcrypt Fit In?

bcrypt is:

A password hashing algorithm

specifically designed for passwords.

Not encryption.

Hashing.


Example:

const hashedPassword =
await bcrypt.hash(password, 10);

Produces:

$2b$10$N7K...

Verification:

await bcrypt.compare(
  password,
  hashedPassword
);

bcrypt hashes the input and checks whether it matches.


Where Does JWT Fit?

This is where many people get confused.

JWT uses neither hashing nor encryption for authentication.

JWT primarily uses:

Digital Signatures

which rely on cryptographic hashing internally.


When we do:

jwt.sign(payload, secret)

the library:

  1. Creates payload
  2. Hashes content
  3. Uses secret key
  4. Generates signature

Result:

JWT

When we do:

jwt.verify(token, secret)

it:

  1. Recomputes hash
  2. Recomputes signature
  3. Compares signatures

If equal:

Token is authentic.

What Each Technology Is Used For

Hashing

Used for:

  • Passwords
  • File integrity
  • Checksums
  • Digital signatures
  • Blockchain

Goal:

Verification

Encryption

Used for:

  • HTTPS
  • Credit card storage
  • Secure messaging
  • Banking systems
  • VPNs

Goal:

Confidentiality

University-Level Mental Model

Think of:

Encryption

as placing a document into a locked box.

The document still exists.

Anyone with the key can read it.


Think of:

Hashing

as generating a unique fingerprint of the document.

The fingerprint proves:

This document is the same one
we saw earlier.

But the fingerprint does not contain the document itself.


Summary

Feature Hashing Encryption
Purpose Verification & Integrity Confidentiality
Reversible? No Yes
Uses Keys? No (normally) Yes
Can Recover Original Data? No Yes
Used For Passwords? Yes No
Used For HTTPS? No Yes
Used In JWT Signatures? Yes (internally) Not usually
Example bcrypt, Argon2, SHA-256 AES, RSA

The simplest way to remember it is:

Hashing:
Can we prove data is correct?

Encryption:
Can we hide data from others?

If we need to verify, we hash.

If we need to hide, we encrypt.

Releases

No releases published

Packages

 
 
 

Contributors