mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
feat(console): nextjs app router guide (#4346)
* feat(console): nextjs app router guide * fix(console): fix app router description * fix: remove further reading * fix: add trailing steps closer
This commit is contained in:
parent
e1dada4d78
commit
f0521fb36d
2 changed files with 280 additions and 3 deletions
|
@ -1 +1,278 @@
|
|||
## Replace this with actual guide
|
||||
import UriInputField from '@/mdx-components-v2/UriInputField';
|
||||
import Tabs from '@mdx/components/Tabs';
|
||||
import TabItem from '@mdx/components/TabItem';
|
||||
import InlineNotification from '@/ds-components/InlineNotification';
|
||||
import { buildIdGenerator } from '@logto/shared/universal';
|
||||
import Steps from '@/mdx-components-v2/Steps';
|
||||
import Step from '@/mdx-components-v2/Step';
|
||||
|
||||
<Steps>
|
||||
|
||||
<Step
|
||||
title="Add Logto SDK as a dependency"
|
||||
subtitle="Please select your favorite package manager"
|
||||
>
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="npm">
|
||||
|
||||
```bash
|
||||
npm i @logto/next
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```bash
|
||||
yarn add @logto/next
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="pnpm" label="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm add @logto/next
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
</Step>
|
||||
|
||||
<Step
|
||||
title="Init LogtoClient"
|
||||
subtitle="1 step"
|
||||
>
|
||||
|
||||
<InlineNotification>
|
||||
In the following steps, we assume your app is running on <code>http://localhost:3000</code>.
|
||||
</InlineNotification>
|
||||
|
||||
Import and initialize LogtoClient:
|
||||
|
||||
<pre>
|
||||
<code className="language-ts">
|
||||
{`// libraries/logto.js
|
||||
import LogtoClient from '@logto/next/edge';
|
||||
|
||||
export const logtoClient = new LogtoClient({
|
||||
endpoint: '${props.endpoint}',${
|
||||
props.alternativeEndpoint ? ` // or "${props.alternativeEndpoint}"` : ''
|
||||
}
|
||||
appId: '${props.app.id}',
|
||||
appSecret: '${props.app.secret}',
|
||||
baseUrl: 'http://localhost:3000', // Change to your own base URL
|
||||
cookieSecret: '${buildIdGenerator(32)()}', // Auto-generated 32 digit secret
|
||||
cookieSecure: process.env.NODE_ENV === 'production',
|
||||
});`}
|
||||
</code>
|
||||
</pre>
|
||||
|
||||
</Step>
|
||||
|
||||
<Step
|
||||
title="Sign in"
|
||||
subtitle="3 steps, 4 API routes"
|
||||
>
|
||||
|
||||
### Configure Redirect URI
|
||||
|
||||
First, let’s enter your redirect URI. E.g. `http://localhost:3000/api/logto/sign-in-callback`.
|
||||
|
||||
<UriInputField name="redirectUris" />
|
||||
|
||||
### Prepare API routes
|
||||
|
||||
Prepare [API routes](https://nextjs.org/docs/api-routes/introduction) to connect with Logto.
|
||||
|
||||
Go back to your IDE/editor, use the following code to implement the API routes first:
|
||||
|
||||
```ts
|
||||
// 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);
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
// 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);
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
// 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);
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
// 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);
|
||||
}
|
||||
```
|
||||
|
||||
This will create 4 routes automatically:
|
||||
|
||||
1. `/api/logto/sign-in`: Sign in with Logto.
|
||||
2. `/api/logto/sign-in-callback`: Handle sign-in callback.
|
||||
3. `/api/logto/sign-out`: Sign out with Logto.
|
||||
4. `/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:
|
||||
|
||||
```tsx
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
const { push } = useRouter();
|
||||
|
||||
<button onClick={() => push('/api/logto/sign-in')}>Sign In</button>;
|
||||
```
|
||||
|
||||
Now you will be navigated to Logto sign-in page when you click the button.
|
||||
|
||||
</Step>
|
||||
|
||||
<Step
|
||||
title="Get user profile"
|
||||
subtitle="2 steps"
|
||||
>
|
||||
|
||||
We'll use "async component" to get user profile, check the [Data Fetching](https://nextjs.org/docs/app/building-your-application/data-fetching) doc to learn more.
|
||||
|
||||
### Create `getUser` helper
|
||||
|
||||
```tsx
|
||||
// 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
|
||||
|
||||
```tsx
|
||||
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;
|
||||
```
|
||||
|
||||
</Step>
|
||||
|
||||
<Step
|
||||
title="Protect API resources"
|
||||
>
|
||||
|
||||
Call `logtoClient.getLogtoContext` to get user authentication state.
|
||||
|
||||
```ts
|
||||
// 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',
|
||||
})
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
</Step>
|
||||
|
||||
<Step
|
||||
title="Sign out"
|
||||
subtitle="1 step"
|
||||
>
|
||||
|
||||
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 user back to your website. Let's add `http://localhost:3000` as the Post Sign-out URI below before calling `/api/logto/sign-out`.
|
||||
|
||||
<UriInputField name="postLogoutRedirectUris" />
|
||||
|
||||
### Implement a sign-out button
|
||||
|
||||
```tsx
|
||||
<button onClick={() => push('/api/logto/sign-out')}>Sign Out</button>
|
||||
```
|
||||
|
||||
</Step>
|
||||
|
||||
</Steps>
|
||||
|
|
|
@ -5,11 +5,11 @@ import { type GuideMetadata } from '../types';
|
|||
const metadata: Readonly<GuideMetadata> = Object.freeze({
|
||||
name: 'Next.js (App Router)',
|
||||
description:
|
||||
'Next.js (App Router) is a React framework for production - it makes building fullstack React apps and sites a breeze and ships with built-in SSR.',
|
||||
"Next.js is a full stack React SSR framework for production, the App Router is a new paradigm for building applications using React's latest features.",
|
||||
target: ApplicationType.Traditional,
|
||||
sample: {
|
||||
repo: 'js',
|
||||
path: 'packages/next-sample',
|
||||
path: 'packages/next-app-dir-sample',
|
||||
},
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in a new issue