@auth-ezz/nextjs 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,418 @@
1
+ # @ezz-auth/next
2
+
3
+ A secure, high-performance authentication SDK designed specifically for modern Next.js applications using the App Router. Built on HTTP-only cookies and server-side session validation for maximum security.
4
+
5
+ [![npm version](https://badge.fury.io/js/%40ezz-auth%2Fnext.svg)](https://badge.fury.io/js/%40ezz-auth%2Fnext)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ## ✨ Features
9
+
10
+ - 🔒 **Zero Token Exposure** - Authentication tokens never reach the client-side
11
+ - 🚀 **Next.js Optimized** - Deep integration with App Router, Server Components, and Middleware
12
+ - 🛡️ **Session-Based** - Robust session management without complex token handling
13
+ - ⚡ **Edge Ready** - Lightweight middleware for edge deployment
14
+ - 🎯 **TypeScript First** - Full TypeScript support with comprehensive type definitions
15
+ - 🧩 **Framework Agnostic** - Works with any React-based Next.js application
16
+
17
+ ## 📦 Installation
18
+
19
+ ```bash
20
+ npm install @ezz-auth/next
21
+ ```
22
+
23
+ ## 🚀 Quick Start
24
+
25
+ ### 1. Environment Setup
26
+
27
+ Add to your `.env.local`:
28
+
29
+ ```bash
30
+ # Core Auth System URL
31
+ AUTH_BASE_URL=https://auth.your-domain.com
32
+
33
+ # Your Application Credentials
34
+ AUTH_APP_KEY=your_public_key
35
+ AUTH_APP_SECRET=your_secret_key
36
+ ```
37
+
38
+ ### 2. Initialize Routes
39
+
40
+ ```bash
41
+ npx ezz-auth init
42
+ ```
43
+
44
+ This creates the necessary API routes for authentication callbacks.
45
+
46
+ ### 3. Protect Your App
47
+
48
+ ```tsx
49
+ // app/layout.tsx
50
+ import { AuthProvider } from "@ezz-auth/next/client";
51
+ import { auth } from "@ezz-auth/next/server";
52
+
53
+ export default async function RootLayout({ children }) {
54
+ const { user } = await auth();
55
+
56
+ return (
57
+ <html>
58
+ <body>
59
+ <AuthProvider initialUser={user}>
60
+ {children}
61
+ </AuthProvider>
62
+ </body>
63
+ </html>
64
+ );
65
+ }
66
+ ```
67
+
68
+ ```tsx
69
+ // app/dashboard/page.tsx
70
+ import { auth } from "@ezz-auth/next/server";
71
+ import { redirect } from "next/navigation";
72
+
73
+ export default async function Dashboard() {
74
+ const { user } = await auth();
75
+
76
+ if (!user) {
77
+ redirect("/");
78
+ }
79
+
80
+ return <h1>Welcome, {user.name}!</h1>;
81
+ }
82
+ ```
83
+
84
+ ## 📚 API Reference
85
+
86
+ ### Client-Side API
87
+
88
+ #### `AuthProvider`
89
+
90
+ Context provider that manages authentication state across your React application.
91
+
92
+ ```tsx
93
+ import { AuthProvider } from "@ezz-auth/next/client";
94
+
95
+ <AuthProvider initialUser={user}>
96
+ <App />
97
+ </AuthProvider>
98
+ ```
99
+
100
+ **Props:**
101
+ - `initialUser` (optional): Initial user object from server-side auth
102
+ - `children`: React components to render
103
+
104
+ #### `useUser()`
105
+
106
+ React hook to access the current authenticated user in client components.
107
+
108
+ ```tsx
109
+ import { useUser } from "@ezz-auth/next/client";
110
+
111
+ function Profile() {
112
+ const { user, isSignedIn, isLoaded } = useUser();
113
+
114
+ if (!isLoaded) return <div>Loading...</div>;
115
+ if (!isSignedIn) return <div>Please sign in</div>;
116
+
117
+ return <div>Hello, {user.name}!</div>;
118
+ }
119
+ ```
120
+
121
+ **Returns:**
122
+ - `user`: Current user object or `null`
123
+ - `isSignedIn`: Boolean indicating authentication status
124
+ - `isLoaded`: Boolean indicating if auth state has been determined
125
+
126
+ #### `SignInButton`
127
+
128
+ Pre-built component that redirects users to the authentication flow.
129
+
130
+ ```tsx
131
+ import { SignInButton } from "@ezz-auth/next/client";
132
+
133
+ <SignInButton />
134
+ ```
135
+
136
+ **Props:** None (customizable via CSS classes)
137
+
138
+ #### `UserButton`
139
+
140
+ Component that displays user information and provides logout functionality.
141
+
142
+ ```tsx
143
+ import { UserButton } from "@ezz-auth/next/client";
144
+
145
+ <UserButton />
146
+ ```
147
+
148
+ **Props:** None (customizable via CSS classes)
149
+
150
+ #### `signOut()`
151
+
152
+ Function to sign out the current user and clear their session.
153
+
154
+ ```tsx
155
+ import { signOut } from "@ezz-auth/next/client";
156
+
157
+ function handleLogout() {
158
+ signOut();
159
+ // User will be redirected and session cleared
160
+ }
161
+ ```
162
+
163
+ ### Server-Side API
164
+
165
+ #### `auth()`
166
+
167
+ Primary server-side function to authenticate requests and get user data.
168
+
169
+ ```tsx
170
+ import { auth } from "@ezz-auth/next/server";
171
+
172
+ export default async function ProtectedPage() {
173
+ const { user, session } = await auth();
174
+
175
+ if (!user) {
176
+ redirect("/login");
177
+ }
178
+
179
+ return <div>User: {user.email}</div>;
180
+ }
181
+ ```
182
+
183
+ **Returns:**
184
+ - `user`: User object or `null`
185
+ - `session`: Session object or `null`
186
+
187
+ #### `currentUser()`
188
+
189
+ Simplified function that returns only the current user (alias for `auth().user`).
190
+
191
+ ```tsx
192
+ import { currentUser } from "@ezz-auth/next/server";
193
+
194
+ const user = await currentUser();
195
+ ```
196
+
197
+ **Returns:** User object or `null`
198
+
199
+ #### `requireAuth()`
200
+
201
+ Throws an error if user is not authenticated. Useful for protecting API routes.
202
+
203
+ ```tsx
204
+ import { requireAuth } from "@ezz-auth/next/server";
205
+
206
+ export async function GET() {
207
+ const { user } = await requireAuth();
208
+
209
+ // User is guaranteed to be authenticated here
210
+ return Response.json({ data: "protected content" });
211
+ }
212
+ ```
213
+
214
+ **Options:**
215
+ - `state` (optional): Custom state to pass through auth flow
216
+
217
+ **Returns:** Object with `user` and `session` (throws if unauthenticated)
218
+
219
+ #### `handleAuthCallback()`
220
+
221
+ Route handler for processing OAuth callbacks after authentication.
222
+
223
+ ```tsx
224
+ // app/api/auth/callback/route.ts
225
+ import { handleAuthCallback } from "@ezz-auth/next/server";
226
+
227
+ export const GET = handleAuthCallback({
228
+ redirectTo: "/dashboard"
229
+ });
230
+ ```
231
+
232
+ **Options:**
233
+ - `redirectTo` (optional): Where to redirect after successful authentication (default: "/")
234
+
235
+ #### `handleLogout()`
236
+
237
+ Route handler for processing logout requests.
238
+
239
+ ```tsx
240
+ // app/api/auth/logout/route.ts
241
+ import { handleLogout } from "@ezz-auth/next/server";
242
+
243
+ export const POST = handleLogout();
244
+ ```
245
+
246
+ ### Middleware API
247
+
248
+ #### `authMiddleware()`
249
+
250
+ Next.js middleware for protecting routes at the edge.
251
+
252
+ ```tsx
253
+ // middleware.ts
254
+ import { authMiddleware } from "@ezz-auth/next/middleware";
255
+
256
+ export default authMiddleware();
257
+
258
+ export const config = {
259
+ matcher: ["/dashboard/:path*"]
260
+ };
261
+ ```
262
+
263
+ **Returns:** Next.js middleware function
264
+
265
+ ### CLI API
266
+
267
+ #### `ezz-auth init`
268
+
269
+ Scaffolds the necessary authentication routes for your Next.js application.
270
+
271
+ ```bash
272
+ npx ezz-auth init
273
+ ```
274
+
275
+ Creates:
276
+ - `app/api/auth/callback/route.ts` - OAuth callback handler
277
+ - `app/api/auth/logout/route.ts` - Logout handler
278
+
279
+ ## 🔧 Configuration
280
+
281
+ ### Environment Variables
282
+
283
+ | Variable | Description | Required |
284
+ |----------|-------------|----------|
285
+ | `AUTH_BASE_URL` | URL of your Ezz Auth core system | ✅ |
286
+ | `AUTH_APP_KEY` | Public application key from developer console | ✅ |
287
+ | `AUTH_APP_SECRET` | Secret application key for server-side operations | ✅ |
288
+
289
+ ### Advanced Configuration
290
+
291
+ The SDK automatically detects your Next.js app directory structure:
292
+ - `src/app/` (if `src` directory exists)
293
+ - `app/` (standard App Router location)
294
+
295
+ ## 📝 Examples
296
+
297
+ ### Complete App Setup
298
+
299
+ ```tsx
300
+ // app/layout.tsx
301
+ import { AuthProvider } from "@ezz-auth/next/client";
302
+ import { auth } from "@ezz-auth/next/server";
303
+
304
+ export default async function Layout({ children }) {
305
+ const { user } = await auth();
306
+
307
+ return (
308
+ <html>
309
+ <body>
310
+ <AuthProvider initialUser={user}>
311
+ <Header />
312
+ {children}
313
+ </AuthProvider>
314
+ </body>
315
+ </html>
316
+ );
317
+ }
318
+
319
+ // app/components/Header.tsx
320
+ "use client";
321
+ import { SignInButton, UserButton, useUser } from "@ezz-auth/next/client";
322
+
323
+ export function Header() {
324
+ const { isSignedIn } = useUser();
325
+
326
+ return (
327
+ <header>
328
+ <nav>
329
+ <Link href="/">Home</Link>
330
+ {isSignedIn ? <UserButton /> : <SignInButton />}
331
+ </nav>
332
+ </header>
333
+ );
334
+ }
335
+ ```
336
+
337
+ ### API Route Protection
338
+
339
+ ```tsx
340
+ // app/api/user/profile/route.ts
341
+ import { auth } from "@ezz-auth/next/server";
342
+
343
+ export async function GET() {
344
+ const { user } = await auth();
345
+
346
+ if (!user) {
347
+ return Response.json({ error: "Unauthorized" }, { status: 401 });
348
+ }
349
+
350
+ return Response.json({ profile: user });
351
+ }
352
+ ```
353
+
354
+ ### Middleware Protection
355
+
356
+ ```tsx
357
+ // middleware.ts
358
+ import { authMiddleware } from "@ezz-auth/next/middleware";
359
+
360
+ export default authMiddleware();
361
+
362
+ export const config = {
363
+ matcher: ["/dashboard/:path*", "/api/protected/:path*"]
364
+ };
365
+ ```
366
+
367
+ ## 🔄 How It Works
368
+
369
+ 1. **Authorization Request**: User clicks sign-in, SDK redirects to Ezz Auth core with your `AUTH_APP_KEY`
370
+ 2. **User Authentication**: Core system handles login via configured providers (OAuth, email, etc.)
371
+ 3. **Callback Processing**: Core redirects back with authorization code
372
+ 4. **Token Exchange**: Your app exchanges code for session token using `AUTH_APP_SECRET`
373
+ 5. **Session Storage**: Session stored in HTTP-only, secure, SameSite=Lax cookie
374
+ 6. **Request Validation**: Each request validates cookie against core introspection endpoint
375
+
376
+ ## 🛡️ Security Features
377
+
378
+ - **No Token Exposure**: Authentication tokens never reach client-side JavaScript
379
+ - **HTTP-Only Cookies**: Session cookies cannot be accessed via JavaScript
380
+ - **Secure by Default**: Automatic security headers and cookie configuration
381
+ - **Server-Side Validation**: All authentication checks happen server-side
382
+ - **CSRF Protection**: Built-in protection against cross-site request forgery
383
+
384
+ ## 🐛 Troubleshooting
385
+
386
+ ### Common Issues
387
+
388
+ **"No app directory found"**
389
+ - Ensure you're using Next.js App Router (not Pages Router)
390
+ - Check that your app directory is at `app/` or `src/app/`
391
+
392
+ **"Authentication failed"**
393
+ - Verify `AUTH_BASE_URL` is correct and accessible
394
+ - Check that `AUTH_APP_KEY` and `AUTH_APP_SECRET` are valid
395
+ - Ensure callback URL is configured in your auth provider
396
+
397
+ **"Session not persisting"**
398
+ - Check that cookies are enabled in the browser
399
+ - Verify your domain configuration matches the auth core
400
+
401
+ ## 📄 License
402
+
403
+ MIT © [Your Organization]
404
+
405
+ ## 🤝 Contributing
406
+
407
+ 1. Fork the repository
408
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
409
+ 3. Commit your changes (`git commit -m 'Add amazing feature'`)
410
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
411
+ 5. Open a Pull Request
412
+
413
+ ## 📞 Support
414
+
415
+ - 📖 [Documentation](https://docs.ezz-auth.com)
416
+ - 💬 [Discord Community](https://discord.gg/ezz-auth)
417
+ - 🐛 [Issue Tracker](https://github.com/your-org/ezz-auth/issues)
418
+ - 📧 [Email Support](mailto:support@ezz-auth.com)
package/bin/index.js ADDED
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "fs";
4
+ import path from "path";
5
+ import url from "url";
6
+
7
+ const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
8
+ const projectRoot = process.cwd();
9
+
10
+ const command = process.argv[2];
11
+
12
+ /* ---------------- helpers ---------------- */
13
+
14
+ function findAppDir() {
15
+ const srcApp = path.join(projectRoot, "src", "app");
16
+ const app = path.join(projectRoot, "app");
17
+
18
+ if (fs.existsSync(srcApp)) return srcApp;
19
+ if (fs.existsSync(app)) return app;
20
+
21
+ console.error("❌ No app or src/app directory found");
22
+ process.exit(1);
23
+ }
24
+
25
+ function copyTemplate(relativePath) {
26
+ const source = path.join(__dirname, "..", "templates", relativePath);
27
+ const target = path.join(
28
+ findAppDir(),
29
+ relativePath.replace(/^app\//, "")
30
+ );
31
+
32
+ fs.mkdirSync(path.dirname(target), { recursive: true });
33
+
34
+ if (fs.existsSync(target)) {
35
+ console.log("⚠ Skipped:", target);
36
+ return;
37
+ }
38
+
39
+ fs.copyFileSync(source, target);
40
+ console.log("✔ Created:", target);
41
+ }
42
+
43
+ /* ---------------- commands ---------------- */
44
+
45
+ function runInit() {
46
+ copyTemplate("app/api/auth/callback/route.ts");
47
+ copyTemplate("app/api/auth/logout/route.ts");
48
+
49
+ console.log("\n✅ ezz-auth initialized");
50
+ console.log("Next steps:");
51
+ console.log("1. Set env variables");
52
+ console.log("2. Redirect users to ezz-auth login");
53
+ }
54
+
55
+ /* ---------------- entry ---------------- */
56
+
57
+ if (command === "init") {
58
+ runInit();
59
+ } else {
60
+ console.log(`
61
+ Usage:
62
+ npx ezz-auth init
63
+
64
+ Description:
65
+ Scaffold ezz-auth routes for Next.js App Router
66
+ `);
67
+ }
@@ -0,0 +1,18 @@
1
+
2
+ "use client";
3
+ import React, { createContext, useContext } from "react";
4
+ const AuthContext = createContext(null);
5
+
6
+ export function AuthProvider({ children, user }) {
7
+ return (
8
+ <AuthContext.Provider value={{ user, isSignedIn: !!user }}>
9
+ {children}
10
+ </AuthContext.Provider>
11
+ );
12
+ }
13
+
14
+ export function useAuthContext() {
15
+ const ctx = useContext(AuthContext);
16
+ if (!ctx) throw new Error("AuthProvider missing");
17
+ return ctx;
18
+ }
@@ -0,0 +1,28 @@
1
+ "use client";
2
+
3
+ export function SignInButton({ state }) {
4
+ const signIn = () => {
5
+ const url = new URL(
6
+ "/api/app/v1/authorize",
7
+ process.env.NEXT_PUBLIC_AUTH_BASE_URL
8
+ );
9
+
10
+ url.searchParams.set(
11
+ "app_key",
12
+ process.env.NEXT_PUBLIC_AUTH_PUBLIC_KEY
13
+ );
14
+
15
+ url.searchParams.set(
16
+ "redirect_uri",
17
+ window.location.origin + "/api/auth/callback"
18
+ );
19
+
20
+ // ✅ ADD THIS
21
+ const finalState = state ?? window.location.pathname;
22
+ url.searchParams.set("state", finalState);
23
+
24
+ window.location.href = url.toString();
25
+ };
26
+
27
+ return <button onClick={signIn}>Sign in</button>;
28
+ }
@@ -0,0 +1,16 @@
1
+ export function AuthProvider(props: {
2
+ children: React.ReactNode;
3
+ user: any;
4
+ }): JSX.Element;
5
+
6
+ export function useUser(): {
7
+ user: any;
8
+ isSignedIn: boolean;
9
+ isLoaded: boolean;
10
+ };
11
+
12
+ export function SignInButton(): JSX.Element;
13
+
14
+ export function UserButton(): JSX.Element;
15
+
16
+ export function signOut(): void;
@@ -0,0 +1,7 @@
1
+
2
+ export { AuthProvider } from "./AuthProvider.js";
3
+ export { useUser } from "./useUser.js";
4
+ export { SignInButton } from "./SignInButton.js";
5
+ export { UserButton } from "./userButton.js";
6
+ export { signOut } from "./signOut.js";
7
+
@@ -0,0 +1,8 @@
1
+ // packages/next/client/signOut.js
2
+ export async function signOut() {
3
+ try {
4
+ await fetch("/api/auth/logout", { method: "POST" });
5
+ } catch {}
6
+
7
+ window.location.reload();
8
+ }
@@ -0,0 +1,6 @@
1
+
2
+ import { useAuthContext } from "./AuthProvider.js";
3
+ export function useUser() {
4
+ const { user, isSignedIn } = useAuthContext();
5
+ return { user, isSignedIn, isLoaded: true };
6
+ }
@@ -0,0 +1,65 @@
1
+ "use client";
2
+
3
+ import { useUser } from "./useUser.js";
4
+ import { signOut } from "./signOut.js";
5
+
6
+ import {
7
+ DropdownMenu,
8
+ DropdownMenuTrigger,
9
+ DropdownMenuContent,
10
+ DropdownMenuItem,
11
+ DropdownMenuLabel,
12
+ DropdownMenuSeparator,
13
+ } from "@/components/ui/dropdown-menu";
14
+
15
+ import { Avatar, AvatarFallback } from "@/components/ui/avatar";
16
+ import { Button } from "@/components/ui/button";
17
+
18
+ export function UserButton() {
19
+ const { user, isSignedIn, isLoaded } = useUser();
20
+
21
+ if (!isLoaded || !isSignedIn || !user) return null;
22
+
23
+ const initial =
24
+ user.name?.charAt(0)?.toUpperCase() ||
25
+ user.email?.charAt(0)?.toUpperCase() ||
26
+ "?";
27
+
28
+ return (
29
+ <DropdownMenu>
30
+ <DropdownMenuTrigger asChild>
31
+ <Button
32
+ variant="ghost"
33
+ size="icon"
34
+ className="rounded-full"
35
+ >
36
+ <Avatar className="h-8 w-8">
37
+ <AvatarFallback>{initial}</AvatarFallback>
38
+ </Avatar>
39
+ </Button>
40
+ </DropdownMenuTrigger>
41
+
42
+ <DropdownMenuContent align="end" className="w-64">
43
+ <DropdownMenuLabel className="font-normal">
44
+ <div className="flex flex-col space-y-1">
45
+ <p className="text-xs text-muted-foreground">
46
+ Signed in as
47
+ </p>
48
+ <p className="text-sm font-medium leading-none">
49
+ {user.email}
50
+ </p>
51
+ </div>
52
+ </DropdownMenuLabel>
53
+
54
+ <DropdownMenuSeparator />
55
+
56
+ <DropdownMenuItem
57
+ onClick={signOut}
58
+ className="cursor-pointer"
59
+ >
60
+ Log out
61
+ </DropdownMenuItem>
62
+ </DropdownMenuContent>
63
+ </DropdownMenu>
64
+ );
65
+ }
package/index.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ export * from "./client";
2
+ export * from "./server";
3
+ export * from "./bin";
4
+ export * from "./middleware";
5
+
@@ -0,0 +1,3 @@
1
+ import type { NextMiddleware } from "next/server";
2
+
3
+ export function authMiddleware(): NextMiddleware;
@@ -0,0 +1,14 @@
1
+
2
+ import { NextResponse } from "next/server";
3
+ export function authMiddleware() {
4
+ return function middleware(req) {
5
+ const session = req.cookies.get("__auth_session")?.value;
6
+ if (!session) {
7
+ const loginUrl = new URL("/api/app/v1/authorize", process.env.NEXT_PUBLIC_AUTH_BASE_URL);
8
+ loginUrl.searchParams.set("app_key", process.env.NEXT_PUBLIC_AUTH_PUBLIC_KEY);
9
+ loginUrl.searchParams.set("redirect_uri", req.nextUrl.href);
10
+ return NextResponse.redirect(loginUrl);
11
+ }
12
+ return NextResponse.next();
13
+ };
14
+ }
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@auth-ezz/nextjs",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "types": "./index.d.ts",
6
+ "bin": {
7
+ "ezz-auth": "bin/index.js"
8
+ },
9
+ "publishConfig": {
10
+ "access": "public"
11
+ },
12
+
13
+ "exports": {
14
+ ".": {
15
+ "types": "./index.d.ts",
16
+ "default": "./client/index.js"
17
+ },
18
+ "./client": {
19
+ "types": "./client/index.d.ts",
20
+ "default": "./client/index.js"
21
+ },
22
+ "./server": {
23
+ "types": "./server/index.d.ts",
24
+ "default": "./server/index.js"
25
+ },
26
+ "./middleware": {
27
+ "types": "./middleware/authMiddleware.d.ts",
28
+ "default": "./middleware/authMiddleware.js"
29
+ }
30
+ }
31
+ }
package/server/auth.js ADDED
@@ -0,0 +1,18 @@
1
+
2
+ import { cookies } from "next/headers";
3
+ const AUTH_COOKIE = "user_session";
4
+
5
+ export async function auth() {
6
+ const cookieStore = await cookies();
7
+ const sessionToken = cookieStore.get(AUTH_COOKIE)?.value;
8
+ if (!sessionToken) return { user: null, session: null };
9
+
10
+ const res = await fetch(
11
+ `${process.env.AUTH_BASE_URL}/api/app/v1/userinfo`,
12
+ { headers: { Authorization: `Bearer ${sessionToken}` }, cache: "no-store" }
13
+ );
14
+
15
+ if (!res.ok) return { user: null, session: null };
16
+ const data = await res.json();
17
+ return { user: data.user, session: { token: sessionToken } };
18
+ }
@@ -0,0 +1,6 @@
1
+
2
+ import { auth } from "./auth.js";
3
+ export async function currentUser() {
4
+ const { user } = await auth();
5
+ return user;
6
+ }
@@ -0,0 +1,50 @@
1
+ import { NextResponse } from "next/server";
2
+
3
+ export function handleAuthCallback(options = {}) {
4
+ const fallbackRedirect = options.redirectTo ?? "/";
5
+
6
+ return async function GET(req) {
7
+ const { searchParams, origin } = req.nextUrl;
8
+
9
+ const code = searchParams.get("code");
10
+ const rawState = searchParams.get("state");
11
+
12
+ // ✅ SAFETY CHECK
13
+ const state =
14
+ rawState && rawState.startsWith("/") ? rawState : fallbackRedirect;
15
+
16
+ if (!code) {
17
+ return NextResponse.redirect(new URL(state, origin));
18
+ }
19
+
20
+ const res = await fetch(
21
+ `${process.env.AUTH_BASE_URL}/api/app/v1/token`,
22
+ {
23
+ method: "POST",
24
+ headers: {
25
+ "Content-Type": "application/json",
26
+ Authorization: `Bearer ${process.env.AUTH_APP_SECRET}`,
27
+ },
28
+ body: JSON.stringify({ code }),
29
+ }
30
+ );
31
+
32
+ if (!res.ok) {
33
+ return NextResponse.redirect(new URL(state, origin));
34
+ }
35
+
36
+ const data = await res.json();
37
+
38
+ const response = NextResponse.redirect(new URL(state, origin));
39
+
40
+ response.cookies.set("user_session", data.sessionToken, {
41
+ httpOnly: true,
42
+ sameSite: "lax",
43
+ secure: process.env.NODE_ENV === "production",
44
+ path: "/",
45
+ expires: new Date(data.expiresAt),
46
+ });
47
+
48
+ return response;
49
+ };
50
+ }
@@ -0,0 +1,5 @@
1
+ import { logout } from "./logout";
2
+
3
+ export function handleLogout() {
4
+ return async (req) => logout(req);
5
+ }
@@ -0,0 +1,24 @@
1
+ export function auth(): Promise<{
2
+ user: any | null;
3
+ session: any | null;
4
+ }>;
5
+
6
+ export function currentUser(): Promise<any | null>;
7
+
8
+
9
+
10
+ export function handleAuthCallback(options?: {
11
+ redirectTo?: string;
12
+ }): (req: import("next/server").NextRequest) =>
13
+ Promise<import("next/server").NextResponse>;
14
+
15
+
16
+ export function handleLogout(): (req: import("next/server").NextRequest) =>
17
+ Promise<import("next/server").NextResponse>;
18
+
19
+ export function requireAuth(options?: {
20
+ state?: string;
21
+ }): Promise<{
22
+ user: any | null;
23
+ session: any | null;
24
+ }>;
@@ -0,0 +1,6 @@
1
+
2
+ export { auth } from "./auth.js";
3
+ export { currentUser } from "./currentUser.js";
4
+ export { requireAuth } from "./requireAuth.js";
5
+ export { handleAuthCallback } from "./handleAuthCallback.js";
6
+ export { handleLogout } from "./handleLogout.js";
@@ -0,0 +1,25 @@
1
+ import { NextResponse } from "next/server";
2
+
3
+ export async function logout(req) {
4
+ const sessionToken = req.cookies.get("user_session")?.value;
5
+
6
+ const res = NextResponse.json({ ok: true });
7
+ res.cookies.delete("user_session", { path: "/" });
8
+
9
+ if (!sessionToken) return res;
10
+
11
+ try {
12
+ await fetch(`${process.env.AUTH_BASE_URL}/api/app/v1/logout`, {
13
+ method: "POST",
14
+ headers: {
15
+ Authorization: `Bearer ${process.env.AUTH_APP_SECRET}`,
16
+ "Content-Type": "application/json",
17
+ },
18
+ body: JSON.stringify({ sessionToken }),
19
+ });
20
+ } catch {
21
+ // logout must never block UX
22
+ }
23
+
24
+ return res;
25
+ }
@@ -0,0 +1,34 @@
1
+ import { redirect } from "next/navigation";
2
+ import { auth } from "./auth.js";
3
+
4
+ export async function requireAuth(options = {}) {
5
+ const result = await auth();
6
+
7
+ if (result.user) {
8
+ return result;
9
+ }
10
+
11
+ const state = options.state || "/";
12
+
13
+ // 🔒 Safety check
14
+ const safeState = state.startsWith("/") ? state : "/";
15
+
16
+ const url = new URL(
17
+ "/api/app/v1/authorize",
18
+ process.env.NEXT_PUBLIC_AUTH_BASE_URL
19
+ );
20
+
21
+ url.searchParams.set(
22
+ "app_key",
23
+ process.env.NEXT_PUBLIC_AUTH_PUBLIC_KEY
24
+ );
25
+
26
+ url.searchParams.set(
27
+ "redirect_uri",
28
+ `${process.env.NEXT_PUBLIC_APP_URL}/api/auth/callback`
29
+ );
30
+
31
+ url.searchParams.set("state", safeState);
32
+
33
+ redirect(url.toString());
34
+ }
@@ -0,0 +1,5 @@
1
+ import { handleAuthCallback } from "@ezz-auth/next/server";
2
+
3
+ export const GET = handleAuthCallback({
4
+ redirectTo: "/dashboard",
5
+ });
@@ -0,0 +1,4 @@
1
+ // test-app/app/api/auth/logout/route.js
2
+ import { handleLogout } from "@ezz-auth/next/server";
3
+
4
+ export const POST = handleLogout();