Skip to content
Draft
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,33 @@ export const auth0 = new Auth0Client();
> The Auth0Client automatically uses safe defaults to manage authentication cookies. For advanced use cases, you can customize transaction cookie behavior by providing your own configuration. See [Transaction Cookie Configuration](https://github.com/auth0/nextjs-auth0/blob/main/EXAMPLES.md#transaction-cookie-configuration) for details.

### 4. Add the authentication middleware
Authentication requests in Next.js are intercepted at the network boundary using a middleware or proxy file.
Follow the setup below depending on your Next.js version.

#### 🟦 On Next.js 15

Create a `middleware.ts` file in the root of your project:

```ts
import type { NextRequest } from "next/server";
import { auth0 } from "./lib/auth0"; // Adjust path if your auth0 client is elsewhere

export async function middleware(request: NextRequest) {
return await auth0.middleware(request);
}

export const config = {
matcher: [
/*
* Match all request paths except for:
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico, sitemap.xml, robots.txt (metadata files)
*/
"/((?!_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)"
]
};
```

Create a `middleware.ts` file in the root of your project's directory:

Expand Down Expand Up @@ -98,6 +125,34 @@ export const config = {
> [!NOTE]
> If you're using a `src/` directory, the `middleware.ts` file must be created inside the `src/` directory.


#### 🟨 On Next.js 16
Next.js 16 introduces a new convention called proxy.ts, replacing middleware.ts.
This change better represents the network interception boundary and unifies request handling
for both the Edge and Node runtimes.

Create a proxy.ts file in the root of your project (Or rename your existing middleware.ts to proxy.ts):
```ts
import { auth0 } from "./lib/auth0";

export async function proxy(request: Request) { // Note that proxy uses the standard Request type
return await auth0.middleware(request);
}

export const config = {
matcher: [
"/((?!_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)"
]
};
```
> [!IMPORTANT]
> Starting with **Next.js 16**, the recommended file for handling authentication boundaries is **`proxy.ts`**. You can still continue using **`middleware.ts`** for backward compatibility, it will work under the **Edge runtime** in Next.js 16. However, it is **deprecated** for the Node runtime and will be removed in a future release.
>
> The new proxy layer also executes slightly earlier in the routing pipeline, so make sure your matcher patterns do not conflict with other proxy or middleware routes.
>
> Additionally, the Edge runtime now applies stricter header and cookie validation,
> so avoid setting non-string cookie values or invalid header formats.

> [!IMPORTANT]
> This broad middleware matcher is essential for rolling sessions and security features. For scenarios when rolling sessions are disabled, see [Session Configuration](https://github.com/auth0/nextjs-auth0/blob/main/EXAMPLES.md#session-configuration) for alternative approaches.

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
"vitest": "^2.1.4"
},
"peerDependencies": {
"next": "^14.2.25 || ^15.2.3",
"next": "^14.2.25 || ^15.2.3 || ^16.0.0-0",
"react": "^18.0.0 || ^19.0.0 || ^19.0.0-0",
"react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-0"
},
Expand Down
4 changes: 4 additions & 0 deletions src/server/auth-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ import {
FetcherHooks,
FetcherMinimalConfig
} from "./fetcher.js";
import { isPrefetch } from "./next-compat.js";
import { AbstractSessionStore } from "./session/abstract-session-store.js";
import { TransactionState, TransactionStore } from "./transaction-store.js";
import { filterDefaultIdTokenClaims } from "./user.js";
Expand Down Expand Up @@ -400,6 +401,9 @@ export class AuthClient {
) {
return this.handleConnectAccount(req);
} else {
if (isPrefetch(req)) {
return NextResponse.next();
}
// no auth handler found, simply touch the sessions
// TODO: this should only happen if rolling sessions are enabled. Also, we should
// try to avoid reading from the DB (for stateful sessions) on every request if possible.
Expand Down
42 changes: 27 additions & 15 deletions src/server/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
WithPageAuthRequiredAppRouterOptions,
WithPageAuthRequiredPageRouterOptions
} from "./helpers/with-page-auth-required.js";
import { toNextRequest } from "./next-compat.js";
import {
AbstractSessionStore,
SessionConfiguration,
Expand Down Expand Up @@ -502,8 +503,8 @@ export class Auth0Client {
/**
* middleware mounts the SDK routes to run as a middleware function.
*/
middleware(req: NextRequest): Promise<NextResponse> {
return this.authClient.handler.bind(this.authClient)(req);
middleware(req: Request | NextRequest): Promise<NextResponse> {
return this.authClient.handler.bind(this.authClient)(toNextRequest(req));
}

/**
Expand All @@ -526,12 +527,13 @@ export class Auth0Client {
* getSession returns the session data for the current request.
*/
async getSession(
req?: PagesRouterRequest | NextRequest
req?: Request | PagesRouterRequest | NextRequest
): Promise<SessionData | null> {
if (req) {
// middleware usage
if (req instanceof NextRequest) {
return this.sessionStore.get(req.cookies);
if (req instanceof Request) {
const nextReq = toNextRequest(req);
return this.sessionStore.get(nextReq.cookies);
}

// pages router usage
Expand Down Expand Up @@ -745,7 +747,7 @@ export class Auth0Client {
*/
async getAccessTokenForConnection(
options: AccessTokenForConnectionOptions,
req: PagesRouterRequest | NextRequest | undefined,
req: PagesRouterRequest | NextRequest | Request | undefined,
res: PagesRouterResponse | NextResponse | undefined
): Promise<{ token: string; expiresAt: number }>;

Expand All @@ -768,11 +770,12 @@ export class Auth0Client {
*/
async getAccessTokenForConnection(
options: AccessTokenForConnectionOptions,
req?: PagesRouterRequest | NextRequest,
req?: PagesRouterRequest | NextRequest | Request,
res?: PagesRouterResponse | NextResponse
): Promise<{ token: string; expiresAt: number; scope?: string }> {
const session: SessionData | null = req
? await this.getSession(req)
const nextReq = req instanceof Request ? toNextRequest(req) : req;
const session: SessionData | null = nextReq
? await this.getSession(nextReq)
: await this.getSession();

if (!session) {
Expand Down Expand Up @@ -828,7 +831,7 @@ export class Auth0Client {
...session,
connectionTokenSets: tokenSets
},
req,
nextReq,
res
);
}
Expand All @@ -846,7 +849,7 @@ export class Auth0Client {
* This method can be used in middleware and `getServerSideProps`, API routes, and middleware in the **Pages Router**.
*/
async updateSession(
req: PagesRouterRequest | NextRequest,
req: PagesRouterRequest | NextRequest | Request,
res: PagesRouterResponse | NextResponse,
session: SessionData
): Promise<void>;
Expand All @@ -862,10 +865,18 @@ export class Auth0Client {
* updateSession updates the session of the currently authenticated user. If the user does not have a session, an error is thrown.
*/
async updateSession(
reqOrSession: PagesRouterRequest | NextRequest | SessionData,
reqOrSession: PagesRouterRequest | NextRequest | Request | SessionData,
res?: PagesRouterResponse | NextResponse,
sessionData?: SessionData
) {
// Normalize plain Request (Next 16 Node runtime) → NextRequest
if (
reqOrSession instanceof Request &&
!(reqOrSession instanceof NextRequest)
) {
reqOrSession = toNextRequest(reqOrSession);
}

if (!res) {
// app router: Server Actions, Route Handlers
const existingSession = await this.getSession();
Expand Down Expand Up @@ -1243,7 +1254,7 @@ export class Auth0Client {
* @see {@link FetcherMinimalConfig} for available configuration options
*/
public async createFetcher<TOutput extends Response = Response>(
req: PagesRouterRequest | NextRequest | undefined,
req: PagesRouterRequest | NextRequest | Request | undefined,
options: {
/** Enable DPoP for this fetcher instance (overrides global setting) */
useDPoP?: boolean;
Expand All @@ -1255,8 +1266,9 @@ export class Auth0Client {
fetch?: CustomFetchImpl<TOutput>;
}
) {
const session: SessionData | null = req
? await this.getSession(req)
const nextReq = req instanceof Request ? toNextRequest(req) : req;
const session: SessionData | null = nextReq
? await this.getSession(nextReq)
: await this.getSession();

if (!session) {
Expand Down
2 changes: 1 addition & 1 deletion src/server/cookies.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { NextResponse } from "next/server.js";
import type { NextResponse } from "next/server.js";
import {
RequestCookie,
RequestCookies,
Expand Down
9 changes: 6 additions & 3 deletions src/server/helpers/with-api-auth-required.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { NextApiHandler } from "next";
import { NextRequest, NextResponse } from "next/server.js";

import { Auth0Client } from "../client.js";
import { toNextRequest } from "../next-compat.js";

/**
* This contains `param`s, which is a Promise that resolves to an object
Expand All @@ -22,7 +23,7 @@ export type AppRouteHandlerFn = (
/**
* Incoming request object.
*/
req: NextRequest,
req: NextRequest | Request,
/**
* Context properties on the request (including the parameters if this was a
* dynamic route).
Expand Down Expand Up @@ -80,8 +81,10 @@ export type WithApiAuthRequired = WithApiAuthRequiredAppRoute &
export const appRouteHandlerFactory =
(client: Auth0Client): WithApiAuthRequiredAppRoute =>
(apiRoute) =>
async (req, params): Promise<NextResponse> => {
const session = await client.getSession();
async (req: NextRequest | Request, params): Promise<NextResponse> => {
const nextReq = req instanceof Request ? toNextRequest(req) : req;

const session = await client.getSession(nextReq);

if (!session || !session.user) {
return NextResponse.json(
Expand Down
23 changes: 23 additions & 0 deletions src/server/next-compat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { NextRequest, NextResponse } from "next/server.js";

Check failure on line 1 in src/server/next-compat.ts

View workflow job for this annotation

GitHub Actions / Lint Code

'NextResponse' is defined but never used. Allowed unused vars must match /^_/u

export function toNextRequest(input: Request | NextRequest): NextRequest {
if (input instanceof NextRequest) {
return input;
}

// 16 proxy.ts → plain Request
return new NextRequest(input.url, {
method: input.method,
headers: input.headers,
body: input.body as any,
duplex: (input as any).duplex ?? "half"
});
}

export function isPrefetch(req: Request | NextRequest): boolean {
const h = req.headers;
if (h.get("x-middleware-prefetch") === "1") return true;
if (h.get("next-router-prefetch") === "1") return true;
if (h.get("purpose") === "prefetch") return true;
return false;
}
2 changes: 1 addition & 1 deletion src/utils/request.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { IncomingMessage } from "http";
import { NextApiRequest } from "next";
import { NextRequest } from "next/server.js";
import type { NextRequest } from "next/server.js";

type Req =
| IncomingMessage
Expand Down
Loading