mirror of
https://github.com/logto-io/logto.git
synced 2025-01-06 20:40:08 -05:00
feat(console): add Next.js integration guide in admin console
This commit is contained in:
parent
71b8807404
commit
7d3f94738f
11 changed files with 466 additions and 7 deletions
222
packages/console/src/assets/docs/tutorial/integrate-sdk/next.mdx
Normal file
222
packages/console/src/assets/docs/tutorial/integrate-sdk/next.mdx
Normal file
|
@ -0,0 +1,222 @@
|
|||
import UriInputField from '@mdx/components/UriInputField';
|
||||
import Step from '@mdx/components/Step';
|
||||
import Tabs from '@mdx/components/Tabs';
|
||||
import TabItem from '@mdx/components/TabItem';
|
||||
import Alert from '@/components/Alert';
|
||||
import { generateRandomString } from '@logto/shared';
|
||||
|
||||
<Step
|
||||
title="Add Logto SDK as a dependency"
|
||||
subtitle="Please select your favorite package manager"
|
||||
index={0}
|
||||
activeIndex={props.activeStepIndex}
|
||||
onButtonClick={() => props.onNext(1)}
|
||||
>
|
||||
<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"
|
||||
index={1}
|
||||
activeIndex={props.activeStepIndex}
|
||||
onButtonClick={() => props.onNext(2)}
|
||||
>
|
||||
|
||||
<Alert>
|
||||
In the following steps, we assume your app is running on <code>http://localhost:3000</code>.
|
||||
</Alert>
|
||||
|
||||
Import and initialize LogtoClient:
|
||||
|
||||
<pre>
|
||||
<code className="language-ts">
|
||||
{`// libraries/logto.js
|
||||
import { LogtoProvider, LogtoConfig } from '@logto/next';
|
||||
|
||||
export const logtoClient = new LogtoClient({
|
||||
endpoint: '${props.endpoint}',
|
||||
appId: '${props.appId}',
|
||||
baseUrl: 'http://localhost:3000', // Change to your own base URL
|
||||
cookieSecret: '${generateRandomString(32)}', // Auto-generated 32 digit secret
|
||||
cookieSecure: process.env.NODE_ENV === 'production',
|
||||
});`}
|
||||
</code>
|
||||
</pre>
|
||||
|
||||
</Step>
|
||||
|
||||
<Step
|
||||
title="Sign In"
|
||||
subtitle="3 steps"
|
||||
index={2}
|
||||
activeIndex={props.activeStepIndex}
|
||||
onButtonClick={() => props.onNext(3)}
|
||||
>
|
||||
|
||||
### Configure Redirect URI
|
||||
|
||||
First, let’s enter your redirect URI. E.g. `http://localhost:3000/api/logto/sign-in-callback`.
|
||||
|
||||
<UriInputField appId={props.appId} isSingle={!props.isCompact} name="redirectUris" title="application_details.redirect_uri" />
|
||||
|
||||
### 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:
|
||||
|
||||
<pre>
|
||||
<code className="language-ts">
|
||||
{`// pages/api/logto/[action].ts
|
||||
import { logtoClient } from '../../../libraries/logto';
|
||||
|
||||
export default logtoClient.handleAuthRoutes();`}
|
||||
</code>
|
||||
</pre>
|
||||
|
||||
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 ways"
|
||||
index={3}
|
||||
activeIndex={props.activeStepIndex}
|
||||
onButtonClick={() => props.onNext(4)}
|
||||
>
|
||||
|
||||
### Through API request in the frontend
|
||||
|
||||
You can fetch user info by calling `/api/logto/user`.
|
||||
|
||||
```tsx
|
||||
import { LogtoUser } from '@logto/next';
|
||||
import useSWR from 'swr';
|
||||
|
||||
const Home = () => {
|
||||
const { data } = useSWR<LogtoUser>('/api/logto/user');
|
||||
|
||||
return <div>User ID: {data?.claims?.sub}</div>;
|
||||
};
|
||||
|
||||
export default Profile;
|
||||
```
|
||||
|
||||
Check [this guide](https://swr.vercel.app/docs/getting-started) to learn more about `useSWR`.
|
||||
|
||||
### Through `getServerSideProps` in the backend
|
||||
|
||||
```tsx
|
||||
import { LogtoUser } from '@logto/next';
|
||||
import { logtoClient } from '../libraries/logto';
|
||||
|
||||
type Props = {
|
||||
user: LogtoUser;
|
||||
};
|
||||
|
||||
const Profile = ({ user }: Props) => {
|
||||
return <div>User ID: {user.claims?.sub}</div>;
|
||||
};
|
||||
|
||||
export default Profile;
|
||||
|
||||
export const getServerSideProps = logtoClient.withLogtoSsr(({ request }) => {
|
||||
const { user } = request;
|
||||
|
||||
return {
|
||||
props: { user },
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
Check [Next.js documentation](https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props) for more details on `getServerSideProps`.
|
||||
|
||||
</Step>
|
||||
|
||||
<Step
|
||||
title="Sign Out"
|
||||
subtitle="1 step"
|
||||
index={4}
|
||||
activeIndex={props.activeStepIndex}
|
||||
onButtonClick={() => props.onNext(5)}
|
||||
>
|
||||
|
||||
Calling `.signOut()` 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 appId={props.appId} isSingle={!props.isCompact} name="postLogoutRedirectUris" title="application_details.post_sign_out_redirect_uri" />
|
||||
|
||||
### Implement a sign-out button
|
||||
|
||||
<pre>
|
||||
<code className="language-tsx">
|
||||
{`<button onClick={() => push('/api/logto/sign-out')}>
|
||||
Sign Out
|
||||
</button>`}
|
||||
</code>
|
||||
</pre>
|
||||
|
||||
</Step>
|
||||
|
||||
<Step
|
||||
title="Further readings"
|
||||
subtitle="4 articles"
|
||||
index={5}
|
||||
activeIndex={props.activeStepIndex}
|
||||
buttonText="general.done"
|
||||
buttonType="primary"
|
||||
onButtonClick={props.onComplete}
|
||||
>
|
||||
|
||||
- [Customize sign-in experience](https://docs.logto.io/docs/recipes/customize-sie)
|
||||
- [Enable SMS or email passcode sign-in](https://docs.logto.io/docs/tutorials/get-started/enable-passcode-sign-in)
|
||||
- [Enable social sign-in](https://docs.logto.io/docs/tutorials/get-started/enable-social-sign-in)
|
||||
- [Protect your API](https://docs.logto.io/docs/recipes/protect-your-api)
|
||||
|
||||
</Step>
|
|
@ -0,0 +1,222 @@
|
|||
import UriInputField from '@mdx/components/UriInputField';
|
||||
import Step from '@mdx/components/Step';
|
||||
import Tabs from '@mdx/components/Tabs';
|
||||
import TabItem from '@mdx/components/TabItem';
|
||||
import Alert from '@/components/Alert';
|
||||
import { generateRandomString } from '@logto/shared';
|
||||
|
||||
<Step
|
||||
title="将 Logto SDK 添加至依赖"
|
||||
subtitle="选择你熟悉的包管理工具"
|
||||
index={0}
|
||||
activeIndex={props.activeStepIndex}
|
||||
onButtonClick={() => props.onNext(1)}
|
||||
>
|
||||
<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="初始化 LogtoClient"
|
||||
subtitle="共 1 步"
|
||||
index={1}
|
||||
activeIndex={props.activeStepIndex}
|
||||
onButtonClick={() => props.onNext(2)}
|
||||
>
|
||||
|
||||
<Alert>
|
||||
在如下代码示例中, 我们均先假设你的 React 应用运行在 <code>http://localhost:3000</code> 上。
|
||||
</Alert>
|
||||
|
||||
引入并实例化 LogtoClient:
|
||||
|
||||
<pre>
|
||||
<code className="language-ts">
|
||||
{`// libraries/logto.js
|
||||
import { LogtoProvider, LogtoConfig } from '@logto/next';
|
||||
|
||||
export const logtoClient = new LogtoClient({
|
||||
endpoint: '${props.endpoint}',
|
||||
appId: '${props.appId}',
|
||||
baseUrl: 'http://localhost:3000', // 你可以修改为自己真实的 URL
|
||||
cookieSecret: '${generateRandomString(32)}', // Logto 自动帮你生成的 32 位密钥
|
||||
cookieSecure: process.env.NODE_ENV === 'production',
|
||||
});`}
|
||||
</code>
|
||||
</pre>
|
||||
|
||||
</Step>
|
||||
|
||||
<Step
|
||||
title="登录"
|
||||
subtitle="共 3 步"
|
||||
index={2}
|
||||
activeIndex={props.activeStepIndex}
|
||||
onButtonClick={() => props.onNext(3)}
|
||||
>
|
||||
|
||||
### 配置 Redirect URI
|
||||
|
||||
首先,我们来添加 Redirect URI,如:`http://localhost:3000/api/logto/sign-in-callback`.
|
||||
|
||||
<UriInputField appId={props.appId} isSingle={!props.isCompact} name="redirectUris" title="application_details.redirect_uri" />
|
||||
|
||||
### 准备 API 路由
|
||||
|
||||
实现与 Logto 后台交互的 [API 路由](https://nextjs.org/docs/api-routes/introduction)。
|
||||
|
||||
返回你的 IDE 或编辑器,首先让我们使用如下代码来实现一组 API 路由:
|
||||
|
||||
<pre>
|
||||
<code className="language-ts">
|
||||
{`// pages/api/logto/[action].ts
|
||||
import { logtoClient } from '../../../libraries/logto';
|
||||
|
||||
export default logtoClient.handleAuthRoutes();`}
|
||||
</code>
|
||||
</pre>
|
||||
|
||||
这将为你自动创建好 4 个路由,分别是:
|
||||
|
||||
1. `/api/logto/sign-in`:登录
|
||||
2. `/api/logto/sign-in-callback`:处理登录重定向
|
||||
3. `/api/logto/sign-out`:登出
|
||||
4. `/api/logto/user`:检查用户是否以登录到 Logto。如果是,则返回用户信息。
|
||||
|
||||
### 实现登录按钮
|
||||
|
||||
马上就要大功告成!在这最后一步,我们将用如下代码实现一个登录按钮:
|
||||
|
||||
```tsx
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
const { push } = useRouter();
|
||||
|
||||
<button onClick={() => push('/api/logto/sign-in')}>
|
||||
登录
|
||||
</button>
|
||||
```
|
||||
|
||||
现在你可以尝试点击登录按钮了,点击之后页面会跳转到 Logto 的登录界面。
|
||||
|
||||
</Step>
|
||||
|
||||
<Step
|
||||
title="获取用户信息"
|
||||
subtitle="两种方式"
|
||||
index={3}
|
||||
activeIndex={props.activeStepIndex}
|
||||
onButtonClick={() => props.onNext(4)}
|
||||
>
|
||||
|
||||
### 通过前端发送 API 请求获取
|
||||
|
||||
你可以调用 `/api/logto/user` 接口来获取用户信息,如:
|
||||
|
||||
```tsx
|
||||
import { LogtoUser } from '@logto/next';
|
||||
import useSWR from 'swr';
|
||||
|
||||
const Home = () => {
|
||||
const { data } = useSWR<LogtoUser>('/api/logto/user');
|
||||
|
||||
return <div>用户 ID:{data?.claims?.sub}</div>;
|
||||
};
|
||||
|
||||
export default Profile;
|
||||
```
|
||||
|
||||
你可以查看 [这篇教程](https://swr.vercel.app/docs/getting-started) 来了解有关 `useSWR` 的更多信息。
|
||||
|
||||
### 通过后端的 `getServerSideProps` 方法获取
|
||||
|
||||
```tsx
|
||||
import { LogtoUser } from '@logto/next';
|
||||
import { logtoClient } from '../libraries/logto';
|
||||
|
||||
type Props = {
|
||||
user: LogtoUser;
|
||||
};
|
||||
|
||||
const Profile = ({ user }: Props) => {
|
||||
return <div>用户 ID:{user.claims?.sub}</div>;
|
||||
};
|
||||
|
||||
export default Profile;
|
||||
|
||||
export const getServerSideProps = logtoClient.withLogtoSsr(({ request }) => {
|
||||
const { user } = request;
|
||||
|
||||
return {
|
||||
props: { user },
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
查看 [Next.js 官方文档](https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props) 来了解有关 `getServerSideProps` 的更多信息。
|
||||
|
||||
</Step>
|
||||
|
||||
<Step
|
||||
title="退出登录"
|
||||
subtitle="共 1 步"
|
||||
index={4}
|
||||
activeIndex={props.activeStepIndex}
|
||||
onButtonClick={() => props.onNext(5)}
|
||||
>
|
||||
|
||||
调用 `.signOut()` 将清理内存与 cookies 中的所有 Logto 数据(如果有)。
|
||||
|
||||
在退出登录后,让你的用户重新回到你的网站是个不错的选择。在调用 `api/logto/sign-out` 发起退出登录操作之前,让我们先将 `http://localhost:3000` 添加至下面的输入框。
|
||||
|
||||
<UriInputField appId={props.appId} isSingle={!props.isCompact} name="postLogoutRedirectUris" title="application_details.post_sign_out_redirect_uri" />
|
||||
|
||||
### 实现退出登录按钮
|
||||
|
||||
<pre>
|
||||
<code className="language-tsx">
|
||||
{`<button onClick={() => push('/api/logto/sign-out')}>
|
||||
退出登录
|
||||
</button>`}
|
||||
</code>
|
||||
</pre>
|
||||
|
||||
</Step>
|
||||
|
||||
<Step
|
||||
title="延展阅读"
|
||||
subtitle="共 4 篇"
|
||||
index={5}
|
||||
activeIndex={props.activeStepIndex}
|
||||
buttonText="general.done"
|
||||
buttonType="primary"
|
||||
onButtonClick={props.onComplete}
|
||||
>
|
||||
|
||||
- [自定义登录体验](https://docs.logto.io/zh-cn/docs/recipes/customize-sie)
|
||||
- [启用短信或邮件验证码登录](https://docs.logto.io/zh-cn/docs/tutorials/get-started/enable-passcode-sign-in)
|
||||
- [启用社交登录](https://docs.logto.io/zh-cn/docs/tutorials/get-started/enable-social-sign-in)
|
||||
- [保护你的 API](https://docs.logto.io/zh-cn/docs/recipes/protect-your-api)
|
||||
|
||||
</Step>
|
|
@ -26,6 +26,7 @@ const Guides: Record<string, LazyExoticComponent<(props: MDXProps) => JSX.Elemen
|
|||
vue: lazy(async () => import('@/assets/docs/tutorial/integrate-sdk/vue.mdx')),
|
||||
vanilla: lazy(async () => import('@/assets/docs/tutorial/integrate-sdk/vanilla.mdx')),
|
||||
express: lazy(async () => import('@/assets/docs/tutorial/integrate-sdk/express.mdx')),
|
||||
next: lazy(async () => import('@/assets/docs/tutorial/integrate-sdk/next.mdx')),
|
||||
'ios_zh-cn': lazy(async () => import('@/assets/docs/tutorial/integrate-sdk/ios_zh-cn.mdx')),
|
||||
'android_zh-cn': lazy(
|
||||
async () => import('@/assets/docs/tutorial/integrate-sdk/android_zh-cn.mdx')
|
||||
|
@ -38,6 +39,7 @@ const Guides: Record<string, LazyExoticComponent<(props: MDXProps) => JSX.Elemen
|
|||
'express_zh-cn': lazy(
|
||||
async () => import('@/assets/docs/tutorial/integrate-sdk/express_zh-cn.mdx')
|
||||
),
|
||||
'next_zh-cn': lazy(async () => import('@/assets/docs/tutorial/integrate-sdk/next_zh-cn.mdx')),
|
||||
};
|
||||
|
||||
const Guide = ({ app, isCompact, onClose }: Props) => {
|
||||
|
|
|
@ -34,6 +34,8 @@ const getSampleProjectUrl = (sdk: SupportedSdk) => {
|
|||
return `${githubUrlPrefix}/js/tree/master/packages/vue-sample`;
|
||||
case SupportedSdk.Vanilla:
|
||||
return `${githubUrlPrefix}/js/tree/master/packages/browser-sample`;
|
||||
case SupportedSdk.Next:
|
||||
return `${githubUrlPrefix}/js/tree/master/packages/next-sample`;
|
||||
case SupportedSdk.Express:
|
||||
return `${githubUrlPrefix}/express-example`;
|
||||
default:
|
||||
|
|
|
@ -13,10 +13,11 @@ export enum SupportedSdk {
|
|||
Vue = 'Vue',
|
||||
Vanilla = 'Vanilla',
|
||||
Express = 'Express',
|
||||
Next = 'Next',
|
||||
}
|
||||
|
||||
export const applicationTypeAndSdkTypeMappings = Object.freeze({
|
||||
[ApplicationType.Native]: [SupportedSdk.iOS, SupportedSdk.Android],
|
||||
[ApplicationType.SPA]: [SupportedSdk.React, SupportedSdk.Vue, SupportedSdk.Vanilla],
|
||||
[ApplicationType.Traditional]: [SupportedSdk.Express],
|
||||
[ApplicationType.Traditional]: [SupportedSdk.Next, SupportedSdk.Express],
|
||||
} as const);
|
||||
|
|
|
@ -26,7 +26,7 @@ const applications = {
|
|||
traditional: {
|
||||
title: 'Traditional Web',
|
||||
subtitle: 'An app that renders and updates pages by the web server alone',
|
||||
description: 'E.g., JSP, PHP',
|
||||
description: 'E.g., Next.js, PHP',
|
||||
},
|
||||
},
|
||||
guide: {
|
||||
|
|
|
@ -24,7 +24,7 @@ const applications = {
|
|||
traditional: {
|
||||
title: '传统网页应用',
|
||||
subtitle: '仅由 Web 服务器渲染和更新的应用程序',
|
||||
description: '例如 JSP, PHP',
|
||||
description: '例如 Next.js, PHP',
|
||||
},
|
||||
},
|
||||
guide: {
|
||||
|
|
|
@ -21,6 +21,10 @@
|
|||
"engines": {
|
||||
"node": "^16.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"color": "^4.2.3",
|
||||
"nanoid": "^3.1.23"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@silverhand/eslint-config": "^0.17.0",
|
||||
"@silverhand/eslint-config-react": "^0.17.0",
|
||||
|
@ -42,8 +46,5 @@
|
|||
"stylelint": {
|
||||
"extends": "@silverhand/eslint-config-react/.stylelintrc"
|
||||
},
|
||||
"prettier": "@silverhand/eslint-config/.prettierrc",
|
||||
"dependencies": {
|
||||
"color": "^4.2.3"
|
||||
}
|
||||
"prettier": "@silverhand/eslint-config/.prettierrc"
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export * from './file';
|
||||
export * from './react-router';
|
||||
export * from './color';
|
||||
export * from './string';
|
||||
|
|
6
packages/shared/src/utilities/string.ts
Normal file
6
packages/shared/src/utilities/string.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
import { customAlphabet } from 'nanoid';
|
||||
|
||||
export const generateRandomString = (
|
||||
size: number,
|
||||
alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
|
||||
): string => customAlphabet(alphabet, size)();
|
|
@ -1273,12 +1273,14 @@ importers:
|
|||
color: ^4.2.3
|
||||
eslint: ^8.19.0
|
||||
lint-staged: ^13.0.0
|
||||
nanoid: ^3.1.23
|
||||
postcss: ^8.4.6
|
||||
prettier: ^2.3.2
|
||||
stylelint: ^14.8.2
|
||||
typescript: ^4.6.2
|
||||
dependencies:
|
||||
color: 4.2.3
|
||||
nanoid: 3.3.4
|
||||
devDependencies:
|
||||
'@silverhand/eslint-config': 0.17.0_udveqfgjtporakzcjlry75cuqi
|
||||
'@silverhand/eslint-config-react': 0.17.0_ooy3j52eylrixuekzgeaykdz54
|
||||
|
|
Loading…
Reference in a new issue