Using the Next.js Page Router instead? See the Page Router version of this guide.
This guide assumes you have a Next.js project set up and running. If you haven't set up authentication in your Next.js App Router project yet, please refer to the quickstart guide.
First, we need to create the API endpoint we want to protect with MonoCloud.
Inside the directory src/app/api/protected, create a file named route.ts and insert the following code:
import { NextResponse } from "next/server";
export const GET = () => NextResponse.json({ secret: "The secret" });
This endpoint will return a JSON response with the payload: { "secret": "The secret" }.
Create a file named middleware.ts in the src directory and enter the following code:
import { monoCloudMiddleware } from "@monocloud/nextjs-auth";
export default monoCloudMiddleware();
export const config = {
matcher: ['/', '/(api)(.*)', '/((?!.*\\..*|_next).*)'],
};
The matcher configuration ensures that the middleware will only process requests for main user-facing pages and API routes, while excluding static files and Next.js-specific resources.
By default, the MonoCloud middleware protects all routes in your application. Later, we'll see how to customize this behavior to protect only specific routes.
Now that we have an endpoint, let's see it in action. We'll trigger a call to the protected API when a button is clicked.
Open src/app/page.tsx and replace the existing code with:
"use client";
import { SignIn, SignOut } from "@monocloud/nextjs-auth/components";
import { useUser } from "@monocloud/nextjs-auth/client";
import { useState } from "react";
export default function FetchData() {
const { isAuthenticated } = useUser();
const [data, setData] = useState();
const handleFetch = async (url: string) => {
const response = await fetch(url);
setData(await response.json());
};
return (
<div className="flex flex-col gap-3 max-w-sm">
<div className="flex flex-col gap-1">
<p>Data from route: {JSON.stringify(data, undefined, 2)}</p>
</div>
<button className="outline p-2" onClick={() => handleFetch("/api/protected")}>
Fetch Protected Data
</button>
{isAuthenticated ? <SignOut>Sign Out</SignOut> : <SignIn>Sign In</SignIn>}
</div>
);
}
Let's review what this code does:
handleFetch() function.handleFetch() function calls the /api/protected endpoint we created earlier.<div> above the button.See it in action by running:
npm run dev
Then visit http://localhost:3000.
Since we've protected all routes, including the root, you'll be redirected to the sign-in page.
Once you're signed in, click the Fetch Protected Data button. You should see the secret data retrieved from the protected API: { "secret": "The Secret." }.
It's likely that you'll want to protect some routes while leaving others open to everyone. For example, you might have a public homepage or a contact page that doesn't require authentication.
We can configure monoCloudMiddleware() to protect specific routes selectively.
The SDK gives you multiple ways to configure which routes to protect:
monoCloudMiddleware()Let's look at each one in turn.
To protect specific routes, you can configure the protectedRoutes option in the monoCloudMiddleware(). The option accepts an array of route identifiers.
For example, if you want to protect the /api/protected endpoint, while leaving everything else publicly accessible, you'd pass protectedRoutes: ["/api/protected"] as an option to the monoCloudMiddleware():
import { monoCloudMiddleware } from "@monocloud/nextjs-auth";
export default monoCloudMiddleware({ protectedRoutes: ["/api/protected"] });
export const config = {
matcher: ['/', '/(api)(.*)', '/((?!.*\\..*|_next).*)'],
};
To get more control over the routes you want to protect, you can also use regular expressions in the array you pass to protectedRoutes.
For example, to protect all routes under /api/protected, you could use the regex pattern ^/api/protected(/.*)?$ This pattern will match any route that starts with /api/protected.
import { monoCloudMiddleware } from "@monocloud/nextjs-auth";
export default monoCloudMiddleware({ protectedRoutes: ["^/api/protected(/.*)?$"] });
export const config = {
matcher: ['/', '/(api)(.*)', '/((?!.*\\..*|_next).*)'],
};
When the protectedRoutes option is set to an empty array, all routes are unprotected.
You can protect routes dynamically by providing a custom function to monoCloudMiddleware() instead of a static list of routes. This callback function provides the incoming request as an argument, enabling you to decide which routes to secure based on the details of the request.
For example, you could protect all routes under /api/protected by checking if the request URL contains that path:
import { monoCloudMiddleware } from "@monocloud/nextjs-auth";
export default monoCloudMiddleware({
protectedRoutes: (req) => {
// Your logic
return req.nextUrl.pathname.startsWith('/api/protected');
},
});
export const config = {
matcher: ['/', '/(api)(.*)', '/((?!.*\\..*|_next).*)'],
};
monoCloudMiddleware()If your application requires complex logic to determine which routes to protect, you can create a custom middleware that conditionally runs the monoCloudMiddleware(). This approach also allows you to combine multiple middlewares.
import { monoCloudMiddleware } from "@monocloud/nextjs-auth";
import { NextFetchEvent, NextRequest, NextResponse } from "next/server";
export default function customMiddleware(req: NextRequest, evt: NextFetchEvent) {
if (req.nextUrl.pathname.startsWith("/api/protected")) {
return monoCloudMiddleware(req, evt);
}
return NextResponse.next();
}
export const config = {
matcher: ['/', '/(api)(.*)', '/((?!.*\\..*|_next).*)'],
};
In this example, the customMiddleware() function conditionally runs the monoCloudMiddleware(). When a route meets certain criteria (for instance, any route beginning with /api/protected), the monoCloudMiddleware() is activated to manage authentication.
For other routes, the middleware allows the request to proceed without any additional checks by returning NextResponse.next(). This ensures that authentication checks are only performed where necessary.
By default, the monoCloudMiddleware() protects all routes. However, when you specify particular routes to protect using one of the three methods described earlier, any routes not explicitly listed will remain unprotected. This approach allows you to secure specific parts of your application while leaving others accessible to all users.
We can put this to the test by creating a new route that will be unprotected by the middleware.
First, create a file named route.ts under src/app/api/unprotected and add the following code:
import { NextResponse } from "next/server";
export const GET = async () =>
NextResponse.json({ message: "Open access" });
The /api/unprotected endpoint returns { "message": "Open access" } as the response body.
Next, add a second button to fetch data from /api/unprotected. In your src/app/page.tsx file, place this new button next to the existing Fetch Protected Data button:
"use client";
import { SignIn, SignOut } from "@monocloud/nextjs-auth/components";
import { useUser } from "@monocloud/nextjs-auth/client";
import { useState } from "react";
export default function FetchData() {
const { isAuthenticated } = useUser();
const [data, setData] = useState();
const handleFetch = async (url: string) => {
const response = await fetch(url);
setData(await response.json());
};
return (
<div className="flex flex-col gap-3 max-w-sm">
<div className="flex flex-col gap-1">
<p>Data from route: {JSON.stringify(data, undefined, 2)}</p>
</div>
<button className="outline p-2" onClick={() => handleFetch("/api/protected")}>
Fetch Protected Data
</button>
<button className="outline p-2" onClick={() => handleFetch("/api/unprotected")}>
Fetch Unprotected Data
</button>
{isAuthenticated ? <SignOut>Sign Out</SignOut> : <SignIn>Sign In</SignIn>}
</div>
);
}
The second button will fetch data from the /api/unprotected route and display it on the page.
Fire up your Next.js application by running:
npm run dev
Once the app is running, visit http://localhost:3000. This time, you won't be prompted to sign in because we've explicitly protected some routes, leaving all others unrestricted.
If you fetch data from the unprotected route /api/unprotected, you'll see the message { "message": "Open access" }.
However, if you're not signed in and attempt to fetch data from a protected route, you'll see the message { "message": "unauthorized" }.
Try it out and then sign in by clicking the Sign In button. Once you're back at the Next.js application, click the button to fetch data from the protected route. This time, you'll see the data { "secret": "The secret" }.
And that's how you can use the monoCloudMiddleware() to protect specific routes in your Next.js application.