React Login Series - Auth Context Session, Persistence & Logout | Part 5

Post 5: Auth Context: Session, Persistence & Logout

“Authentication is not a form. It’s application state.”

Next logical posts in the series:

Implementing the API Layers (Node / Express / Nest / etc.) Part 4c Coding the endpoints.

  • Token storage strategies (cookies vs memory)
  • Connecting the API to the AuthContext
  • End-to-end login flow walkthrough

Why Auth Context Exists

By now we have:

  • UI components (Post 2)
  • useAuth hook managing async state (Post 3)
  • authApi abstracting the backend (Post 4)

What we still don’t have is a single source of truth for:

  • whether the user is logged in
  • who the user is
  • how long the session lives
  • how the app reacts to logout

That responsibility belongs to Auth Context.


The Role of AuthContext

AuthContext is:

  • the app’s authentication authority

  • session state manager

  • persistence coordinator

AuthContext is not:

  • responsible for forms

  • making network requests directly

  • aware of UI layout

  • tied to any one route

Think of it as application infrastructure, not a feature component.

Session State: What Do We Store?

At minimum:

  • authentication status

  • user info (if needed)

  • token (or session id)

Example shape:

{
  user: null | { id, email },
  token: null | string,
  isAuthenticated: boolean
}

Rule

If the app needs it globally → Context owns it.


Memory vs localStorage (The Honest Tradeoff)

In-Memory State

  • ✅ Secure against XSS persistence
  • ❌ Lost on refresh
  • ❌ Requires re-login

localStorage

  • ✅ Survives reloads
  • ✅ Simple to implement
  • ❌ Accessible to JS

This series chooses localStorage intentionally, without pretending it’s more secure than it is.


Diagram: Session Persistence Flow

flowchart TD
  LoginSuccess --> AuthContext
  AuthContext -->|save| localStorage
  AuthContext -->|update| MemoryState

  Reload --> AppInit
  AppInit -->|rehydrate| localStorage
  localStorage --> AuthContext

Rehydration on Reload

On app start:

  1. Context checks storage

  2. Valid session restores state

  3. UI updates immediately

No flicker. No guessing.


Diagram: App Startup Rehydration

flowchart TD
  AppStart --> AuthContext
  AuthContext -->|check| localStorage

  localStorage -->|session exists| AuthContext
  AuthContext -->|set authenticated| UI

  localStorage -->|empty| AuthContext
  AuthContext -->|unauthenticated| UI

Logout Patterns (Simple, Predictable)

Logout should:

  • clear storage
  • reset memory state
  • redirect user
  • invalidate session hints

Nothing else.


🚪 Diagram: Logout Flow

flowchart TD
  User --> LogoutAction
  LogoutAction --> AuthContext

  AuthContext -->|clear| localStorage
  AuthContext -->|reset| MemoryState
  AuthContext -->|notify| UI

  UI -->|redirect| Login
src/
├─ app/
│  ├─ App.jsx
│  └─ Router.jsx
│
├─ context/
│  └─ AuthContext.jsx
│
├─ features/
│  └─ auth/
│     ├─ components/
│     │  ├─ LoginForm.jsx
│     │  ├─ PasswordInput.jsx
│     │  ├─ AuthError.jsx
│     │  └─ AuthCTA.jsx
│     ├─ hooks/
│     │  └─ useLogin.js
│     ├─ services/
│     │  └─ authApi.js
│     ├─ validators/
│     │  └─ loginSchema.js
│     ├─ tests/
│     │  ├─ LoginForm.test.jsx
│     │  └─ useLogin.test.js
│     └─ index.js
│
└─ main.jsx


App-Level Integration

AuthContext should wrap everything that depends on session state.

Typically:

<AuthProvider>
  <Router />
</AuthProvider>

This ensures:

  • routes respond to auth changes

  • logout works everywhere

  • session is consistent

Why Context (Not Props, Not Redux)

Context wins here because:

  • scope is global
  • updates are infrequent
  • semantics are clear
  • no external dependency needed

This is exactly what Context was built for.


Right now you have token-in-localStorage auth. Cookie-based auth moves session ownership to the browser + server instead of JS.

With Context in place:

  • protected routes are trivial
  • logout is universal
  • session persistence “just works”
  • testing becomes straightforward

Both Authentication work. They solve different problems.

Review the code found in Github Post 5 Session Persistence

What You Have Now (LocalStorage Token Auth)

How it works (our app today) If we review the code from our last Github checked in branch, we can review how the code matches the below flow:

sequenceDiagram
  participant U as User
  participant F as Frontend
  participant API as Backend
  participant LS as localStorage

  U->>F: Submit login form
  F->>API: POST /login (credentials)
  API-->>F: { token, user, expiresAt }

  F->>LS: Store token + user + expiresAt
  F->>F: Determine isAuthenticated

  U->>F: Refresh / revisit app
  F->>LS: Rehydrate auth state
  F->>F: Check expiresAt > Date.now()

  F->>API: Request with Authorization header
  API->>API: Verify token signature & claims
  API-->>F: Protected response
In the code:
localStorage.setItem(
  "auth_user",
  JSON.stringify({
    token,
    expiresAt,
    user,
  })
);

const isAuthenticated = !!authuser && authuser.expiresAt > Date.now();
flowchart LR
  Frontend -->|Stores| Token[JWT Token]
  Frontend -->|Stores| Meta[expiresAt + user]
  Frontend -->|Decides| AuthState[Authenticated?]

  Frontend -->|Sends token via header| Backend
  Backend -->|Verifies| Token
  Backend -->|Allows / Denies| APIAccess[API Access]

  Frontend:::frontend
  Backend:::backend

  classDef frontend fill:#E3F2FD,stroke:#1E88E5
  classDef backend fill:#FFF3E0,stroke:#FB8C00


** Pros (why this is popular)**

  • Simple to understand
  • Easy to debug
  • Works without backend changes
  • Fine for MVPs
  • Works with mobile, desktop, SPAs

** Cons (important)**

  • Vulnerable to XSS
  • JS can read token
  • Logout is “best effort”
  • Refresh logic is manual
  • Harder to rotate tokens securely

What weve built can be found on Github. Review the code branch found at Post 5 Auth Persistence


Post 5b Preview: Cookie-Based Auth

In the next post, we’ll shift authentication responsibility away from the frontend and into the browser and backend, using HTTP-only cookies and server-managed sessions.

We’ll walk through how cookie-based auth works end-to-end, why the frontend never needs to touch a token, and how this model simplifies state management while improving security by default. We’ll also look at what actually happens during a request, how sessions are validated, and where expiration logic truly belongs.

This post is especially useful if you’ve ever wondered “How am I logged in if I can’t see the token?” or if you’re comparing JWT-based approaches with a more traditional session model.

Post 5b Cookie-Based Auth




Enjoy Reading This Article?

Here are some more articles you might like to read next:

  • React Login Series - Reusing the Feature in Another App | Part 7
  • React Login Series - Login Feature Complete Test Suite | Part 6b
  • What Git Never Explains -- Stacked Branches & Merge Order
  • Why Branch Discipline in Git Isn’t Optional (And How It Saved This Auth Refactor)
  • React Login Series - Testing the Login Feature (RTL + Jest) | Part 6