Authentication in the Real World: Lessons from Nuxt 3, Laravel, React, and Express

Facts first: a practical blueprint for cookie sessions (Nuxt/React), Sanctum (Laravel), and HMAC/JWT (Express), with code.

nuxt3laravelreactexpressauthenticationsecurityux
Browser-based login flow secured by HTTP-only session cookies
Authentication is where browsers, frameworks, and microservices collide.

Why I'm Writing This

I've worked on survey tools, health trackers, and account dashboards. The common friction point was authentication. Here are the approaches that held up in practice, with working snippets.

The Pain Points I Ran Into

  • LocalStorage tokens broke under SSR and subdomains.
  • CORS misconfigurations silently blocked cookies.
  • Safari’s cookie rules exposed bugs Chrome hid.
  • Service-to-service trust needed signed requests, not user cookies.
  • Public forms attracted bots until I hardened them.

Nuxt 3 (Vue)

Nuxt 3 using HTTP-only cookie sessions compatible with SSR
export function useAuth() {
  const user = useState('user', () => null as any);

  async function login(email: string, password: string) {
    await $fetch('/api/login', {
      method: 'POST',
      body: { email, password },
      credentials: 'include',
    });
    await refreshUser();
  }

  async function refreshUser() {
    user.value = await $fetch('/api/me', { credentials: 'include' }).catch(() => null);
  }

  return { user, login, refreshUser };
}

Fact learned: Cookies work naturally with SSR. Avoid storing tokens in LocalStorage: they cause hydration bugs and add XSS risk.

Laravel 10

Laravel Sanctum handling session-based SPA authentication
Route::post('/login', function (Request $request) {{
  $credentials = $request->validate([
      'email' => 'required|email',
      'password' => 'required|string|min:6',
  ]);

  if (! Auth::attempt($credentials, true)) {{
      return response()->json(['message' => 'Invalid credentials'], 422);
  }}

  $request->session()->regenerate();
  return response()->noContent();
}});

Route::get('/me', fn (Request $r) => $r->user())->middleware('auth:sanctum');

Fact learned: Sanctum is built for SPAs/SSR. Regenerate sessions on login/logout to prevent fixation.

Express.js

Two Express services authenticating requests with HMAC signatures
import crypto from 'crypto';

function sign(body, secret) {{
  return crypto.createHmac('sha256', secret).update(body).digest('hex');
}}

function verify(req, res, next) {{
  const sig = req.header('x-signature');
  const expected = sign(req.rawBody, process.env.S2S_SECRET);
  if (sig !== expected) return res.status(401).send('Invalid signature');
  next();
}}

Fact learned: Don’t pass user cookies between services. Use HMAC or short-lived JWTs; rotate secrets.

React

React client sending credentials with fetch to establish a secure session
async function login(email, password) {{
  await fetch('/api/login', {{
    method: 'POST',
    credentials: 'include',
    headers: {{ 'Content-Type': 'application/json' }},
    body: JSON.stringify({{ email, password }}),
  }});
}}

Fact learned: Cookies still win for browser apps. With Next.js, SSR can read the cookie server-side.

Controls That Actually Worked

  • Rate limiting for login/signup endpoints.
  • Honeypot fields that bots fill but humans ignore.
  • Analytics on failed logins to surface UX issues early.
  • SameSite=Lax + Secure cookies to avoid silent drops in Safari.

Final Thoughts

  • Nuxt / React: Cookie sessions are safer and SSR-friendly than LocalStorage tokens.
  • Laravel: Sanctum + sessions is the simplest, reliable path for SPA/SSR.
  • Express: Prefer HMAC/JWT for service-to-service; never forward user cookies.
  • Browsers: Test in Safari early; its stricter rules expose hidden bugs.
  • Security + UX: Pair rate limits + honeypots with analytics; passwords alone aren’t “secure.”