Hosted Login Integration (Next.js)
Important Notes and Prerequisites
- Frontegg Next.js integration is dependent on SSR (Server Side Rendering). Consequently, Frontegg hooks will not work with Static Site Generation (SSG).
- Required minimum versions:
Next.js >= 12
Typescript >= 3.9.7
frontegg/nextjs >= 7.0.0- If you are using @frontegg/nextjs =< 7.0.0, please see the migration guide here
This guide will seamlessly walk you through integrating Frontegg's login box into your Next.js application, with robust functionalities including Sign In, Sign Up, and Single Sign-On (SSO). Streamline your authentication process with just a few lines of code, leveraging the power of redirects, OpenID Connect, and OAuth2.
⚡ Before you start: ⚡
Getting your Frontegg subdomain and clientId
Frontegg creates a unique
subdomain
andclient id
for every environment created on the account. In order to retrieve theclientId
subdomain
that will act as theFRONTEGG_BASE_URL
in the integration, navigate to your environment 'Settings' menu, copy theFrontegg domain
andclientId
and use them in .env.local file.
STEP 1: Create Frontegg Next.js sample app.
If you have an existing app with Next.js app, skip this step.
To create a new app based on our sample or Next.js template or a new Next.js app, run the below commands.
npx create-next-app my-nextjs-app-name
// Next.js with experimental app directory
npx create-next-app --experimental-app my-nextjs-app-name
cd my-nextjs-app-name
// Next.js 13 will be installed by default
yarn create next-app my-nextjs-app-name
// Next.js with experimental app directory
yarn create-next-app --experimental-app my-nextjs-app-name
cd my-nextjs-app-name
STEP 2: Install frontegg/nextjs
frontegg/nextjs
Run the following command to install @frontegg/nextjs
.
npm install @frontegg/nextjs@latest
yarn add @frontegg/nextjs@latest
Note - the files in this guide are in typescript - if you are working in js, simply change the file types and remove the typing
STEP 3: Configure the App file, Router, Middleware files
When integrating Next.js application with Frontegg, Next.js custom app and several files should be added. Make sure that these exist in the project you cloned from our sample or created.
Next.js pages
Architecture
pages
ArchitectureCreate custom Next.js files under the pages
directory and paste the snippets below:
Existing custom Next.js app
If you have a custom app already (the file
_app.tsx
exists), wrap and replace the default export component withwithFronteggApp
function and pass your original component.
import { withFronteggApp } from "@frontegg/nextjs/pages";
function CustomApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}
export default withFronteggApp(CustomApp, {
hostedLoginBox: true,
authOptions: {
// keepSessionAlive: true, // Uncomment this in order to maintain the session alive
},
});
import { FronteggApiMiddleware } from '@frontegg/nextjs/middleware';
export default FronteggApiMiddleware;
export const config = {
api: {
externalResolver: true,
// https://nextjs.org/docs/messages/api-routes-response-size-limit
responseLimit: false,
},
};
import { FronteggRouter, FronteggRouterProps } from '@frontegg/nextjs/pages';
export const getServerSideProps = FronteggRouterProps;
export default FronteggRouter;
Next.js appDir
Architecture
appDir
ArchitectureCreate custom Next.js files under app
directory and paste the snippets below. The ./pages/api/frontegg/[...frontegg-middleware].ts
file should stay in the pages
directory:
import { FronteggAppProvider } from '@frontegg/nextjs/app';
export default function RootLayout({ children }: { children: React.ReactNode }) {
const authOptions = {
keepSessionAlive: true // Uncomment this in order to maintain the session alive
}
return (
<html>
<head></head>
<body>
{/* @ts-expect-error Server Component for more details visit: https://github.com/vercel/next.js/issues/42292 */}
<FronteggAppProvider authOptions={authOptions} hostedLoginBox={true}>
{children}
</FronteggAppProvider>
</body>
</html>
);
}
import { FronteggAppRouter } from '@frontegg/nextjs/app';
export default FronteggAppRouter;
import { FronteggApiMiddleware } from '@frontegg/nextjs/middleware';
export default FronteggApiMiddleware;
export const config = {
api: {
externalResolver: true,
// https://nextjs.org/docs/messages/api-routes-response-size-limit
responseLimit: false,
},
};
"use client";
import { useAuth, useLoginWithRedirect } from "@frontegg/nextjs";//, useLoginWithRedirect
import { AdminPortal } from '@frontegg/nextjs'
import { useRouter } from 'next/navigation';
export const ClientComponent = () => {
const { user, isAuthenticated } = useAuth();
const loginWithRedirect = useLoginWithRedirect();
const router = useRouter();
const logout = useCallback(() => {
router.replace('/account/logout');
}, [router]);
return (
<div className="App">
{isAuthenticated ? (
<div>
<div>
<img src={user?.profilePictureUrl} alt={user?.name} />
</div>
<div>
<span>Logged in as: {user?.name}</span>
</div>
<div>
<button onClick={() => alert(user?.accessToken)}>
What is my access token?
</button>
</div>
<div>
<button onClick={() => logout()}>Click to logout</button>
</div>
<div>
<button onClick={handleClick}>Settings</button>
</div>
</div>
) : (
<><></><div>
<button onClick={() => loginWithRedirect()}>Click me to login</button>
</div></>
)}
</div>
);
};
import { getAppUserSession, getAppUserTokens } from '@frontegg/nextjs/app';
export const ServerSession = async () => {
const userSession = await getAppUserSession();
const tokens = await getAppUserTokens();
return (
<div>
<div>user session server side: {JSON.stringify(userSession)}</div>;
<div>user tokens server side: {JSON.stringify(tokens)}</div>
</div>
);
};
import { ClientComponent } from "./ClientComponent";
import { ServerSession } from "./ServerComponent";
export default function MainPage() {
return (
<div>
<h3>Next JS application with frontegg</h3>
{/* @ts-expect-error Server Component for more details visit: https://github.com/vercel/next.js/issues/42292 */}
<ServerSession />
<ClientComponent/>
</div>
);
}
File Names
The locations and the file names should be exactly as written on the file names, including the brackets and the three dots.
For
pages
directory -./pages/[...frontegg-router].tsx
,./pages/api/frontegg/[...frontegg-middleware].ts
and./pages/_app.tsx
.For
app
directory -./app/layout.tsx
,./app/[...frontegg-router]/page.tsx
,./app/ServerComponent.tsx
,app/ClientComponent.tsx
.
STEP 4 Setup Environment
To setup your Next.js application to communicate with Frontegg, you have to create a new file named .env.local
under your root project directory, this file will be used to store environment variables that will be used, configuration options:
# The AppUrl you set during integration - this is to tell Frontegg your application hostname
FRONTEGG_APP_URL='http://localhost:3000'
# The Frontegg domain is your unique URL to connect to the Frontegg gateway
FRONTEGG_BASE_URL='https://[YOUR_SUBDOMAIN].frontegg.com'
# Your Frontegg application's Client ID
FRONTEGG_CLIENT_ID='[YOUR_CLIENT_ID]'
# The statless session encruption password, used to encrypt
# jwt before sending it to the client side.
#
# For quick password generation use the following command:
# node -e "console.log(crypto.randomBytes(32).toString('hex'))"
FRONTEGG_ENCRYPTION_PASSWORD='[64_CHAR_SESSION_ENCRYPTION_PASSWORD]'
# The statless session cookie name - you should not change this
FRONTEGG_COOKIE_NAME='fe_session'
# By default on each refresh of the page the next middleware will try to refresh the access token even if it’s still valid. The below option allows to disable that behavior and re-use the access token and thus to improve refresh times
# Only for pages directory.
DISABLE_INITIAL_PROPS_REFRESH_TOKEN=true
FRONTEGG_HOSTED_LOGIN='true`
# For printing verbose log messages in regards to nextjs middleware activity
#FRONTEGG_LOG_LEVEL="debug"
# For improving nextjs perfomrance by providing environment public key to the nextjs middleware.
#FRONTEGG_JWT_PUBLIC_KEY='{"kty":"RSA", "kid":"xxx", "use":"sig", "alg":"RS256", "n":"xxxx", "e":"xxx"}'
Remember to replace the relevant fields in this file with your personal information!
STEP 5 (optional) NextRequest
NextRequest
Support for NextJS middleware is available from @frontegg/nextjs v6.4.0
To prevent access of an unauthenticated user to all routes, use Next.js middlewares.
NOTE: If you were using Middleware prior to 12.2, please see the upgrade guide.
Note
It is recommended to check for the user's session either on the
middleware.ts
or per each page. It is not recommended to use several sources for getting or checking for a user's session.Frontegg uses
getInitialProps
in the Next.js wrapper, therefore, there is no need to call defaultgetInitialProps
and instead usecontext
. Frontegg wrapper holds this data already.
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { getSessionOnEdge, shouldByPassMiddleware, redirectToLogin } from '@frontegg/nextjs/edge';
export const middleware = async (request: NextRequest) => {
const pathname = request.nextUrl.pathname;
if (shouldByPassMiddleware(pathname)) {
return NextResponse.next();
}
const session = await getSessionOnEdge(request);
if (!session) {
return redirectToLogin(pathname);
}
return NextResponse.next();
};
export const config = {
matcher: '/(.*)',
};
When to use
shouldByPassMiddleware
- To protect all application routes.
- Static files and image requests.
The default whitelists:
_next/static
(static files)_next/image
(image optimization files)favicon.icon
(favicon file)api/frontegg
(API frontegg middleware)account/[login|logout|saml/callback|...]
(Frontegg authentication routes)You can override the whitelist by passing the
options
parameterNOTE: this will slow down your application due to session checks.
STEP 6: Redirect to login from a page
Alternatively, you can use the Frontegg withSSRSession
on each page, to redirect users to the login screen if they are not authenticated.
import { useCallback } from "react";
import { GetServerSideProps } from "next";
import { getSession } from "@frontegg/nextjs/pages";
import { useAuth } from "@frontegg/nextjs";
import { useRouter } from "next/router"
export default function MyPage({ products }) {
const { user } = useAuth();
const router = useRouter();
const logout = useCallback(() => {
router.replace('/account/logout');
}, [router]);
return (
<div>
<h1>My Page</h1>
{products}
<div>
<img src={user?.profilePictureUrl} alt={user?.name} />
</div>
<div>
<span>Logged in as: {user?.name}</span>
</div>
<div>
<button onClick={logout}>Log out</button>
</div>
</div>
);
}
// In the `getServerSideProps` method you can get data from an external service to pull relevant data for a logged in user.
// we used the prop `products`. See the commented code for an example.
export const getServerSideProps: GetServerSideProps = withSSRSession(
async (context, session) => {
// const { data } = await fetch('{external}/product', {
// headers: {
// Authorization: 'bearer ' + session.accessToken,
// },
// });
return { props: {} };
}
);
STEP 7 (optional) getSession
getSession
As an extension of step 6, for any page that requires an AccessToken
on the server side (e.g. you'd like to load the data only if a user is logged in), you can use the getSession
method. Remember to replace external
with the link to your external service.
import { GetServerSideProps } from 'next';
import { getSession } from '@frontegg/nextjs/pages';
export default function MyPage({ products }) {
return (
<div>
<h1>My Page</h1>
{products}
</div>
);
}
export const getServerSideProps: GetServerSideProps = async (context) => {
const session = await getSession(context.req);
if (session) {
const { data } = await fetch('{external}/product', {
headers: {
Authorization: 'bearer ' + session.accessToken,
},
});
return { props: { products: data } };
}
return { props: { products: [] } };
};
STEP 8 (optional) customLoader
customLoader
import "../styles/globals.css";
export default withFronteggApp(CustomApp,
{
hostedLoginBox: true,
customLoader: true,
authOptions: {
// keepSessionAlive: true // Uncomment this in order to maintain the session alive
}
});
Add _document.tsx
file under the pages folder.
import { Html, Head, Main, NextScript } from 'next/document';
export default function Document() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
<div className="custom-loader"> I am a custom loader!</div>
</body>
</Html>
);
}
Add the following styles to styles/globals.css:
.custom-loader {
width: 100vw;
height: 100vh;
background: red;
display: none;
position: fixed;
top: 0;
left: 0;
text-align: center;
justify-content: center;
align-items: center;
font-size: 50px;
color: white;
}
body.frontegg-loading .custom-loader {
display: flex;
}
STEP 9: Verify Redirect URL
On the Frontegg portal, verify under the hosted login configuration, http://localhost:3000/oauth/callback
is listed as the allowed redirectUrl
.
Preventing auto-refresh of access token
The middleware is set (by default) to refresh the access token even if it’s still valid. By using the following code, you can disable that behavior in your pages directory and re-use the access token while still valid:
DISABLE_INITIAL_PROPS_REFRESH_TOKEN=true
Updated about 2 months ago