# Token Authentication and Authorization

Course: [Express.js](https://www.devtutorials.ai/learn/expressjs) · Section: APIs and Errors
Source: https://www.devtutorials.ai/learn/expressjs/api-errors/token-authentication

> Sessions versus tokens, how a JWT is built, login that signs tokens, Bearer middleware, expiry and refresh tokens, role and ownership checks, token storage, Passport and Google sign-in.

## Token Authentication Introduction

Sessions work well for websites. Mobile apps and other servers usually log in a different way.

- **Authentication** = proving **who** you are.
- **Authorization** = deciding **what** you may do.
- **Token** = a signed piece of text the client sends with every request.
- **JWT** (JSON Web Token) = the most common token format. It carries a few facts, called **claims**.
- The client sends it in a header: `Authorization: Bearer <token>`.
- The server checks the signature, so nobody can change the claims without the secret.

**Simple lesson:** A token is a signed ticket. The server checks the signature, then trusts the claims.

### A Real-World Example

Think about the school's annual day.

1. At the office, your ID is checked once. You get a printed pass with your name, class and a stamp.
2. At each gate, the guard only checks the stamp, and reads your class from the pass.
3. The pass says "valid until 6 PM". After that, it is useless.
4. A pass for "Class 8" does not open the staff room.

The office is your login route. The pass is a JWT. The stamp is the signature. The staff room rule is authorization.

In this lesson, you will learn:

1. How sessions and tokens differ, and when to use each.
2. What is inside a JWT.
3. How to issue a token at login and check it on every request.
4. How expiry and refresh tokens work.
5. How roles and ownership checks protect data.
6. Where tokens should be stored, and where Passport and Google sign-in fit.

## Sessions vs Tokens

### Sessions

- The server stores the login. The cookie only holds an ID. See **Cookies and Sessions**.
- Easy to log someone out: delete the session.
- The browser sends the cookie by itself.
- Best for: classic websites and apps on the same domain.

### Tokens

- The token itself holds the login facts. The server stores nothing per user.
- Hard to cancel before it expires, so keep tokens short-lived.
- The client must attach the `Authorization` header itself.
- Best for: mobile apps, single-page apps on another domain, and server-to-server calls.

Both are fine. Many websites use sessions, many mobile apps use tokens, and some apps use both: a session for the website, tokens for the app.

**Simple lesson:** Sessions keep the login on the server. Tokens carry it with every request.

## Inside a JWT

A JWT is three base64url parts joined by dots: **header.payload.signature**.

- **Header:** the algorithm, such as `HS256`.
- **Payload:** the claims, such as `sub` (user ID), `role`, `iat` (issued at) and `exp` (expires).
- **Signature:** an HMAC of the first two parts, made with your secret.
- Anyone can **read** the payload. Only the secret can **sign** it.
- So never put passwords or private details in a JWT.

- `exp` is `iat` plus 900 seconds: 15 minutes.
- Install the package on your computer with `npm install jsonwebtoken`.

A JWT payload is **not secret**. Paste any token into a decoder and you can read it. Keep only IDs and roles in it.

**Simple lesson:** Anyone can read a JWT. Only the server can make a valid one.

## Logging In and Checking Tokens

The flow has two parts:

1. **POST /api/login** checks the password, then signs a token with the user's ID and role.
2. **requireAuth** middleware reads `Authorization: Bearer <token>` on protected routes.
3. It calls `jwt.verify`. A missing, changed or expired token gets **401**.
4. A valid token puts the claims on `req.user`, then calls `next()`.

- Log in, copy the `token`, open **+ Request headers** and type `Authorization: Bearer <token>`. Then send `GET /api/me`.
- The forged token was signed with another secret. The expired one is past its `exp`.
- This lab compares plain passwords to stay short. Real apps compare hashes. See **Cookies and Sessions**.

**Simple lesson:** Sign at login. Verify on every protected request. 401 for anything wrong.

## Expiry and Refresh Tokens

Short tokens are safer, but logging in every 15 minutes is annoying.

- **Access token** = short-lived (5 to 15 minutes). Sent on every API call.
- **Refresh token** = long-lived (days). Used only to get a new access token.
- Keep the refresh token in an **httpOnly, secure, sameSite** cookie, so scripts cannot read it.
- Store refresh tokens on the server too, so **logout can cancel them**.
- **Rotate** them: each refresh gives a new refresh token and cancels the old one.

- After logout, `/api/refresh` answers 401. The refresh token was cancelled on the server.

**Simple lesson:** Short access tokens, a cancellable refresh token in a safe cookie.

## Roles and Ownership

Being logged in is not the same as being allowed.

- **Role check** = only `staff` may change prices. Wrong role gets **403**.
- **Ownership check** = a student may read **their own** orders, not other students' orders.
- Missing ownership checks are one of the most common API bugs: changing `/orders/41` to `/orders/42` shows someone else's data.
- Check ownership in the route, using the ID from the token, never an ID from the request body.
- Some APIs answer **404** instead of 403 for other people's data, so attackers cannot even see it exists.

**Simple lesson:** Authentication says who. Roles and ownership checks say what. Check both on every route.

## Storing Tokens and Other Login Options

### In the Browser

- **httpOnly cookies** cannot be read by scripts, so XSS cannot steal them. Add CSRF protection. See **Express Security**.
- **localStorage** is easy, but any injected script can read it.
- **Memory** (a JavaScript variable) is lost on refresh, but is safest for short access tokens.
- Mobile apps use the phone's secure storage.

- The browser sends the cookie by itself, like the lab's client does here.

### Passport and Google Sign-In

- **Passport** = a popular Express library with "strategies" for many login methods.
- **OAuth 2.0 / OpenID Connect** = "Sign in with Google" and similar. Users log in on Google's page.
- Your server receives a code, checks it with Google, then creates its **own** session or token.
- You never see or store the user's Google password.

```js
import passport from "passport";
import { Strategy as GoogleStrategy } from "passport-google-oauth20";

passport.use(new GoogleStrategy(
  { clientID: process.env.GOOGLE_ID, clientSecret: process.env.GOOGLE_SECRET, callbackURL: "/auth/google/callback" },
  (accessToken, refreshToken, profile, done) => done(null, { id: profile.id, name: profile.displayName }),
));

app.get("/auth/google", passport.authenticate("google", { scope: ["profile", "email"] }));
app.get("/auth/google/callback", passport.authenticate("google", { session: false }), (req, res) => {
  res.json({ token: jwt.sign({ sub: req.user.id }, process.env.JWT_SECRET, { expiresIn: "15m" }) });
});
```

Easy way to remember: **who logs you in can change. What you do after login stays the same:** create your own session or token, then check roles and ownership.

**Simple lesson:** Store tokens where scripts cannot reach them, and let trusted providers handle passwords when you can.

## Common mistakes

<div className="tscroll">

| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| Secrets in the JWT payload | JWTs are encrypted | Anyone can read the payload | Private data leaks |
| `jwt.decode` instead of `jwt.verify` | Decoding checks it | Only `verify` checks the signature | Forged tokens are accepted |
| Long-lived access tokens | Fewer logins is friendlier | Short access, refresh for long sessions | A stolen token works for weeks |
| No ownership check | Logged-in users only read their own data | Check `order.studentId === req.user.sub` | Anyone reads anyone's orders |
| Trusting a user ID from the body | The client sends its own ID | Use `req.user.sub` from the token | Users act as other users |
| Weak or hard-coded secret | Any text works | A long random secret from the environment | Anyone can sign valid tokens |
| No `algorithms` option in `verify` | The default is safe enough | Pin `["HS256"]` | Algorithm-confusion attacks |

</div>

## Check Yourself

## Exercises

## What to Remember

- Authentication proves who. Authorization decides what.
- A JWT is header.payload.signature. Anyone can read it, only your secret can sign it.
- Sign at login, `jwt.verify` on every protected request, 401 for anything wrong.
- Short access tokens, plus a cancellable refresh token in an httpOnly cookie.
- Check roles and ownership on every route, using the ID from the token.
- Passport and Google sign-in handle the login. You still create your own session or token.

Next: **Redirects, Content Negotiation and Proxies**. You will send people to the right place, in the right format, behind a proxy.
