0
Fork 0
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:
Charles Zhao 2022-07-29 23:36:02 +08:00
parent 71b8807404
commit 7d3f94738f
No known key found for this signature in database
GPG key ID: 4858774754C92DF2
11 changed files with 466 additions and 7 deletions

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

View 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="将 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>

View file

@ -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) => {

View file

@ -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:

View file

@ -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);

View file

@ -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: {

View file

@ -24,7 +24,7 @@ const applications = {
traditional: {
title: '传统网页应用',
subtitle: '仅由 Web 服务器渲染和更新的应用程序',
description: '例如 JSP, PHP',
description: '例如 Next.js, PHP',
},
},
guide: {

View file

@ -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"
}

View file

@ -1,3 +1,4 @@
export * from './file';
export * from './react-router';
export * from './color';
export * from './string';

View file

@ -0,0 +1,6 @@
import { customAlphabet } from 'nanoid';
export const generateRandomString = (
size: number,
alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
): string => customAlphabet(alphabet, size)();

View file

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