Next.js (Pages Router)¶
Next.js is a highly popular JavaScript framework for Server-Side Rendering. It tries to combine the reactivity of React and the performance of backend technologies. As such, it has many subtelties and comes with very specific challenges, especially regarding user authentication.
To help you with this, the Fief JavaScript client provides tools dedicated to Next.js. Let's see how to use them!
Starting a new Next.js app?
We recommend you to look at the more modern App Router approach.
Install the client¶
The big picture¶
As we said, Next.js is a hybrid framework combining backend technologies and frontend technologies. It means that managing authentication will have to happen at several levels.
graph LR
subgraph Next.js
M[Middleware]
A[API]
R[React]
end
F[Fief]
RQ((Request))
RQ --> M
M -- not authenticated --> F
F -- callback --> M
M -- authenticated --> R
R -- useFiefUserinfo --> A
Middleware level¶
In most recent versions, Next.js introduced Middleware. It's a useful mechanism allowing us to define logic before every request. Next.js team designed it to be highly performant: it's executed by their Edge Runtime technology, a special runtime different from Node.js.
In Fief implementation, we'll use the Middleware to check if the user is authenticated and if they have the right scope and permissions.
Besides, it'll also automatically handle special routes dedicated to authentication:
/login
will redirect to Fief authentication page./auth-callback
will complete the OAuth2 callback and set the session cookie;/logout
will clear the session cookie and redirect to Fief logout.
API level¶
Next.js allows us to define API routes. They can be used to perform backend logic and are executed by a standard Node.js runtime.
In Fief implemenetation, we'll define an API route allowing the React application to retrieve the current user information.
React level¶
Finally, the React level is the interface shown to the user.
In Fief implementation, we provide hooks allowing you to retrieve the current user information and authentication state.
Configure your project¶
This is for you if...
- You use Next.js with Pages Router.
Prerequisites
- Bootstrap a Next.js project as described in Automatic Setup section of the Next.js documentation.
- Allow the following Redirect URI on your Fief Client:
http://localhost:3000/auth-callback
1. Create a fief
module¶
Let's create a fief.ts
module at the root of the project. It'll contain the basic instantiation of Fief helpers.
import { Fief, FiefUserInfo } from '@fief/fief';
import { FiefAuth, IUserInfoCache } from '@fief/fief/nextjs';
export const SESSION_COOKIE_NAME = "user_session"; // (1)!
export const fiefClient = new Fief({ // (2)!
baseURL: 'http://localhost:8000',
clientId: 'YOUR_CLIENT_ID',
clientSecret: 'YOUR_CLIENT_SECRET',
requestInit: { next: { revalidate: 3600 } },
});
class MemoryUserInfoCache implements IUserInfoCache { // (3)!
private storage: Record<string, any>;
constructor() {
this.storage = {};
}
async get(id: string): Promise<FiefUserInfo | null> {
const userinfo = this.storage[id];
if (userinfo) {
return userinfo;
}
return null;
}
async set(id: string, userinfo: FiefUserInfo): Promise<void> {
this.storage[id] = userinfo;
}
async remove(id: string): Promise<void> {
this.storage[id] = undefined;
}
async clear(): Promise<void> {
this.storage = {};
}
}
export const fiefAuth = new FiefAuth({ // (4)!
client: fiefClient, // (5)!
sessionCookieName: SESSION_COOKIE_NAME, // (6)!
redirectURI: 'http://localhost:3000/auth-callback', // (7)!
logoutRedirectURI: 'http://localhost:3000', // (8)!
userInfoCache: new MemoryUserInfoCache(), // (9)!
});
-
Define a session cookie name constant
We'll use a cookie to maintain the user session.
For convenience, we set its name in a constant.
-
Fief client instantiation
As we showed in the standard JavaScript section, we instantiate here a Fief client here with the base tenant URL and client credentials.
-
Implement a user information cache
To make sure we don't call the Fief API every time we want the user data, we'll cache it in our application. It'll be way more performant!
To do this, we impement a class following the
IUserInfoCache
interface.In this example, we use very simple approach that will just store the data in memory. If your server is rebooted, the cache will be lost.
It can work quite well when starting, but you'll probably need more robust approaches in the long run, like writing to a Redis store. The good thing is that you'll only need to change this class when the time comes!
-
Fief helper for Next.js
This is the helper doing the tedious work for you with Next.js.
We'll review here the required parameters. You can have a complete list of parameters in the reference documentation.
-
The Fief client
The first mandatory parameter is the Fief client we just created above.
-
The session cookie name
We pass the constant we defined above.
-
Absolute redirect URI
After the user has succesfully authenticated on Fief, our user will need to be redirected to our application so we can get the access token and set our session.
This constant is an absolute URL to the
/auth-callback
route the Middleware will handle. -
Absolute redirect URI after logout
When logging out, the user is redirected to Fief so the session stored on Fief's side can also be cleared.
After that, Fief will redirect the user to your application. This parameter allows you to set the page where they should be redirected.
Typically, this can be the home page of your application.
-
We use our user information cache
We tell about our user information cache, so the helper can use it to save and retrieve the data.
2. Add the Middleware¶
If not already, create a middleware.ts
module at the root of the project.
import type { NextRequest } from 'next/server';
import { fiefAuth } from './fief'; // (1)!
const authMiddleware = fiefAuth.middleware([ // (2)!
{
matcher: '/private', // (3)!
parameters: {},
},
{
matcher: '/castles/:path*', // (4)!
parameters: {
permissions: ['castles:read'], // (5)!
},
},
]);
export async function middleware(request: NextRequest) { // (6)!
return authMiddleware(request); // (7)!
};
-
Import the
fiefAuth
instance fromfief
moduleThis is the instance of FiefAuth we created in the previous section.
-
Create an instance of the middleware
Using the
fiefAuth.middleware
method, we create an instance of a Next.js Middleware that'll check the authentication on our routes.All it needs is an array of routes, consisting of the elements described below.
-
Match the
/private
pathBy doing this, we tell the middleware to ensure a user is authenticated before accessing the
/private
page.If they're not, they will be automatically redirected to the Fief login page.
-
Match all the routes starting with
/castles
The
matcher
property follows the same syntax as the one supported by Next.js.It means that you can match a single path, a group of paths or a specific pattern.
Here, we match all the routes starting with
/castles
. We'll be sure that the user is authenticated before accessing those routes. -
Require a permission to access
/castles
routesYou can add
AuthenticateRequestParameters
to a path matcher.Here, we require the user to have
castles:read
permission to access those routes.If they don't have the required permission, the middleware will redirect them to the
/forbidden
page. -
Define the Middleware function
This is the standard way to define a Next.js Middleware.
-
Call the
authMiddleware
functionYou can now call the
authMiddleware
function with therequest
object.This function returns a
NextResponse
, so you can very well add your own middleware logic if needed to further customize it.
Don't mix it with default config
matcher
The Next.js Middleware documentation shows you how to filter the execution of your Middleware using path matchers.
When using Fief middleware, it's better to avoid it to make sure the authentication logic is called on the routes you defined.
Indeed, let's imagine you have this configuration:
const authMiddleware = fiefAuth.middleware([
{
matcher: '/private',
parameters: {},
},
]);
// ❌ Don't do this
export const config = {
matcher: ['/another-route'],
}
The authentication middleware will never be called in this configuration, because Next.js will only run the Middleware for /another-route
, not /private
.
3. Add the current-user
API¶
In the /pages/api
directory, create a current-user.ts
module.
import { fiefAuth } from '../../fief'; // (1)!
export default fiefAuth.currentUser(); // (2)!
-
Import the
fiefAuth
instance fromfief
moduleThis is the instance of FiefAuth we created in the first section.
-
Instantiate and export the
currentUser
API routeThanks to the
fiefAuth.currentUser
method, you can generate an API route that'll return the user information of the authenticated user.Nothing more to do!
4. Create a forbidden
page¶
When the user tries to access a page they are not authorized to see, e.g. if they lack a scope or a permission; the Middleware will automatically render the pages/forbidden.tsx
page.
You should implement it to show a useful error message to the user. It can be as simple as the following:
import type { NextPage } from 'next';
const Forbidden: NextPage = () => {
return (
<h2>Sorry, you are not authorized to access this page.</h2>
)
};
export default Forbidden;
5. Prepare your React application¶
On the React side, we'll need to declare a context so the frontend application can store the authentication state and user information in its state. It'll also let us use a set of useful hooks.
Your /pages/_app.tsx
file should look like this:
import { FiefAuthProvider } from '@fief/fief/nextjs/react';
import type { AppContext, AppProps, AppInitialProps } from 'next/app';
import App from 'next/app';
import Header from '../components/Header/Header';
import '../styles/globals.css';
function MyApp({ Component, pageProps }: AppProps) {
return (
<FiefAuthProvider state={{ accessTokenInfo: null, userinfo: null }} currentUserPath="/api/current-user"> {/* (1)! */}
<Header /> {/* (2)! */}
<Component {...pageProps} />
</FiefAuthProvider>
);
};
MyApp.getInitialProps = async (
context: AppContext
): Promise<AppInitialProps> => {
const ctx = await App.getInitialProps(context)
return { ...ctx }
}
export default MyApp;
-
Wrap
Component
withFiefAuthProvider
FiefAuthProvider
will provide the necessary React context so we can use the hooks in our application.The only property required is
currentUserPath
, which should be the path of the current user API we defined in the previous section. -
A simple demo
Header
componentThis is a simple demo component to show you how to use hooks. We'll detail it in the next sections.
In React context, the import path is @fief/fief/nextjs/react
For technical reasons, components and hooks need to be imported from @fief/fief/nextjs/react
, not @fief/fief/nextjs
.
Good to go!¶
Our project is now ready! You can run it with:
If you go to http://localhost:3000, you'll see the index public page.
If you click on the Private page link in the header, you'll be automatically redirected on Fief authentication pages. After a successful login, you'll be taken back to the /private
page.
As you can see, we can show the email address of the current user. This is done quite simply using the provided React hooks:
import { useFiefUserinfo } from '@fief/fief/nextjs/react';
import type { NextPage } from 'next';
const Private: NextPage = () => {
const user = useFiefUserinfo(); // (1)!
return (
<h2>You are authenticated. Your user email is {user?.email}</h2>
)
};
export default Private;
-
useFiefUserinfo
gives you access to the current user informationThe hook
useFiefUserinfo
returns you aFiefUserInfo
object you can use to work with the user data.
Now, let's try to go to the Castles / Index page. If you've not added the correct permission to the user, you'll probably see the Forbidden page:
If you assign the correct permission to the user and authenticate again, you'll see the actual page.
As you can see, we are able to show the list of permissions granted to the user. It can be useful to hide or show parts of the UI depending on those permissions.
You can easily access them using the useFiefAccessTokenInfo
hook.
import { useFiefAccessTokenInfo } from '@fief/fief/nextjs/react';
import type { NextPage } from 'next';
const CastlesIndex: NextPage = () => {
const accessTokenInfo = useFiefAccessTokenInfo();
return (
<>
<h2>You have the following permissions:</h2>
<ul>
{accessTokenInfo?.permissions.map((permission) => <li key={permission}>{permission}</li>)}
</ul>
</>
);
};
export default CastlesIndex;
If you click on Logout, your session will be cleared and you will be redirected to the index page.
Generating links to login, logout and other pages is no different from a standard Next.js links. Just be sure you use the right path!
import { useFiefIsAuthenticated, useFiefUserinfo } from '@fief/fief/nextjs/react';
import Link from 'next/link';
import React from 'react';
const Header: React.FunctionComponent = () => {
const isAuthenticated = useFiefIsAuthenticated(); /* (1)! */
const userinfo = useFiefUserinfo();
return (
<ul>
<li><Link href="/">Public page</Link></li>
<li><Link href="/private">Private page</Link></li>
<li>
Castles
<ul>
<li><Link href="/castles">Index</Link></li>
<li><Link href="/castles/create">Create</Link></li>
</ul>
</li>
<li>
{/* (2)! */}
{!isAuthenticated && <Link href="/login">Login</Link>}
{isAuthenticated && userinfo && (
<div>
<span>{userinfo.email} - </span>
{/* (3)! */}
<Link href="/logout">Logout</Link>
</div>
)}
</li>
</ul>
);
};
export default Header;
-
Use
useFiefIsAuthenticated
hook to know if a user is authenticateduseFiefIsAuthenticated
is very useful to quickly check if there is a valid session and adapt the UI. -
Show a Login link if not authenticated
Simply add a Link component pointing to the
/login
path. This path is automatically handled by the Middleware. -
Show a Logout link if authenticated
Simply add a Link component pointing to the
/logout
path. This path is automatically handled by the Middleware.
Summary¶
Your Next.js project is now ready and can easily control the authentication and permissions of your users! Here are the most important things to remember while developing your app:
- If you want to protect a page, add it to the
authMiddleware
paths inmiddleware.ts
. - If you want to access user information or permissions, use
useFiefUserinfo
anduseFiefAccessTokenInfo
hooks.
If you want to go further and customize more aspects, like the path to login or logout routes, be sure to check the library reference.
Get access token¶
In somes contexts, you might need to call a separate API backend that will expect a valid access token to authenticate the request. For example, this is what we show in the FastAPI example.
In this case, your Next.js took care of authenticating the user and has a valid access token in session. Now, you only need to get this access token and set it as Authorization
header when making requests to your API backend.
With Next.js, you can do this in two contexts:
1. From getServerSideProps
¶
getServerSideProps
is a Next.js feature to fetch data server-side, in a pure SSR approach. It's a good candidate to fetch the data you want to show to the users from your API, like a list of items.
Fief's integration allows you to retrieve the user ID and the access token information server-side using the getUserId
and getAccessTokenInfo
methods.
import { fiefAuth } from '../fief';
export const getServerSideProps: GetServerSideProps = async (context) => {
const userId = await fiefAuth.getUserId(context.req)
const accessTokenInfo = await fiefAuth.getAccessTokenInfo(context.req)
const res = await fetch('https://api.bretagne.duchy', {
headers: {
Authorization: `Bearer ${accessTokenInfo?.access_token}`,
},
});
const data = await res.json()
// Pass data to the page via props
return { props: { data } }
}
2. From React¶
Of course, you might need to call your API from the frontend side as well, typically when the user needs to create or update new data.
For this, you can get the access token with the useFiefAccessTokenInfo
hook.