@kenac/nextjs-oauth2-proxy-auth
v0.1.0
Published
A comprehensive authentication library for Next.js applications using OAuth2 Proxy with full RBAC support, Keycloak integration, and federated logout
Maintainers
Readme
@kenac/nextjs-oauth2-proxy-auth
A comprehensive authentication library for Next.js applications using OAuth2 Proxy with full RBAC (Role-Based Access Control) support.
Features
- 🔐 OAuth2 Proxy Integration - Extracts user data from OAuth2 Proxy headers
- 🎭 Multiple Provider Support - Keycloak, Auth0, Okta, and generic OAuth2 providers
- 🛡️ Full RBAC - Permission and role-based access control
- 🚪 Federated Logout - Complete logout flow with OAuth2 provider session clearing
- ⚡ Server Components - Full support for Next.js App Router and Server Components
- 🎣 React Hooks - Comprehensive hooks for authentication and authorization
- 🧱 Guard Components - Client and server-side route protection
- 🔧 Highly Configurable - Customize headers, endpoints, and behavior
- 🧪 Development Mocking - Built-in mock user for local development
Installation
npm install @kenac/nextjs-oauth2-proxy-auth
# or
pnpm add @kenac/nextjs-oauth2-proxy-auth
# or
yarn add @kenac/nextjs-oauth2-proxy-authQuick Start (CLI - Recommended)
The fastest way to get started is using our CLI which sets up everything automatically:
npx @kenac/nextjs-oauth2-proxy-auth initThis will:
- ✅ Create
lib/auth.config.tswith your provider configuration - ✅ Create
app/api/auth/[...auth]/route.ts(handles all auth API routes) - ✅ Create
app/logout-helper/page.tsxfor federated logout - ✅ Create
app/providers.tsxwith the UserProvider wrapper - ✅ Create
.env.examplewith required environment variables
After running the CLI, just:
- Copy environment variables:
cp .env.example .env.localand fill in your values - Update
app/layout.tsxto wrap children with the Providers component - Configure OAuth2 Proxy to skip auth for logout helper:
--skip-auth-routes=^/logout-helper
That's it! Start using authentication in your components:
import { useUser, AuthGuard, PermissionGuard } from "@kenac/nextjs-oauth2-proxy-auth";CLI Options
# Interactive setup (prompts for provider)
npx @kenac/nextjs-oauth2-proxy-auth init
# Specify provider directly
npx @kenac/nextjs-oauth2-proxy-auth init --provider keycloak
npx @kenac/nextjs-oauth2-proxy-auth init --provider auth0
npx @kenac/nextjs-oauth2-proxy-auth init --provider okta
npx @kenac/nextjs-oauth2-proxy-auth init --provider genericManual Setup
If you prefer to set up manually, follow these steps:
1. Create Configuration
// lib/auth-config.ts
import { createAuthConfig } from "@kenac/nextjs-oauth2-proxy-auth";
export const authConfig = createAuthConfig({
provider: {
type: "keycloak",
domain: process.env.KEYCLOAK_DOMAIN!,
realm: process.env.KEYCLOAK_REALM!,
},
development: {
enableMocking: process.env.NODE_ENV === "development",
mockUser: {
email: "[email protected]",
name: "Development User",
roles: ["admin", "user"],
},
},
});2. Set Up API Routes
Option A: Single catch-all route (recommended)
// app/api/auth/[...auth]/route.ts
import { createAuthRouteHandler } from "@kenac/nextjs-oauth2-proxy-auth/server";
import { authConfig } from "@/lib/auth-config";
const handler = createAuthRouteHandler(authConfig);
export const GET = handler;
export const dynamic = "force-dynamic";This single file handles /api/auth/user, /api/auth/id-token, and /api/auth/health.
Option B: Individual routes
// app/api/auth/user/route.ts
import { createUserHandler } from "@kenac/nextjs-oauth2-proxy-auth/server";
import { authConfig } from "@/lib/auth-config";
export const GET = createUserHandler({
headers: authConfig.headers,
development: authConfig.development,
});// app/api/auth/id-token/route.ts
import { createIdTokenHandler } from "@kenac/nextjs-oauth2-proxy-auth/server";
export const GET = createIdTokenHandler();3. Create Logout Helper Page
// app/logout-helper/page.tsx
import { LogoutHelperPage } from "@kenac/nextjs-oauth2-proxy-auth";
export default function Page() {
return <LogoutHelperPage />;
}⚠️ Important: Configure OAuth2 Proxy to skip auth for this page:
--skip-auth-routes=^/logout-helper
4. Wrap Your App with UserProvider
// app/providers.tsx
"use client";
import { UserProvider } from "@kenac/nextjs-oauth2-proxy-auth";
import { authConfig } from "@/lib/auth-config";
export function Providers({ children }: { children: React.ReactNode }) {
return <UserProvider config={authConfig}>{children}</UserProvider>;
}// app/layout.tsx
import { Providers } from "./providers";
export default function RootLayout({ children }) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}5. Use in Components
"use client";
import { useUser, useAuth, PermissionGuard } from "@kenac/nextjs-oauth2-proxy-auth";
export function Dashboard() {
const { user, loading, isAuthenticated } = useUser();
const { logout } = useAuth();
if (loading) return <div>Loading...</div>;
if (!isAuthenticated) return <div>Please log in</div>;
return (
<div>
<h1>Welcome, {user.name}!</h1>
<PermissionGuard permission="dashboard:admin" fallback={<p>Admin only</p>}>
<AdminPanel />
</PermissionGuard>
<button onClick={() => logout()}>Logout</button>
</div>
);
}Configuration
Full Configuration Example
import { createAuthConfig } from "@kenac/nextjs-oauth2-proxy-auth";
const authConfig = createAuthConfig({
// OAuth2 Provider Configuration
provider: {
type: "keycloak", // "keycloak" | "auth0" | "okta" | "generic"
domain: "https://auth.example.com",
realm: "my-realm", // For Keycloak
clientId: "my-client", // Optional
},
// Header Configuration (customize if OAuth2 Proxy uses different headers)
headers: {
userIdHeaders: ["x-forwarded-user", "x-user", "x-auth-request-user"],
emailHeaders: ["x-forwarded-email", "x-email", "x-auth-request-email"],
usernameHeaders: ["x-forwarded-preferred-username"],
groupsHeaders: ["x-forwarded-groups", "x-groups"],
},
// RBAC Configuration
rbac: {
enabled: true,
fetchPermissionsOnLogin: true,
permissionsCacheDuration: 5 * 60 * 1000, // 5 minutes
// Custom permission fetcher
fetchPermissions: async (userId) => {
const res = await fetch(`/api/permissions?userId=${userId}`);
const data = await res.json();
return data.permissions;
},
},
// Logout Configuration
logout: {
oauth2ProxyLogout: "/oauth2/sign_out",
logoutHelperPath: "/logout-helper",
postLogoutRedirect: "/",
includeIdTokenHint: true,
localStorageKeys: ["user", "auth_token"],
},
// Development Configuration
development: {
enableMocking: process.env.NODE_ENV === "development",
enableLogging: true,
mockUser: {
id: "dev-user",
email: "[email protected]",
name: "Dev User",
roles: ["admin"],
permissions: ["dashboard:view", "users:manage"],
},
},
// Endpoints Configuration
endpoints: {
userInfo: "/api/auth/user",
idToken: "/api/auth/id-token",
login: "/oauth2/start",
logout: "/oauth2/sign_out",
},
});Hooks
useUser()
Access the current user and authentication state.
const { user, loading, error, isAuthenticated, refetch } = useUser();useAuth()
Access authentication actions.
const { login, logout, refreshSession, validateSession, clearLocalSession } = useAuth();
// Login with redirect
login("/dashboard");
// Logout
await logout();
// Logout without provider logout (just clear local session)
await logout({ skipProviderLogout: true });usePermissions()
Check user permissions and roles.
const {
hasPermission,
hasAnyPermission,
hasAllPermissions,
hasRole,
hasAnyRole,
isAdmin,
isManager,
permissions,
roles,
} = usePermissions();
// Check single permission
if (hasPermission("users:delete")) {
// ...
}
// Check multiple permissions
if (hasAnyPermission(["users:edit", "users:delete"])) {
// ...
}useUserDisplay()
Get formatted user display information.
const { displayName, displayEmail, initials, avatar, primaryRole } = useUserDisplay();Components
<AuthGuard>
Protect content based on authentication.
<AuthGuard fallback={<LoginPrompt />}>
<ProtectedContent />
</AuthGuard>
<AuthGuard when="unauthenticated">
<LoginButton />
</AuthGuard>
<AuthGuard roles={["admin", "manager"]} fallback={<AccessDenied />}>
<AdminContent />
</AuthGuard><PermissionGuard>
Protect content based on permissions.
<PermissionGuard permission="users:delete" fallback={<NoAccess />}>
<DeleteButton />
</PermissionGuard>
<PermissionGuard permissions={["users:edit", "users:view"]} requireAll>
<UserEditor />
</PermissionGuard><RoleGuard>
Protect content based on roles.
<RoleGuard role="admin">
<AdminPanel />
</RoleGuard>
<RoleGuard roles={["admin", "manager"]}>
<ManagementTools />
</RoleGuard>withAuth() HOC
Protect entire components with a higher-order component.
const ProtectedPage = withAuth(MyPage, {
requiredRoles: ["admin"],
redirectUrl: "/login",
fallback: AccessDeniedComponent,
});Server-Side Usage
Server Actions
// lib/actions/get-user.ts
"use server";
import { createServerUserAction } from "@kenac/nextjs-oauth2-proxy-auth/server";
import { authConfig } from "@/lib/auth-config";
export const getServerUser = createServerUserAction({
headers: authConfig.headers,
development: authConfig.development,
enrichUserWithProfile: async (user) => {
// Fetch additional user data from your database
const profile = await db.profiles.findByEmail(user.email);
return { ...user, profileId: profile?.id };
},
fetchRBAC: async (userId) => {
// Fetch roles and permissions from your backend
const rbac = await db.rbac.getForUser(userId);
return {
roles: rbac.roles,
permissions: rbac.permissions,
};
},
});Server Components (Page Guards)
// lib/guards.ts
import { createServerGuards } from "@kenac/nextjs-oauth2-proxy-auth/server";
import { getServerUser } from "@/lib/actions/get-user";
export const { PermissionGuard, RoleGuard, AdminGuard, AuthGuard } =
createServerGuards(getServerUser);// app/admin/page.tsx
import { AdminGuard } from "@/lib/guards";
export default async function AdminPage() {
return (
<AdminGuard redirectTo="/unauthorized">
<h1>Admin Dashboard</h1>
</AdminGuard>
);
}OAuth2 Proxy Configuration
Ensure your OAuth2 Proxy is configured to pass the required headers:
# oauth2-proxy.cfg or environment variables
pass_user_headers = true
pass_authorization_header = true # Required for logout with id_token_hint
set_xauthrequest = true
# Skip auth for logout helper page
skip_auth_routes = ["/logout-helper"]Logout Flow
The library implements a complete federated logout flow:
- Frontend calls
logout()→ fetches ID token, clears local storage - Redirect to OAuth2 Provider (e.g., Keycloak) with
id_token_hint - Provider clears session → redirects to
/logout-helper - Logout helper redirects to
/oauth2/sign_out - OAuth2 Proxy clears its session → redirects to home
TypeScript
The library is written in TypeScript and includes full type definitions.
import type {
User,
UserResponse,
OAuth2ProxyAuthConfig,
PermissionCheck,
} from "@kenac/nextjs-oauth2-proxy-auth";License
MIT
Contributing
Contributions are welcome! Please read our contributing guidelines before submitting a pull request.
