@drvalue-oss/iam-next
v0.8.0
Published
Next.js Route Handler factories for the drvalue IAM BFF token-exchange pattern (callback / refresh / logout)
Maintainers
Readme
@drvalue-oss/iam-next
Next.js Route Handler factories for the drvalue IAM BFF token-exchange pattern:
- Refresh token lives in an httpOnly cookie on YOUR Next.js app (never touches the browser JS).
- Access token is delivered to the browser via URL hash, then stored in localStorage by the SPA.
- The Next.js app proxies refresh / revoke calls to IAM server-to-server, so the browser never CORS-talks to IAM directly.
Install
pnpm add @drvalue-oss/iam-nextPeer dep: next ^14 || ^15 || ^16. Requires the Node.js runtime (Node >=20.19) — the handlers use fetch/cookies on the server; do not set export const runtime = 'edge' on these routes.
Setup
Create one shared config and three Route Handlers:
// lib/iam.ts
import type { IamNextConfig } from '@drvalue-oss/iam-next';
export const iamConfig: IamNextConfig = {
iamServerUrl: process.env.IAM_SERVER_URL!, // https://iam.drvalue.co.kr
appUrl: process.env.NEXT_PUBLIC_APP_URL!, // https://app.drvalue.co.kr
cookieDomain: process.env.COOKIE_DOMAIN, // .drvalue.co.kr (optional)
internalApiKey: process.env.INTERNAL_API_KEY, // for token revoke (optional)
};// app/auth/callback/route.ts
import { createCallbackHandler } from '@drvalue-oss/iam-next';
import { iamConfig } from '@/lib/iam';
export const GET = createCallbackHandler({
...iamConfig,
resolveLoginPath: (origin) => (origin === 'admin' ? '/admin/login' : '/login'),
});// app/api/auth/refresh/route.ts
import { createRefreshHandler } from '@drvalue-oss/iam-next';
import { iamConfig } from '@/lib/iam';
export const POST = createRefreshHandler(iamConfig);// app/api/auth/logout/route.ts
import { createLogoutHandler } from '@drvalue-oss/iam-next';
import { iamConfig } from '@/lib/iam';
export const POST = createLogoutHandler(iamConfig);The /auth/complete bridge page
The callback handler redirects to /auth/complete#access_token=...&expires_in=... so the access token never lands in a server log. Implement the bridge yourself — it needs to:
- Parse the hash and
decodeURIComponenttheaccess_tokenvalue (the callback URL-encodes it), then callsetAccessToken(token, expiresIn)from@drvalue-oss/iam-react. - (optional) Fetch
/meto populate your auth store. - Redirect to the user's destination (admin dashboard / user dashboard / etc).
See examples/nextjs-app/src/app/auth/complete/page.tsx for a reference implementation.
Why the URL hash
The hash is not sent to the server on navigation. The Next.js server never sees the access token. Combined with window.history.replaceState on the complete page, the token also disappears from the browser URL bar after handoff.
Security notes
Securecookies need HTTPS context. The refresh cookie'sSecureflag defaults toprocess.env.NODE_ENV === 'production'. Some runtimes/hosts don't setNODE_ENV=productionat the Route Handler layer — if yours doesn't, setcookieSecure: trueexplicitly in the config, or the refresh token can be sent over plain HTTP. UsecookieSecure: falseonly for local HTTP dev.- CSRF. The
refreshandlogoutPOST handlers act on the ambient httpOnly cookie. They rely onSameSite=Lax(set by this package) to block cross-site form POSTs. If you widensameSite(e.g. toNonefor cross-site use) or share the cookie across origins, add anOrigin/Sec-Fetch-Sitecheck or a CSRF token. cookieDomainshares the refresh token across every subdomain of that domain — only set it when you intend that.internalApiKeyis server-only. Read it from a non-NEXT_PUBLIC_env var (as the examples do) so it never reaches the browser. The logout handler decodes the refresh token'ssubunverified to address the revoke call — IAM's/auth/token/revokemust authenticate the request server-side and must not revoke an arbitraryuser_idwithout proof.- Token logging. These handlers never log token values; on exchange errors they log only the status and a whitelisted
errorfield.
See the repo-level SECURITY.md for the full threat model.
