mirror of
https://github.com/logto-io/logto.git
synced 2024-12-30 20:33:54 -05:00
refactor(console): update next guide (#6119)
This commit is contained in:
parent
bd0487ecc3
commit
211c3576d7
3 changed files with 107 additions and 243 deletions
|
@ -26,7 +26,7 @@ To learn more about the rationale and benefits of redirect-based sign-in, see [L
|
||||||
In the following steps, we assume your app is running on <code>http://localhost:3000</code>.
|
In the following steps, we assume your app is running on <code>http://localhost:3000</code>.
|
||||||
</InlineNotification>
|
</InlineNotification>
|
||||||
|
|
||||||
Now, let's enter your redirect URI. E.g. `http://localhost:3000/callback`.
|
Now, let's enter your redirect URI. E.g. {`${props.callbackUri ?? 'http://localhost:3000/callback'}`}.
|
||||||
|
|
||||||
<UriInputField name="redirectUris" />
|
<UriInputField name="redirectUris" />
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,18 @@
|
||||||
import UriInputField from '@/mdx-components/UriInputField';
|
|
||||||
import Tabs from '@mdx/components/Tabs';
|
import Tabs from '@mdx/components/Tabs';
|
||||||
import TabItem from '@mdx/components/TabItem';
|
import TabItem from '@mdx/components/TabItem';
|
||||||
import InlineNotification from '@/ds-components/InlineNotification';
|
|
||||||
import { generateStandardSecret } from '@logto/shared/universal';
|
import { generateStandardSecret } from '@logto/shared/universal';
|
||||||
import Steps from '@/mdx-components/Steps';
|
import Steps from '@/mdx-components/Steps';
|
||||||
import Step from '@/mdx-components/Step';
|
import Step from '@/mdx-components/Step';
|
||||||
|
import Checkpoint from '../../fragments/_checkpoint.md';
|
||||||
|
import RedirectUris from '../../fragments/_redirect_uris.mdx';
|
||||||
|
|
||||||
<Steps>
|
<Steps>
|
||||||
|
|
||||||
<Step
|
<Step
|
||||||
title="Add Logto SDK as a dependency"
|
title="Installation"
|
||||||
subtitle="Please select your favorite package manager"
|
subtitle="Install Logto SDK"
|
||||||
>
|
>
|
||||||
|
|
||||||
<Tabs>
|
<Tabs>
|
||||||
<TabItem value="npm" label="npm">
|
<TabItem value="npm" label="npm">
|
||||||
|
|
||||||
|
@ -41,13 +42,9 @@ pnpm add @logto/next
|
||||||
title="Prepare configs"
|
title="Prepare configs"
|
||||||
>
|
>
|
||||||
|
|
||||||
<InlineNotification>
|
Prepare configuration for the Logto client:
|
||||||
In the following steps, we assume your app is running on <code>http://localhost:3000</code>.
|
|
||||||
</InlineNotification>
|
|
||||||
|
|
||||||
Prepare configuration for the Logto client. Create a new file `app/logto.ts` and add the following code:
|
<Code title="app/logto.ts" className="language-ts">
|
||||||
|
|
||||||
<Code className="language-ts">
|
|
||||||
{`export const logtoConfig = {
|
{`export const logtoConfig = {
|
||||||
endpoint: '${props.endpoint}',
|
endpoint: '${props.endpoint}',
|
||||||
appId: '${props.app.id}',
|
appId: '${props.app.id}',
|
||||||
|
@ -61,21 +58,12 @@ Prepare configuration for the Logto client. Create a new file `app/logto.ts` and
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
<Step
|
<Step
|
||||||
title="Sign in"
|
title="Implement callback route"
|
||||||
>
|
>
|
||||||
|
|
||||||
### Configure Redirect URI
|
Add a callback route to your app:
|
||||||
|
|
||||||
First, let’s enter your redirect URI. E.g. `http://localhost:3000/callback`.
|
```tsx title="/app/callback/route.ts"
|
||||||
|
|
||||||
<UriInputField name="redirectUris" />
|
|
||||||
|
|
||||||
### Implement callback page
|
|
||||||
|
|
||||||
Add a callback page to your app:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// pages/callback/page.tsx
|
|
||||||
import { handleSignIn } from '@logto/next/server-actions';
|
import { handleSignIn } from '@logto/next/server-actions';
|
||||||
import { redirect } from 'next/navigation';
|
import { redirect } from 'next/navigation';
|
||||||
import { NextRequest } from 'next/server';
|
import { NextRequest } from 'next/server';
|
||||||
|
@ -89,12 +77,26 @@ export async function GET(request: NextRequest) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Implement sign-in button
|
</Step>
|
||||||
|
|
||||||
The sign-in button will call the method we just created, it is a client component:
|
<Step
|
||||||
|
title="Configure redirect URIs"
|
||||||
```tsx
|
subtitle="2 URIs"
|
||||||
// app/sign-in.tsx
|
>
|
||||||
|
|
||||||
|
<RedirectUris />
|
||||||
|
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step
|
||||||
|
title="Implement sign-in and sign-out"
|
||||||
|
>
|
||||||
|
|
||||||
|
### Implement sign-in and sign-out button
|
||||||
|
|
||||||
|
In Next.js App Router, events are handled in client components, so we need to create two components first: `SignIn` and `SignOut`.
|
||||||
|
|
||||||
|
```tsx title="/app/sign-in.tsx"
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -116,53 +118,7 @@ const SignIn = ({ onSignIn }: Props) => {
|
||||||
export default SignIn;
|
export default SignIn;
|
||||||
```
|
```
|
||||||
|
|
||||||
### Add sign in button to home page
|
```tsx title="/app/sign-out.tsx"
|
||||||
|
|
||||||
We're almost there! Add this button to home page at `/app/page.tsx` and implement the `onSignIn` function:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { signIn } from '@logto/next/server-actions';
|
|
||||||
import SignIn from './sign-in';
|
|
||||||
import { logtoConfig } from './logto';
|
|
||||||
|
|
||||||
export default async function Home() {
|
|
||||||
return (
|
|
||||||
<main>
|
|
||||||
<h1>Hello Logto.</h1>
|
|
||||||
<div>
|
|
||||||
<SignIn
|
|
||||||
onSignIn={async () => {
|
|
||||||
'use server';
|
|
||||||
|
|
||||||
await signIn(logtoConfig);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Now you will be navigated to Logto sign-in page when you click the button.
|
|
||||||
|
|
||||||
</Step>
|
|
||||||
|
|
||||||
<Step
|
|
||||||
title="Sign out"
|
|
||||||
>
|
|
||||||
|
|
||||||
### Configure URI
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
<UriInputField name="postLogoutRedirectUris" />
|
|
||||||
|
|
||||||
### Implement a sign-out button
|
|
||||||
|
|
||||||
The sign-out button is also a client component, so we will create it in `/app/sign-out.tsx`:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// app/sign-out.tsx
|
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -184,63 +140,26 @@ const SignOut = ({ onSignOut }: Props) => {
|
||||||
export default SignOut;
|
export default SignOut;
|
||||||
```
|
```
|
||||||
|
|
||||||
### Add sign out button to home page
|
Remember to add `'use client'` to the top of the file to indicate that these components are client components.
|
||||||
|
|
||||||
Then add the sign-out button to the home page in `/app/page.tsx`:
|
### Add buttons to home page
|
||||||
|
|
||||||
```tsx
|
Now let's add the sign-in and sign-out buttons in your hoem page. We need to call the server actions in SDK when needed. To help with this, use `getLogtoContext` to fetch authentication status.
|
||||||
import { signIn, signOut } from '@logto/next/server-actions';
|
|
||||||
import SignIn from './sign-in';
|
|
||||||
import SignOut from './sign-out';
|
|
||||||
import { logtoConfig } from './logto';
|
|
||||||
|
|
||||||
export default async function Home() {
|
```tsx title="/app/page.tsx"
|
||||||
return (
|
|
||||||
<main>
|
|
||||||
<h1>Hello Logto.</h1>
|
|
||||||
<div>
|
|
||||||
<SignOut
|
|
||||||
onSignOut={async () => {
|
|
||||||
'use server';
|
|
||||||
|
|
||||||
await signOut(logtoConfig);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<SignIn
|
|
||||||
onSignIn={async () => {
|
|
||||||
'use server';
|
|
||||||
|
|
||||||
await signIn(logtoConfig);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
</Step>
|
|
||||||
|
|
||||||
<Step
|
|
||||||
title="Handle authentication state"
|
|
||||||
>
|
|
||||||
|
|
||||||
We can call the function `getLogtoContext` to get context as the authentication state in pages, let's modify the home page:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { getLogtoContext, signIn, signOut } from '@logto/next/server-actions';
|
import { getLogtoContext, signIn, signOut } from '@logto/next/server-actions';
|
||||||
import SignIn from './sign-in';
|
import SignIn from './sign-in';
|
||||||
import SignOut from './sign-out';
|
import SignOut from './sign-out';
|
||||||
import { logtoConfig } from './logto';
|
import { logtoConfig } from './logto';
|
||||||
|
|
||||||
export default async function Home() {
|
const Home = () => {
|
||||||
const { isAuthenticated } = await getLogtoContext(logtoConfig);
|
const { isAuthenticated, claims } = await getLogtoContext(logtoConfig);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main>
|
<nav>
|
||||||
<h1>Hello Logto.</h1>
|
{isAuthenticated ? (
|
||||||
<div>
|
<p>
|
||||||
{isAuthenticated ? (
|
Hello, {claims?.sub},
|
||||||
<SignOut
|
<SignOut
|
||||||
onSignOut={async () => {
|
onSignOut={async () => {
|
||||||
'use server';
|
'use server';
|
||||||
|
@ -248,7 +167,9 @@ export default async function Home() {
|
||||||
await signOut(logtoConfig);
|
await signOut(logtoConfig);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
</p>
|
||||||
|
) : (
|
||||||
|
<p>
|
||||||
<SignIn
|
<SignIn
|
||||||
onSignIn={async () => {
|
onSignIn={async () => {
|
||||||
'use server';
|
'use server';
|
||||||
|
@ -256,13 +177,23 @@ export default async function Home() {
|
||||||
await signIn(logtoConfig);
|
await signIn(logtoConfig);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
</p>
|
||||||
</div>
|
)}
|
||||||
</main>
|
</nav>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default Home;
|
||||||
```
|
```
|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
|
<Step
|
||||||
|
title="Checkpoint: Test your application"
|
||||||
|
>
|
||||||
|
|
||||||
|
<Checkpoint />
|
||||||
|
|
||||||
|
</Step>
|
||||||
|
|
||||||
</Steps>
|
</Steps>
|
||||||
|
|
|
@ -1,17 +1,18 @@
|
||||||
import UriInputField from '@/mdx-components/UriInputField';
|
|
||||||
import Tabs from '@mdx/components/Tabs';
|
import Tabs from '@mdx/components/Tabs';
|
||||||
import TabItem from '@mdx/components/TabItem';
|
import TabItem from '@mdx/components/TabItem';
|
||||||
import InlineNotification from '@/ds-components/InlineNotification';
|
|
||||||
import { generateStandardSecret } from '@logto/shared/universal';
|
import { generateStandardSecret } from '@logto/shared/universal';
|
||||||
import Steps from '@/mdx-components/Steps';
|
import Steps from '@/mdx-components/Steps';
|
||||||
import Step from '@/mdx-components/Step';
|
import Step from '@/mdx-components/Step';
|
||||||
|
import Checkpoint from '../../fragments/_checkpoint.md';
|
||||||
|
import RedirectUris from '../../fragments/_redirect_uris.mdx';
|
||||||
|
|
||||||
<Steps>
|
<Steps>
|
||||||
|
|
||||||
<Step
|
<Step
|
||||||
title="Installation"
|
title="Installation"
|
||||||
subtitle="Install Logto SDK for your project"
|
subtitle="Install Logto SDK"
|
||||||
>
|
>
|
||||||
|
|
||||||
<Tabs>
|
<Tabs>
|
||||||
<TabItem value="npm" label="npm">
|
<TabItem value="npm" label="npm">
|
||||||
|
|
||||||
|
@ -41,15 +42,10 @@ pnpm add @logto/next
|
||||||
title="Init LogtoClient"
|
title="Init LogtoClient"
|
||||||
>
|
>
|
||||||
|
|
||||||
<InlineNotification>
|
|
||||||
In the following steps, we assume your app is running on <code>http://localhost:3000</code>.
|
|
||||||
</InlineNotification>
|
|
||||||
|
|
||||||
Import and initialize LogtoClient:
|
Import and initialize LogtoClient:
|
||||||
|
|
||||||
<Code className="language-ts">
|
<Code title="libraries/logto.js" className="language-ts">
|
||||||
{`// libraries/logto.js
|
{`import LogtoClient from '@logto/next';
|
||||||
import LogtoClient from '@logto/next';
|
|
||||||
|
|
||||||
export const logtoClient = new LogtoClient({
|
export const logtoClient = new LogtoClient({
|
||||||
endpoint: '${props.endpoint}',
|
endpoint: '${props.endpoint}',
|
||||||
|
@ -63,24 +59,13 @@ export const logtoClient = new LogtoClient({
|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
<Step
|
<Step title="Prepare API routes">
|
||||||
title="Implement sign-in"
|
|
||||||
>
|
|
||||||
|
|
||||||
### 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.
|
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:
|
Go back to your IDE/editor, use the following code to implement the API routes first:
|
||||||
|
|
||||||
```ts
|
```ts title="pages/api/logto/[action].ts"
|
||||||
// pages/api/logto/[action].ts
|
|
||||||
import { logtoClient } from '../../../libraries/logto';
|
import { logtoClient } from '../../../libraries/logto';
|
||||||
|
|
||||||
export default logtoClient.handleAuthRoutes();
|
export default logtoClient.handleAuthRoutes();
|
||||||
|
@ -93,122 +78,70 @@ This will create 4 routes automatically:
|
||||||
3. `/api/logto/sign-out`: Sign out with Logto.
|
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.
|
4. `/api/logto/user`: Check if user is authenticated with Logto, if yes, return user info.
|
||||||
|
|
||||||
### Implement sign-in button
|
</Step>
|
||||||
|
|
||||||
We're almost there! In the last step, we will create a sign-in button:
|
<Step
|
||||||
|
title="Configure redirect URIs"
|
||||||
|
subtitle="2 URIs"
|
||||||
|
>
|
||||||
|
|
||||||
```tsx
|
<RedirectUris callbackUri="http://localhost:3000/api/logto/sign-in-callback" />
|
||||||
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>
|
||||||
|
|
||||||
<Step
|
<Step
|
||||||
title="Implement sign-out"
|
title="Implement sign-in and sign-out"
|
||||||
>
|
>
|
||||||
|
|
||||||
Calling `/api/logto/sign-out` will clear all the Logto data in memory and cookies if they exist.
|
We have prepared the API routes, now let's implement the sign-in and sign-out buttons in your home page. We need to redirect the user to the sign-in or sign-out route when needed. To help with this, use `useSWR` to fetch authentication status from `/api/logto/user`.
|
||||||
|
|
||||||
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>
|
|
||||||
|
|
||||||
<Step
|
|
||||||
title="Handle authentication status"
|
|
||||||
>
|
|
||||||
|
|
||||||
### 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`.
|
Check [this guide](https://swr.vercel.app/docs/getting-started) to learn more about `useSWR`.
|
||||||
|
|
||||||
### Through `getServerSideProps` in the backend
|
```tsx title="/pages/index.tsx"
|
||||||
|
import { type LogtoContext } from '@logto/next';
|
||||||
|
import useSWR from 'swr';
|
||||||
|
|
||||||
```tsx
|
const Home = () => {
|
||||||
import { LogtoUser } from '@logto/next';
|
const { data } = useSWR<LogtoContext>('/api/logto/user');
|
||||||
import { logtoClient } from '../libraries/logto';
|
|
||||||
|
|
||||||
type Props = {
|
return (
|
||||||
user: LogtoUser;
|
<nav>
|
||||||
|
{data?.isAuthenticated ? (
|
||||||
|
<p>
|
||||||
|
Hello, {data.claims?.sub},
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
window.location.assign('/api/logto/sign-out');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Sign Out
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<p>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
window.location.assign('/api/logto/sign-in');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Sign In
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Profile = ({ user }: Props) => {
|
export default Home;
|
||||||
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`.
|
|
||||||
|
|
||||||
### Protect API routes
|
|
||||||
|
|
||||||
Wrap your handler with `logtoClient.withLogtoApiRoute`.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// pages/api/protected-resource.ts
|
|
||||||
import { logtoClient } from '../../libraries/logto';
|
|
||||||
|
|
||||||
export default logtoClient.withLogtoApiRoute((request, response) => {
|
|
||||||
if (!request.user.isAuthenticated) {
|
|
||||||
response.status(401).json({ message: 'Unauthorized' });
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
response.json({
|
|
||||||
data: 'this_is_protected_resource',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
```
|
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
<Step
|
<Step
|
||||||
title="Checkpoint: Test your application"
|
title="Checkpoint: Test your application"
|
||||||
>
|
>
|
||||||
|
|
||||||
Now, you can test your application:
|
<Checkpoint />
|
||||||
|
|
||||||
1. Run your application, you will see the sign-in button.
|
|
||||||
2. Click the sign-in button, and you will be redirected to the sign in route, and the SDK will then init the sign-in process and redirect to the Logto sign-in page.
|
|
||||||
3. After you signed in, you will be redirect back to your application and see user id and the sign-out button.
|
|
||||||
4. Click the sign-out button to sign-out.
|
|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue