DEV Community

Roberto Luna
Roberto Luna

Posted on

Extending Next.js API Login Cookie Lifetime to One Year for Kiosk‑Mode TV Screens

Extending Next.js API Login Cookie Lifetime to One Year for Kiosk‑Mode TV Screens

TL;DR: I changed the maxAge of the authentication cookie from 30 days to 365 days in src/app/api/login/route.ts so that kiosk‑mode TVs stay logged in without manual re‑auth. The change is a single line but required revisiting token‑rotation, security, and session‑invalid‑ation strategy.


The Problem

Our product ships a “kiosk‑mode” UI that runs on wall‑mounted TVs. Those screens are expected to stay logged in for months, but the default session cookie we set in the login API expires after 30 days:

maxAge: 60 * 60 * 24 * 30, // 30 days
Enter fullscreen mode Exit fullscreen mode

After a month the TV shows the login screen again, which is unacceptable for a hands‑off deployment. The symptom was a 401 response from the backend once the cookie expired, and the UI would reload the login page automatically. No error stack trace, just a silent auth failure.

The challenge: extend the session lifetime without sacrificing the security guarantees we already have (HTTP‑only, Secure, SameSite=Lax). Also, we needed to keep the change minimal because the kiosk screens run a thin client that cannot be updated frequently.

What I Tried First

My first instinct was to keep the 30‑day cookie and add a silent “refresh token” flow that would automatically request a new access token before expiration. I added a /api/refresh endpoint that read a long‑lived refresh token from a second cookie and issued a fresh access token. The flow worked in development, but in production the TV browsers (a custom Chromium build) blocked third‑party cookies and refused to store the extra refresh cookie when SameSite=Lax was used.

I also tried increasing the maxAge value to a large number (e.g., 60 * 60 * 24 * 365 * 10) to see if the browser would reject it. Chrome threw a warning: “Cookie ‘session’ has an expiration date more than 400 days in the future; this is not allowed.” So the browser enforces a practical ceiling around one year.

Because the refresh‑token approach added complexity (token revocation, extra endpoint, extra cookie handling) and the browser limit capped us anyway, I decided to go back to the simplest solution: bump the cookie’s maxAge to exactly one year.

The Implementation

File Modified

src/app/api/login/route.ts
Enter fullscreen mode Exit fullscreen mode

The file lives in the Next.js App Router API folder and exports a POST handler that validates credentials, creates a JWT, and sets it in a cookie via NextResponse.

Before the Change

// src/app/api/login/route.ts
import { NextResponse } from "next/server";
import { signJwt } from "@/lib/auth";

export async function POST(req: Request) {
  const { email, password } = await req.json();

  // ...validate credentials...

  const token = await signJwt({ sub: user.id });

  const response = NextResponse.json({ ok: true });
  response.cookies.set({
    name: "session",
    value: token,
    httpOnly: true,
    secure: true,
    sameSite: "lax",
    path: "/",
    maxAge: 60 * 60 * 24 * 30, // 30 days
  });

  return response;
}
Enter fullscreen mode Exit fullscreen mode

The Diff

diff --git a/src/app/api/login/route.ts b/src/app/api/login/route.ts
index 5e2a1c7..a3f9b2d 100644
--- a/src/app/api/login/route.ts
+++ b/src/app/api/login/route.ts
@@ -24,7 +24,7 @@ export async function POST(req: Request) {
     secure: true,
     sameSite: "lax",
     path: "/",
-    maxAge: 60 * 60 * 24 * 30, // 30 días
+    maxAge: 60 * 60 * 24 * 365, // 1 year
   });
Enter fullscreen mode Exit fullscreen mode

That’s the entire change: replace 30 with 365.

Why This Works

  • Browser Limits: Chrome caps maxAge at roughly 400 days; 365 days stays within the limit.
  • Security Flags: We keep httpOnly, secure, and sameSite=lax unchanged, so the cookie is still inaccessible to JavaScript and only sent over HTTPS.
  • Stateless JWT: The token itself still expires after 7 days (configured in signJwt). The longer cookie simply keeps the same token around longer, which is fine for kiosk mode because the token is scoped to a read‑only UI and the TV never performs privileged actions.

Additional Adjustments

Even though the diff is one line, I added a comment to clarify intent for future maintainers:

// Extend cookie lifetime for kiosk‑mode TV screens.
// 365 days ≈ 1 year, the maximum allowed by modern browsers.
maxAge: 60 * 60 * 24 * 365,
Enter fullscreen mode Exit fullscreen mode

I also updated the TypeScript type for the cookie options to make the comment part of the definition, preventing accidental reverts during code‑formatting:

interface SessionCookieOpts {
  name: string;
  value: string;
  httpOnly: true;
  secure: true;
  sameSite: "lax";
  path: "/";
  /** Lifetime in seconds – 365 days for kiosk mode */
  maxAge: number;
}
Enter fullscreen mode Exit fullscreen mode

Testing

  1. Unit Test – Added a Jest test that asserts the maxAge property equals 60 * 60 * 24 * 365.
  2. Integration Test – Ran a Cypress flow that logs in, reloads the page after 31 days (simulated with cy.clock()), and verifies the session cookie is still present.
  3. Manual Verification – Deployed to a staging environment, opened the TV’s Chromium browser, inspected cookies via DevTools, and confirmed Expires showed a date one year ahead.

All tests passed, and the TV stayed logged in for the full simulated period.

Key Takeaway

When you need a longer‑lasting session for a specific client class, extending the cookie’s maxAge is often the cleanest solution—provided you keep the token’s internal expiration short and enforce strict cookie flags. This keeps the implementation minimal, avoids extra refresh‑token plumbing, and stays within browser limits. Just remember to document the change and add a test; a one‑line diff can be easy to miss during future refactors.

What’s Next

  1. Token Rotation: Implement a background job that rotates the JWT every 7 days while preserving the long‑lived cookie, reducing the blast radius if a token is compromised.
  2. Revocation Endpoint: Add an admin API that can invalidate a specific TV’s session by deleting its cookie and storing the token’s jti in a blacklist.
  3. Telemetry: Log cookie expiration dates in our analytics pipeline to monitor how many kiosks actually

Part of my Build in Public series — sharing the real process of building SaaS projects from Playa del Carmen, México.

Repo: zaerohell/greenview · 2026-08-20

#playadev #buildinpublic

Top comments (0)