Next.js: Integrate @logto/next
with Next.js 13 App Router
This tutorial assumes you have created an Application of type "Traditional Web" in Admin Console. If you are not ready, read this before continuing.
Add Logto SDK as a dependencyโ
- npm
- Yarn
- pnpm
npm i @logto/next
yarn add @logto/next
pnpm add @logto/next
Init LogtoClientโ
In the following code snippets, we assume your app is running on http://localhost:3000
.
You can find and copy "App Secret" from application details page in Admin Console:

Import and initialize LogtoClient:
// libraries/logto.ts
import LogtoClient from '@logto/next/edge';
export const logtoClient = new LogtoClient({
appId: '<your-application-id>',
appSecret: '<your-app-secret-copied-from-console>',
endpoint: '<your-logto-endpoint>', // E.g. http://localhost:3001
baseUrl: '<your-nextjs-app-base-url>', // E.g. http://localhost:3000
cookieSecret: 'complex_password_at_least_32_characters_long',
cookieSecure: process.env.NODE_ENV === 'production',
});
Sign inโ
The sign-in flow can be simplified as:
Configure sign-in redirect URIโ
Let's switch to the Application details page of Admin Console in this section. Add a Redirect URI http://localhost:3000/api/logto/sign-in-callback
and click "Save Changes".

Redirect URI is an OAuth 2.0 concept which implies the location should redirect after authentication.
Prepare API routesโ
Prepare API routes to connect with Logto.
Go back to your IDE/editor, use the following code to implement the API routes first:
// app/api/logto/sign-in/route.ts
import { type NextRequest } from 'next/server';
import { logtoClient } from '../../../../libraries/logto';
export const runtime = 'edge';
export async function GET(request: NextRequest) {
return logtoClient.handleSignIn()(request);
}
// app/api/logto/sign-in-callback/route.ts
import { type NextRequest } from 'next/server';
import { logtoClient } from '../../../../libraries/logto';
export const runtime = 'edge';
export async function GET(request: NextRequest) {
return logtoClient.handleSignInCallback()(request);
}
// app/api/logto/sign-out/route.ts
import { type NextRequest } from 'next/server';
import { logtoClient } from '../../../../libraries/logto';
export const runtime = 'edge';
export async function GET(request: NextRequest) {
return logtoClient.handleSignOut()(request);
}
// app/api/logto/user/route.ts
import { type NextRequest } from 'next/server';
import { logtoClient } from '../../../../libraries/logto';
export const runtime = 'edge';
export async function GET(request: NextRequest) {
return logtoClient.handleUser()(request);
}
We created 4 routes:
/api/logto/sign-in
: Sign in with Logto./api/logto/sign-in-callback
: Handle sign-in callback./api/logto/sign-out
: Sign out with Logto./api/logto/user
: Check if user is authenticated with Logto. If yes, return user info.
Implement sign-in buttonโ
We're almost there! In the last step, we will create a sign-in button:
import { useRouter } from 'next/router';
<button onClick={() => window.location.assign('/api/logto/sign-in')}>Sign In</button>;
Now you will be navigated to Logto sign-in page when you click the button.
Get user profileโ
We'll use "async component" to get user profile, check the Data Fetching doc to learn more.
Create getUser
helperโ
// app/api/logto/user/get-user.ts
import { type LogtoContext } from '@logto/next';
import { cookies } from 'next/headers';
// `server-only` guarantees any modules that import code in file
// will never run on the client. Even though this particular api
// doesn't currently use sensitive environment variables, it's
// good practise to add `server-only` preemptively.
// eslint-disable-next-line import/no-unassigned-import
import 'server-only';
import { config } from '../../../../libraries/config';
export async function getUser() {
const response = await fetch(`${config.baseUrl}/api/logto/user`, {
cache: 'no-store',
headers: {
cookie: cookies().toString(),
},
});
if (!response.ok) {
throw new Error('Something went wrong!');
}
// eslint-disable-next-line no-restricted-syntax
const user = (await response.json()) as LogtoContext;
return user;
}
Create an async component to fetchโ
import { getUser } from './api/logto/user/get-user';
const Page = async () => {
const user = await getUser();
console.log(user); // You'll get user profile here.
return (
<div>
<header>
<h1>Hello Logto.</h1>
</header>
</div>
);
};
export default Page;
Protect API resourcesโ
Call logtoClient.getLogtoContext
to get user authentication state.
// pages/api/protected-resource.ts
import { type NextRequest } from 'next/server';
import { logtoClient } from '../../../../libraries/logto-edge';
export const runtime = 'edge';
export async function GET(request: NextRequest) {
const { isAuthenticated, scopes } = await logtoClient.getLogtoContext(request);
if (!isAuthenticated) {
return new Response(JSON.stringify({ message: 'Unauthorized' }), { status: 401 });
}
return new Response(
JSON.stringify({
data: 'this_is_protected_resource',
})
);
}
Sign outโ
Calling /api/logto/sign-out
will clear all the Logto data in memory and cookies if they exist.
After signing out, it'll be great to redirect your user back to your website. Let's add http://localhost:3000
as one of the Post Sign-out URIs in Admin Console (shows under Redirect URIs).
Implement a sign-out buttonโ
<button onClick={() => window.location.assign('/api/logto/sign-out')}>Sign Out</button>
Fetch user informationโ
Logto SDK helps you fetch the user information from the OIDC UserInfo Endpoint.
You can get the user information by calling logtoClient.handleUser({ fetchUserInfo: true })
after signing in.
The user information response will vary based on the scopes used in the LogtoConfig
while initializing the LogtoClient
; and the following table lists the relations between user information and scopes:
Field Name | Type | Required Scope | Notes |
---|---|---|---|
sub | string | openid | The openid scope is added by default. |
name | string | profile | The profile scope is added by default. |
username | string | profile | The profile scope is added by default. |
picture | string | profile | The profile scope is added by default. |
string | email | ||
email_verified | boolean | email | |
phone_number | string | phone | |
phone_number_verified | boolean | phone | |
custom_data | object | custom_data | |
identities | object | identities |