Course outline

Next.js / Intermediate: authority, freshness and route experience / Lesson 8

Sessions, server-only data access and object policy

Resolve current authority on the server and protect every path to private data or mutations.

About 4 min readContent reviewed
On this page

Lesson goals

  • Separate cookie transport, verified session and current principal.
  • Keep data eligibility in a shared server-only layer rather than a navigation gate.

An incoming cookie is a credential claim to verify

A cookie value is caller input until the authentication protocol verifies it and establishes current authority. A server-only module marker helps prevent accidental client imports, but does not authenticate requests or decide object access. Use a reviewed authentication/session solution with explicit expiry, revocation and rotation behavior. Avoid storing secrets in props, URLs or ordinary logs. A cached role can become stale after privileges change, so define how sensitive operations obtain current policy evidence. Server rendering and a hidden navigation item should never be treated as sufficient protection for a direct action or Route Handler.

Await cookies and verify the credential behind a server boundary

The fragment uses the asynchronous cookies API, reads one cookie name and delegates verification. It is a server-only illustration, not a session implementation. verifyCredential must apply the actual protocol, current validity and safe failure behavior; a missing cookie is not automatically a privileged guest. A data-access function then needs tenant and object policy in addition to this principal. Keep authentication mechanisms replaceable through a narrow capability rather than importing a configured database everywhere. No cookie, credential, database or request context is accessed during this course.

typescript Illustrative server-only session boundary
import "server-only";
import {cookies} from "next/headers";
export async function currentPrincipal() {
  const jar=await cookies();
  const credential=jar.get("session")?.value;
  return verifyCredential(credential);
}

Credential verification is an assumed reviewed dependency. The async cookie read and server-only import were not executed.

Protect lists, lookups and mutation results consistently

The same course can appear through a page, a Route Handler, a Server Action result or a related-object list. Centralize eligibility so a protected primary lookup is not bypassed through another representation. Tenant IDs from params and hidden form fields identify requested scope, not membership. Decide whether denied and absent objects share a public not-found response, while retaining internal diagnosis safely. Return a narrow DTO only after access is permitted. Serialization exclusions can hide fields but cannot make it safe to return a forbidden object’s existence or relationships.

Test tenant, state and expiry as separate prerequisites

The local predicate allows a synthetic published course read only for the same tenant and before the principal’s expiry. At the exact expiry instant it denies. These are deliberately small domain rules, not cryptographic verification or a complete authorization engine. Real applications can add enrollment, roles and revocation checks. Test negative cases for each prerequisite with the same object so one successful condition cannot hide another failed one. The model’s time values are supplied explicitly and no real session store, request cookie or server clock is consulted.

javascript Complete offline current-authority rule
function mayRead(principal,course,now) {
  return principal.tenantId === course.tenantId && course.state === "PUBLISHED" && now < principal.expiresAt;
}
const course={id:"c1",tenantId:"lab-a",state:"PUBLISHED"};
console.assert(mayRead({tenantId:"lab-a",expiresAt:1000},course,999));
console.assert(!mayRead({tenantId:"lab-b",expiresAt:1000},course,999));
console.assert(!mayRead({tenantId:"lab-a",expiresAt:1000},course,1000));

Only the eligible nonexpired principal passes the tested cases. Credential authenticity and revocation infrastructure are not tested.

Align caches and client state with identity changes

Do not place a current-user variable in shared mutable module state. Memoization for one render and persistent cache storage have different lifetimes; ensure personalized data is not shared under an insufficient key. Logout must retire server authority and client-visible private state according to the session design, not only hide a button. Late responses from an old principal must not repopulate the next user’s view. Verify direct actions, handlers, layouts and account switching in an authorized isolated integration. This course executes only the eligibility predicate and does not validate authentication, cookies, memoization, logout or live authorization.

Common errors and fixes

A server-only marker is called authentication

Why it happens
Build-time import protection was confused with verified caller authority.
How to fix it
Verify credentials and apply shared object policy at every server entry.
Prevent it next time
Test direct actions and handlers with invalid and expired credentials.

A tenant param grants membership

Why it happens
Requested scope was accepted as permission.
How to fix it
Compare requested scope with the verified principal and domain rules.
Prevent it next time
Use the same object ID across allowed and denied tenants.

Logout still shows the previous account’s card

Why it happens
Server authority and client/private cache lifetimes were not coordinated.
How to fix it
Retire session authority and reject incompatible pending results.
Prevent it next time
Test account switches while requests remain pending.

Check your understanding

1. Does server-only stop a caller from invoking a Server Action?

Show worked solution

No. It is an import-boundary safeguard. An action is still a server entry point and must verify current credentials, validate input and enforce the requested object transition. Keep that policy in the shared use case so other adapters cannot bypass it, and test direct invocation rather than only the protected page.

2. Why test expiry at equality and a different tenant separately?

Show worked solution

They are independent reasons to deny. A correct expiry comparison cannot compensate for missing tenant isolation, and a matching tenant cannot keep an expired principal valid. The explicit cases make both preconditions observable. A real authentication integration must separately establish the trusted values supplied to that decision.

Key takeaways

  • Server execution and import markers do not authorize data.
  • Current authority and private state require coordinated lifetimes.

Official references