Skip to content

feat(nextjs): requireAuth and requireRole guards, trustedProxies client IP, adapter conformance - #266

Merged
Bccorb merged 2 commits into
mainfrom
feat/nextjs-guards
Nov 1, 2026
Merged

Bccorb merged 2 commits into
mainfrom
feat/nextjs-guards

Conversation

@Bccorb

@Bccorb Bccorb commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

Closes #262.

What

@seamless-auth/nextjs route guards

  • requireAuth(handler, opts) and requireRole(roles, handler, opts) wrap App Router route handlers. The handler gets (request, context, user), as createResourceServer().protect does. opts (SeamlessGuardOptions) is a subset of the handler options, so one object serves both.
  • They accept the access cookie or Authorization: Bearer <access token> (the cookie wins when both are sent), verify the token against the auth API's key set so roles come from a signed token, apply the origin check to cookie requests, refuse recovery sessions unless allowRecoverySession is set, and never refresh.
  • Refusals: 401 {"error":"unauthenticated"}, 403 enrollment_required, 403 cross_site_request_blocked, 403 insufficient_role. No body names any roles. requireRole matches with hasScopedRole (any listed role) and throws at setup on no role or a blank one.
  • The user carries organizationId from the verified token's org_id.

Client IP

  • New trustedProxies: string[] (addresses or CIDR ranges) walks X-Forwarded-For from the right past trusted hops, used when resolveClientIp is not given. There is deliberately no hop-count option: a hop count cannot tell a proxy from a client, which is why the Go, Python and Rust adapters take a proxy list too. Invalid entries throw at setup.
  • With neither option set nothing is forwarded, as before, and the handler logs one warning at startup.
  • getSeamlessSession takes the request headers with trustedProxies or resolveClientIp, forwards the client IP, and reads the user agent from headers when userAgent is not given.

Core

Conformance

  • conformance/nextjs-refapp, a private workspace package, implements the reference app contract in seamless-cli verify/CONFORMANCE.md.
  • A nextjs job in conformance.yml starts it and runs seamless verify --adapter-url against it (seamless-cli at the same pinned SHA as the existing jobs), the way the Go, Python and Rust adapters run the specs. No seamless-cli change is needed.

The same role guard semantics, refusal bodies and organization field are landing in the Go, Python and Rust adapters (seamless-auth-go#28, seamless-auth-python#31, seamless-auth-rust#32).

Changeset: minor for @seamless-auth/nextjs and @seamless-auth/core. The linked versioning makes express minor and fastify patch.

Checks

  • pnpm build: all packages done.
  • pnpm test: core 598 passed, express 264 passed, nextjs 311 passed (includes the consumer type checks), fastify 271 passed. Parity tests add 28 cases running the Next.js guards against Express requireAuth and requireRole.
  • pnpm check:types-current: @seamless-auth/types ^0.34.0 is current.
  • Conformance run locally against seamless-auth-api main: 25 passed, including client forwarding, bearer transport, refresh rotation and the protected endpoint.

…nt IP

Add requireAuth(handler, options) and requireRole(roles, handler, options)
for App Router route handlers. They take the access cookie or a bearer
access token, verify the token against the auth API's JWKS, refuse
recovery sessions unless allowed, apply the origin check to cookie
requests and never refresh. Refusals use the codes the Go, Python and
Rust adapters answer with (unauthenticated, enrollment_required,
cross_site_request_blocked, insufficient_role) and never echo roles.
The user carries organizationId from the verified token's org_id, which
core's authenticateBearer and authenticateRequest now set.

Add a trustedProxies option that walks X-Forwarded-For from the right
past the listed addresses or ranges, used when resolveClientIp is not
given. There is no hop count option: a count cannot tell a proxy from a
client. With neither set nothing is forwarded, and the handler logs one
startup warning. getSeamlessSession resolves and forwards the client IP
from the request headers too.

Refs #262
…ence app

conformance/nextjs-refapp is a private workspace app built on
@seamless-auth/nextjs with the routes verify/CONFORMANCE.md in
seamless-cli asks for: the adapter at /auth, /api/me behind requireAuth,
and the /__captured delivery hook. A new job in conformance.yml builds
it, starts it on the host and runs seamless verify --adapter-url against
it, the way the Go, Python and Rust adapters run the same specs. It needs
no seamless-cli change.

Closes #262
@Bccorb
Bccorb merged commit 82687e6 into main Nov 1, 2026
5 checks passed
@Bccorb
Bccorb deleted the feat/nextjs-guards branch November 8, 2026 00:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(nextjs): requireAuth and requireRole guards, client IP default, adapter conformance

1 participant