Next.js integration
@finsel-dgi/pasby-next — OIDC login routes, handshake, and getEID for App Router.
@finsel-dgi/pasby-next wraps the full OIDC sequence for Next.js 14+ App Router: PKCE, session start, handshake callback, encrypted cookies, and claim fetch.
npm: @finsel-dgi/pasby-next · Repository: github.com/Finsel-DGI/pasby-nextjs
Underlying HTTP contract: OIDC quickstart.
Install
npm install @finsel-dgi/pasby-nextPeer dependencies: next 14+, react 18+, axios.
If you link the package from a monorepo, add to next.config:
transpilePackages: ["@finsel-dgi/pasby-next"],Environment variables
| Variable | Purpose |
|---|---|
PASBY_CLIENT_SECRET | App secret (x-access-secret) |
PASBY_CONSUMER_KEY | Organisation API key (x-api-key) |
PASBY_CLIENT_ID | App id (resource body) |
SECRET_GEN | Symmetric key for JWE around PKCE verifier + access token cookies |
PASBY_LOGIN_REDIRECT | Fallback path after handshake if state cookie is missing |
PASBY_LOGOUT_REDIRECT | Target after logout (default /) |
Register OAuth callback in Console as:
https://<your-host>/api/eid/handshake
1. API route
Create a catch-all route that handles login, handshake, and logout:
// app/api/eid/[auth]/route.ts
import { handler } from "@finsel-dgi/pasby-next/server";
import { NextRequest } from "next/server";
const pasbyHandler = handler(
{
claims: ["naming.given", "naming.family", "contact.email"],
action: "login",
payload: "Sign in to your app",
},
"/auth/error",
);
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ auth: string }> },
) {
return pasbyHandler(request, { params: await params });
}| Path | Phase |
|---|---|
GET /api/eid/login | Starts OIDC session (start session), redirects to pasby |
GET /api/eid/handshake | Exchange + sets session cookies |
GET /api/eid/logout | Clears cookies, redirects to PASBY_LOGOUT_REDIRECT |
2. Login button
"use client";
import { LoginButton } from "@finsel-dgi/pasby-next";
export function AuthBar() {
return (
<LoginButton
variant="dark"
action="login"
fallbackPath="/dashboard"
/>
);
}| Prop | Description |
|---|---|
fallbackPath | Post-handshake redirect (URL-encoded as state) |
action | login or identify |
variant | original | light | dark | darktext |
tenantId | Required when using createPasbyHandler (multi-tenant) |
The button navigates to:
GET /api/eid/login?redirect=false&state=<encodeURIComponent(fallbackPath)>
3. Read the signed-in user
// app/dashboard/page.tsx
import { getEID } from "@finsel-dgi/pasby-next/server";
import { cookies } from "next/headers";
export default async function Dashboard() {
const user = await getEID(await cookies());
if (!user) {
return <p>Not signed in</p>;
}
return (
<div>
<p>NIN: {user.national}</p>
<p>Email: {user.claims?.contact?.email}</p>
</div>
);
}getEID calls resource using cookies set at handshake. Returns User from @finsel-dgi/pasby-react.
4. Logout
"use client";
import { useRouter } from "next/navigation";
export function Logout() {
const router = useRouter();
return (
<button type="button" onClick={() => router.push("/api/eid/logout")}>
Sign out
</button>
);
}Multi-tenant / injected config
When credentials vary per tenant (Infisical, Vault, DB row), use createPasbyHandler instead of handler:
import {
createPasbyHandler,
PASBY_TENANT_COOKIE,
pasbyConfigFromEnv,
} from "@finsel-dgi/pasby-next/server";
import type { ResolvePasbyContext } from "@finsel-dgi/pasby-next/server";
const resolveContext: ResolvePasbyContext = async (req, phase) => {
if (phase === "login") {
const tenantId = req.nextUrl.searchParams.get("tenant")?.trim();
if (!tenantId) return null;
const config = await loadPasbyRuntimeConfigForTenant(tenantId);
if (!config) return null;
return { config, tenantId };
}
if (phase === "handshake") {
const tenantId = req.cookies.get(PASBY_TENANT_COOKIE)?.value?.trim();
if (!tenantId) return null;
const config = await loadPasbyRuntimeConfigForTenant(tenantId);
if (!config) return null;
return { config, tenantId };
}
return { config: pasbyConfigFromEnv() };
};
export const GET = createPasbyHandler(
{
claims: ["naming.given", "contact.email"],
action: "login",
payload: "Sign-in request",
},
"/error",
resolveContext,
);Pass tenantId on LoginButton so login can resolve the correct PasbyRuntimeConfig.
Server exports
Import from @finsel-dgi/pasby-next/server:
| Export | Purpose |
|---|---|
handler | Single-tenant App Router handler (env-based) |
createPasbyHandler | Multi-tenant / injected config |
getEID | Load User from cookies |
pasbyConfigFromEnv | Build PasbyRuntimeConfig from env |
PASBY_TENANT_COOKIE | Cookie name for tenant scoping |
Client exports (@finsel-dgi/pasby-next): LoginButton, PasbyButton, Logo, WordMark.
Related
- React OIDC —
@finsel-dgi/pasby-react/serverfor custom Node backends - Brand buttons
- Claims reference