0
Fork 0
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:
wangsijie 2023-08-17 11:36:26 +08:00 committed by GitHub
parent e1dada4d78
commit f0521fb36d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 280 additions and 3 deletions

View file

@ -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, lets 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>

View file

@ -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',
},
});