Skip to main content

Command Palette

Search for a command to run...

Why Authentication Is More Complicated Than Most Frontend Engineers Think

Frontend Security for Engineers (Part 1 of 3)

Updated
7 min read
Why Authentication Is More Complicated Than Most Frontend Engineers Think

Most frontend engineers have worked with authentication.

We've handled login forms, protected routes, JWTs, cookies, refresh tokens, and user sessions.

But for a long time, my understanding of authentication was mostly implementation-focused.

I knew how to make it work.

What I didn't fully understand was why all these pieces existed in the first place.

Why do we need access tokens and refresh tokens?

Why do some teams use sessions while others use JWTs?

Why are so many companies moving away from storing tokens in localStorage?

And what problem is authentication actually solving?

Once I started looking at authentication from first principles rather than framework tutorials, the entire system became much easier to reason about.

The Problem Authentication Is Trying to Solve

Authentication exists because HTTP is stateless.

That's one of the most important concepts to understand.

Imagine a user visits your application and performs three actions:

POST /login

GET /profile

GET /settings

To us, those requests clearly belong to the same person.

To the server, they are simply three independent HTTP requests.

The server does not automatically know:

  • who sent them

  • whether they are related

  • whether the user already logged in

Every request arrives without memory of previous requests.

This creates a fundamental problem:

How does the server know that the person requesting /profile is the same person who successfully logged in earlier?

Authentication exists to solve that problem.

A Mental Model That Helped Me

Think about entering a theme park.

At the entrance:

  • your identity is verified

  • your ticket is checked

Then you're given a wristband.

For the rest of the day, you don't repeatedly show your passport every time you want to ride something.

The wristband becomes proof that you've already been verified.

Authentication systems work in a very similar way.

The login process verifies your identity.

After that, some form of credential acts as your digital wristband.

The rest of the authentication architecture is really just different ways of managing that wristband.

The Original Solution: Sessions

Before JWTs became popular, session-based authentication was the dominant approach.

The flow is relatively simple.

The user logs in:

POST /login

The server validates the credentials and creates a session.

Something like:

{
  "sessionId": "abc123",
  "userId": 42
}

The session is stored on the server.

The browser receives a cookie:

Set-Cookie:
sessionId=abc123

From that point onwards, every request automatically includes the session cookie.

Cookie:
sessionId=abc123

The server looks up the session and identifies the user.

Sessions often get treated as "old technology".

That's a mistake.

They're still widely used because they provide several benefits:

  • easy revocation

  • server-controlled identity

  • straightforward security model

  • simple implementation

The tradeoff is that the server must maintain session state.

At very large scale, managing millions of active sessions can become more complicated.

Enter JWTs

JWT stands for JSON Web Token.

Instead of storing user identity on the server, identity information is stored inside a signed token.

A simplified payload might look like:

{
  "userId": 42,
  "role": "admin"
}

After login, the server returns the token.

Future requests include it:

Authorization:
Bearer <token>

The server verifies the signature and trusts the contents.

Unlike sessions, there is no database lookup required to identify the user.

That's why JWTs are often described as stateless.

The Misconception I See Everywhere

Many developers unconsciously assume:

JWT = modern and secure
Sessions = old and less secure

That's not really true.

JWTs are not inherently more secure than sessions.

They simply solve a different architectural problem.

JWTs are particularly useful when:

  • multiple services need to verify identity

  • mobile and web clients share authentication

  • systems are distributed across several services

Security comes from implementation.

A poorly implemented JWT system can be far less secure than a well-designed session-based system.

Why Access Tokens Exist

Once a user is authenticated, we need a way to prove that identity on future requests.

That's where access tokens come in.

An access token is usually short-lived.

For example:

15 minutes
30 minutes
1 hour

Every authenticated API request uses that token.

GET /profile
POST /orders
PATCH /settings

The problem is obvious.

Eventually the token expires.

And that's actually a good thing.

If a token is stolen, its usefulness is limited.

Why Refresh Tokens Exist

Without refresh tokens, users would need to log in repeatedly throughout the day.

That creates a terrible user experience.

Instead, most modern systems use two credentials:

Access Token

Short-lived.

Example:

15 minutes

Refresh Token

Long-lived.

Example:

30 days

When the access token expires:

Access Token Expired
          ↓
Refresh Endpoint
          ↓
New Access Token

The user remains logged in without interruption.

This is the pattern you'll find in many production systems today.

localStorage vs HttpOnly Cookies

This is where frontend security becomes particularly interesting.

For years, many tutorials recommended storing JWTs in localStorage.

localStorage.setItem("token", jwt);

It's easy.

It's convenient.

It's also risky.

If an attacker successfully injects JavaScript through an XSS vulnerability, they can access localStorage directly.

localStorage.getItem("token");

Your token can be stolen.

Why HttpOnly Cookies Changed My Thinking

A more secure approach is storing authentication cookies with:

HttpOnly
Secure
SameSite

The key benefit is HttpOnly.

JavaScript cannot read these cookies.

Even if malicious JavaScript executes through an XSS attack, it cannot simply grab the token from browser storage.

That's one reason many modern applications now prefer cookie-based authentication.

The browser handles the credential automatically while keeping it inaccessible to application code.

Authentication and Authorization Are Not The Same Thing

This distinction confused me early on.

Authentication answers:

Who are you?

Authorisation answers:

What are you allowed to do?

Examples:

Authentication:

User #42

Authorisation:

Admin

A user might be successfully authenticated and still not have permission to perform certain actions.

One of the most common mistakes in frontend applications is assuming hidden buttons create security.

For example:

if (user.role === "admin") {
  showDeleteButton();
}

Hiding the button improves the user experience.

It does not create security.

Authorisation must always be enforced on the backend.

Always.

The Five Questions I Now Ask About Any Authentication System

Whenever I encounter a new authentication architecture, I try to answer five questions.

  1. Where is identity stored?

    • Session?

    • JWT?

    • Cookie?

  2. Can XSS steal it?

    • localStorage?

    • HttpOnly cookie?

  3. Can CSRF abuse it?

    • SameSite?

    • CSRF protection?

  4. How is expiry handled?

    • Access token?

    • Refresh token?

  5. How are permissions enforced?

    • Frontend only?

    • Backend validation?

If I can answer those five questions, I usually have a solid understanding of the system.

How This Changed The Way I Think About Authentication

The biggest shift for me was realising that authentication isn't really about JWTs, cookies, or login forms.

Those are implementation details.

The real problem is identity.

Every authentication system is ultimately trying to answer one question:

How can a stateless protocol reliably recognise the same user across multiple requests?

Once I started viewing authentication through that lens, sessions, JWTs, access tokens, refresh tokens, cookies, and route protection all began to fit together as parts of a larger system rather than isolated concepts.

And that's when authentication started making a lot more sense.

Final Thought

A lot of frontend engineers can implement authentication.

Far fewer can explain why the architecture looks the way it does.

Understanding the underlying problems – statelessness, identity, token lifecycles, XSS risks, and authorisation boundaries – makes it much easier to evaluate any authentication system you encounter, regardless of the framework or stack being used.

That's the point where authentication stops being a tutorial topic and starts becoming an engineering concept.