Authentication is one of those topics that makes a lot of beginners freeze up. It sounds serious — like something only security experts should touch.
But once you strip away the fancy vocabulary, it's really just about answering one simple question: "How does my app know who this person is?"
Let's walk through it together, nice and slow.
First, Let's Clear Up Two Confusing Words
Before touching any code, there are two terms people constantly mix up: authentication and authorization. They sound similar, but they mean very different things.
- Authentication = Proving who you are. Like showing your ID at the airport.
- Authorization = What you're allowed to do once you're recognized. Like your ID getting you past security, but only your boarding pass lets you onto a specific plane.
So authentication gets you in the door. Authorization decides which rooms you're allowed to walk into once you're inside.
Why Should You Even Bother With Authentication?
It's not just about locking hackers out (though that's a big part of it). Good authentication also:
- Remembers who a user is, so they don't have to log in every five minutes
- Keeps personal data private and safe
- Builds trust — nobody wants to use an app that feels unsafe
- Protects your app from people pretending to be someone they're not
With how common cyberattacks have become, skipping authentication is basically leaving your front door wide open.
The Main Authentication Methods in Express.js
There's no single "correct" way to handle authentication. Different apps need different levels of security and convenience. Here are the four most common approaches you'll run into.
1. Basic Authentication (The Simplest, Least Secure Option)
This is the most old-school method. Every time someone makes a request, they send their username and password along with it, tucked inside the request headers.
app.use((req, res, next) => {
const auth = {login: 'admin', password: 'secret'};
const b64auth = (req.headers.authorization || '').split(' ')[1] || '';
const [login, password] = Buffer.from(b64auth, 'base64').toString().split(':');
if (login && password && login === auth.login && password === auth.password) {
return next();
}
res.set('WWW-Authenticate', 'Basic realm="401"');
res.status(401).send('Authentication required.');
});
What's happening here:
const auth = {...}— This is just a hardcoded example username and password to compare against. In a real app, you'd be checking against a database instead.- Grabbing the header — When someone logs in with Basic Authentication, their browser sends their credentials encoded (not encrypted!) inside the
authorizationheader. This line pulls that value out. - Decoding it — The credentials arrive scrambled in a format called Base64. This line unscrambles it back into a readable
login:passwordpair. - Checking the match — If the submitted login and password match what's expected, the request is allowed to continue with
next(). - If it doesn't match — The server responds with a 401 status, which is the standard "you're not authenticated" error code, and asks the browser to prompt for credentials again.
The catch: Since the credentials are just encoded, not encrypted, this method is only safe when paired with HTTPS. Without it, anyone snooping on the connection could easily decode the credentials. Think of it as writing your password on a postcard instead of sealing it in an envelope — it's readable if someone intercepts it along the way.
2. Token-Based Authentication (The Modern Favorite)
Instead of sending your username and password over and over with every request, you log in once, and the server hands you a token — kind of like a wristband at a concert. From then on, you just show the wristband instead of proving your identity all over again.
The most popular version of this is JWT (JSON Web Token).
const jwt = require('jsonwebtoken');
// Middleware to check token
const verifyToken = (req, res, next) => {
const token = req.headers['x-access-token'];
if (!token) return res.status(403).send('Token is missing.');
jwt.verify(token, 'your-secure-key', (err, decoded) => {
if (err) return res.status(500).send('Failed to authenticate token.');
req.userId = decoded.id;
next();
});
};
What's happening here:
- Importing
jsonwebtoken— This is the library that does the heavy lifting of creating and checking tokens. - Grabbing the token — Instead of a username and password, the client sends a token in the request headers (
x-access-tokenin this example). - No token? Reject it. If there's nothing there, the server responds immediately with a 403, meaning "access forbidden."
jwt.verify(...)— This checks whether the token is legit and hasn't been tampered with, using a secret key only your server knows.- If it's invalid — The server responds with an error.
- If it's valid — The decoded information (like the user's ID) gets attached to the request object, so the rest of your app knows exactly who's making the request.
Tokens are popular because they're lightweight, work well across multiple servers, and are great for mobile apps and APIs.
3. OAuth (The "Login With Google/Facebook" Method)
You've definitely used this before, even if you didn't know it by name. OAuth is what happens whenever you click "Sign in with Google" or "Continue with Facebook" instead of creating a brand-new password for every single website.
OAuth lets a third-party service (like Google) vouch for a user's identity, without your app ever seeing or storing their actual password.
Here's the general flow, broken down simply:
- Register your app with the OAuth provider (like Google or Facebook), so they know who's asking.
- Redirect the user to that provider's login page.
- Handle the callback — once the user logs in there, the provider sends them back to your app along with an access token.
- Use the token to access whatever information the user allowed you to see (like their name or email).
Because setting this up from scratch is fairly involved, most developers use a library like Passport.js to handle the heavy lifting instead of building it by hand.
4. Session-Based Authentication (The Classic Server-Remembers-You Method)
This method works a little differently. Instead of the client holding onto a token, the server keeps track of who's logged in, and gives the user's browser a small cookie as a reference number to that stored session.
const session = require('express-session');
app.use(session({
secret: 'your-secret-key',
resave: false,
saveUninitialized: true,
cookie: { secure: true }
}));
app.get('/login', (req, res) => {
// Store user session info
req.session.userId = 'exampleUserId';
res.send('Logged in!');
});
What's happening here:
express-session— This is the library that manages sessions for you.app.use(session({...}))— This sets up session handling for your whole app. Thesecretis used to keep session data secure, andcookie: { secure: true }makes sure the cookie is only sent over HTTPS.req.session.userId = ...— Once a user logs in, you can store whatever info you want about them right on the session object, and Express handles remembering it for you on future requests.
Think of it like getting a wristband at a coat check. You don't carry your coat around all day — you just carry the ticket, and the server (coat check attendant) keeps track of the actual details behind the scenes.
So... Which One Should You Actually Use?
Honestly, it depends on what you're building. Here's a simple way to think about it:
- Building something small or just learning? Basic or session-based authentication is easy enough to get started with.
- Building an API or a mobile app? Token-based authentication (JWT) tends to work best since it doesn't rely on cookies or server memory.
- Want users to log in with existing accounts like Google or GitHub? OAuth is the way to go.
- Care most about scaling to lots of users across multiple servers? Tokens are usually more flexible than sessions, since sessions require the server to remember each user.
A few extra things worth thinking about:
- Scalability — If your app might grow big, token-based auth generally scales more easily than sessions.
- Security needs — OAuth tends to offer stronger security, but it takes more effort to set up properly.
- User experience — Nobody enjoys re-entering passwords constantly, so smoother methods (like OAuth or tokens) tend to keep users happier.