0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-01-27 21:39:16 -05:00

docs(console): add guides (#5427)

This commit is contained in:
Gao Sun 2024-02-26 11:37:53 +08:00 committed by GitHub
parent cd1239ed4d
commit 677054a245
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 882 additions and 0 deletions

View file

@ -0,0 +1,5 @@
---
"@logto/console": patch
---
add Angular, Nuxt, SvelteKit, Expo (React Native) guides

View file

@ -4,6 +4,9 @@
"raw:*": ["@parcel/transformer-raw"],
"**/assets/**/*.svg": [
"@parcel/transformer-svg-react"
],
"*.{md,mdx}": [
"@parcel/transformer-mdx"
]
},
"compressors": {

View file

@ -8,6 +8,9 @@
"raw:*": ["@parcel/transformer-raw"],
"**/assets/**/*.svg": [
"@parcel/transformer-svg-react"
],
"*.{md,mdx}": [
"@parcel/transformer-mdx"
]
},
"compressors": {

View file

@ -0,0 +1,6 @@
Now, you can test your application:
1. Run your application, you will see the sign-in button.
2. Click the sign-in button, the SDK will init the sign-in process and redirect you to the Logto sign-in page.
3. After you signed in, you will be redirected back to your application and see user data and the sign-out button.
4. Click the sign-out button to sign out.

View file

@ -8,8 +8,10 @@ import apiSpringBoot from './api-spring-boot/index';
import m2mGeneral from './m2m-general/index';
import nativeAndroid from './native-android/index';
import nativeCapacitor from './native-capacitor/index';
import nativeExpo from './native-expo/index';
import nativeFlutter from './native-flutter/index';
import nativeIosSwift from './native-ios-swift/index';
import spaAngular from './spa-angular/index';
import spaReact from './spa-react/index';
import spaVanilla from './spa-vanilla/index';
import spaVue from './spa-vue/index';
@ -25,12 +27,28 @@ import webGptPlugin from './web-gpt-plugin/index';
import webNext from './web-next/index';
import webNextAppRouter from './web-next-app-router/index';
import webNextServerActions from './web-next-server-actions/index';
import webNuxt from './web-nuxt/index';
import webOutline from './web-outline/index';
import webPhp from './web-php/index';
import webPython from './web-python/index';
import webRemix from './web-remix/index';
import webSveltekit from './web-sveltekit/index';
const guides: Readonly<Guide[]> = Object.freeze([
{
order: 1.1,
id: 'native-expo',
Logo: lazy(async () => import('./native-expo/logo.svg')),
Component: lazy(async () => import('./native-expo/README.mdx')),
metadata: nativeExpo,
},
{
order: 1.1,
id: 'spa-angular',
Logo: lazy(async () => import('./spa-angular/logo.svg')),
Component: lazy(async () => import('./spa-angular/README.mdx')),
metadata: spaAngular,
},
{
order: 1.1,
id: 'spa-react',
@ -73,6 +91,13 @@ const guides: Readonly<Guide[]> = Object.freeze([
Component: lazy(async () => import('./web-next/README.mdx')),
metadata: webNext,
},
{
order: 1.2,
id: 'web-sveltekit',
Logo: lazy(async () => import('./web-sveltekit/logo.svg')),
Component: lazy(async () => import('./web-sveltekit/README.mdx')),
metadata: webSveltekit,
},
{
order: 1.3,
id: 'web-go',
@ -115,6 +140,13 @@ const guides: Readonly<Guide[]> = Object.freeze([
Component: lazy(async () => import('./spa-vanilla/README.mdx')),
metadata: spaVanilla,
},
{
order: 2,
id: 'web-nuxt',
Logo: lazy(async () => import('./web-nuxt/logo.svg')),
Component: lazy(async () => import('./web-nuxt/README.mdx')),
metadata: webNuxt,
},
{
order: 2,
id: 'web-php',

View file

@ -0,0 +1,172 @@
import UriInputField from '@/mdx-components/UriInputField';
import Tabs from '@mdx/components/Tabs';
import TabItem from '@mdx/components/TabItem';
import InlineNotification from '@/ds-components/InlineNotification';
import Steps from '@/mdx-components/Steps';
import Step from '@/mdx-components/Step';
import Checkpoint from '../../fragments/_checkpoint.md';
<Steps>
<Step
title="Installation"
subtitle="Install Logto SDK and peer dependencies"
>
<Tabs>
<TabItem value="npm" label="npm">
```bash
npm i @logto/rn
npm i expo-crypto expo-secure-store expo-web-browser @react-native-async-storage/async-storage
```
</TabItem>
<TabItem value="yarn" label="Yarn">
```bash
yarn add @logto/rn
yarn add expo-crypto expo-secure-store expo-web-browser @react-native-async-storage/async-storage
```
</TabItem>
<TabItem value="pnpm" label="pnpm">
```bash
pnpm add @logto/rn
pnpm add expo-crypto expo-secure-store expo-web-browser @react-native-async-storage/async-storage
```
</TabItem>
</Tabs>
The `@logto/rn` package is the SDK for Logto. The remaining packages are its peer dependencies. They couldn't be listed as direct dependencies because the Expo CLI requires that all dependencies for native modules be installed directly within the root project's `package.json`.
<InlineNotification>
If you're installing this in a [bare React Native app](https://docs.expo.dev/bare/overview), you should also follow these [additional installation instructions](https://docs.expo.dev/bare/installing-expo-modules/).
</InlineNotification>
</Step>
<Step title="Init Logto provider">
Import and use `LogtoProvider` to provide a Logto context:
<pre>
<code className="language-tsx">
{`import { LogtoProvider, LogtoConfig } from '@logto/rn';
const config: LogtoConfig = {
endpoint: '${props.endpoint}',
appId: '${props.app.id}',
};
const App = () => (
<LogtoProvider config={config}>
<YourAppContent />
</LogtoProvider>
);`}
</code>
</pre>
</Step>
<Step title="Implement sign-in and sign-out">
Add a native redirect URI (for example, `io.logto://callback`), then click "Save".
<UriInputField name="redirectUris" />
- For iOS, the redirect URI scheme does not really matter since the `ASWebAuthenticationSession` class will listen to the redirect URI regardless of if it's registered.
- For Android, the redirect URI scheme must be filled in Expo's `app.json` file, for example:
```json
{
"expo": {
"scheme": "io.logto"
}
}
```
The redirect URI is used to redirect the user back to your app after they sign in.
</Step>
<Step title="Implement sign-in and sign-out">
You can use `useLogto` hook to sign in and sign out:
<pre>
<code className="language-tsx">
{`import { useLogto } from '@logto/rn';
import { Button } from 'react-native';
const Content = () => {
const { signIn, signOut, isAuthenticated } = useLogto();
return (
<div>
{isAuthenticated ? (
<Button title="Sign out" onPress={async () => signOut()} />
) : (
<Button title="Sign in" onPress={async () => signIn('${props.redirectUris[0] ?? 'io.logto://callback'}')} />
)}
</div>
);
};`}
</code>
</pre>
</Step>
<Step title="Display user information">
To display the user's information, you can use the `getIdTokenClaims()` method:
<pre>
<code className="language-tsx">
{`import { useLogto } from '@logto/rn';
import { Button, Text } from 'react-native';
const Content = () => {
const { getIdTokenClaims, isAuthenticated } = useLogto();
const [user, setUser] = useState(null);
useEffect(() => {
if (isAuthenticated) {
getIdTokenClaims().then((claims) => {
setUser(claims);
});
}
}, [isAuthenticated]);
return (
<div>
{isAuthenticated ? (
<>
<Text>{user?.name}</Text>
<Text>{user?.email}</Text>
<Button title="Sign out" onPress={async () => signOut()} />
</>
) : (
<Button title="Sign in" onPress={async () => signIn('${props.redirectUris[0] ?? 'io.logto://callback'}')} />
)}
</div>
);
};`}
</code>
</pre>
</Step>
<Step title="Checkpoint: Test your app">
<Checkpoint />
</Step>
</Steps>

View file

@ -0,0 +1,3 @@
{
"order": 1.1
}

View file

@ -0,0 +1,19 @@
import { ApplicationType } from '@logto/schemas';
import { type GuideMetadata } from '../types';
const metadata: Readonly<GuideMetadata> = Object.freeze({
name: 'Expo (React Native)',
description: 'Create universal native apps with React that run on Android, iOS, and the web.',
target: ApplicationType.Native,
sample: {
repo: 'react-native',
path: 'packages/rn-sample',
},
fullGuide: {
title: 'Full Expo (React Native) guide',
url: 'https://docs.logto.io/sdk/expo',
},
});
export default metadata;

View file

@ -0,0 +1 @@
<svg width="24" height="22" viewBox="0 0 24 22" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M11.39 8.269c.19-.277.397-.312.565-.312.168 0 .447.035.637.312 1.49 2.03 3.95 6.075 5.765 9.06 1.184 1.945 2.093 3.44 2.28 3.63.7.714 1.66.269 2.218-.541.549-.797.701-1.357.701-1.954 0-.407-7.958-15.087-8.759-16.309C14.027.98 13.775.683 12.457.683h-.988c-1.315 0-1.505.297-2.276 1.472C8.392 3.377.433 18.057.433 18.463c0 .598.153 1.158.703 1.955.558.81 1.518 1.255 2.218.54.186-.19 1.095-1.684 2.279-3.63 1.815-2.984 4.267-7.029 5.758-9.06z" fill="#000"/></svg>

After

Width:  |  Height:  |  Size: 565 B

View file

@ -0,0 +1,171 @@
import UriInputField from '@/mdx-components/UriInputField';
import Tabs from '@mdx/components/Tabs';
import TabItem from '@mdx/components/TabItem';
import InlineNotification from '@/ds-components/InlineNotification';
import Steps from '@/mdx-components/Steps';
import Step from '@/mdx-components/Step';
import Checkpoint from '../../fragments/_checkpoint.md';
<Steps>
<Step
title="Installation"
subtitle="Install Logto core and `angular-auth-oidc-client`"
>
<Tabs>
<TabItem value="npm" label="npm">
```bash
npm i @logto/js angular-auth-oidc-client
```
</TabItem>
<TabItem value="yarn" label="Yarn">
```bash
yarn add @logto/js angular-auth-oidc-client
```
</TabItem>
<TabItem value="pnpm" label="pnpm">
```bash
pnpm add @logto/js angular-auth-oidc-client
```
</TabItem>
</Tabs>
</Step>
<Step title="Configure application">
<InlineNotification>
In the following steps, we assume your app is running on <code>http://localhost:3000</code>.
</InlineNotification>
### Configure redirect URIs
First, let's enter your redirect URI. E.g. `http://localhost:3000/callback`. [Redirect URI](https://www.oauth.com/oauth2-servers/redirect-uris/) is an OAuth 2.0 concept which implies the location should redirect after authentication.
<UriInputField name="redirectUris" />
After signing out, it'll be great to redirect user back to your website. For example, add `http://localhost:3000` as the post sign-out redirect URI below.
<UriInputField name="postLogoutRedirectUris" />
### Configure Angular application
Back to your Angular project, add the auth provider your `app.config.ts`:
<pre>
<code className="language-tsx">
{`import { UserScope, buildAngularAuthConfig } from '@logto/js';
import { provideAuth } from 'angular-auth-oidc-client';
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(withFetch()),
provideAuth({
config: buildAngularAuthConfig({
endpoint: '${props.endpoint}',
appId: '${props.app.id}',
redirectUri: '${props.redirectUris[0] ?? 'http://localhost:3000/callback'}',
postLogoutRedirectUri: '${props.postLogoutRedirectUris[0] ?? 'http://localhost:3000'}',
}),
}),
// ...other providers
],
};`}
</code>
</pre>
</Step>
<Step title="Implement sign-in and sign-out">
In the component where you want to implement sign-in and sign-out (for example, `app.component.ts`), inject the `OidcSecurityService` and use it to sign in and sign out.
```ts
import { OidcSecurityService } from 'angular-auth-oidc-client';
export class AppComponent implements OnInit {
constructor(public oidcSecurityService: OidcSecurityService) {}
signIn() {
this.oidcSecurityService.authorize();
}
signOut() {
this.oidcSecurityService.logoff().subscribe((result) => {
console.log('app sign-out', result);
});
}
}
```
Then, in the template, add buttons to sign in and sign out:
```html
<button (click)="signIn()">Sign in</button>
<br/>
<button (click)="signOut()">Sign out</button>
```
</Step>
<Step title="Subscribe to authentication state and display user information">
The `OidcSecurityService` provides a convenient way to subscribe to the authentication state:
```ts
import { OidcSecurityService } from 'angular-auth-oidc-client';
import type { UserInfoResponse } from '@logto/js';
export class AppComponent implements OnInit {
isAuthenticated = false;
userData?: UserInfoResponse;
idToken?: string;
accessToken?: string;
constructor(public oidcSecurityService: OidcSecurityService) {}
ngOnInit() {
this.oidcSecurityService
.checkAuth()
.subscribe(({ isAuthenticated, userData, idToken, accessToken }) => {
console.log('app authenticated', isAuthenticated, userData);
this.isAuthenticated = isAuthenticated;
this.userData = userData;
this.idToken = idToken;
this.accessToken = accessToken;
});
}
// ...other methods
}
```
And use it in the template:
```html
<button *ngIf="!isAuthenticated" (click)="signIn()">Sign in</button>
<ng-container *ngIf="isAuthenticated">
<pre>{{ userData | json }}</pre>
<p>Access token: {{ accessToken }}</p>
<!-- ... -->
<button (click)="signOut()">Sign out</button>
</ng-container>
```
</Step>
<Step
title="Checkpoint: Test your application"
>
<Checkpoint />
</Step>
</Steps>

View file

@ -0,0 +1,3 @@
{
"order": 1.1
}

View file

@ -0,0 +1,19 @@
import { ApplicationType } from '@logto/schemas';
import { type GuideMetadata } from '../types';
const metadata: Readonly<GuideMetadata> = Object.freeze({
name: 'Angular',
description: 'Angular is a JavaScript library for building user interfaces.',
target: ApplicationType.SPA,
sample: {
repo: 'js',
path: 'packages/angular-sample',
},
fullGuide: {
title: 'Full Angular guide',
url: 'https://docs.logto.io/sdk/angular',
},
});
export default metadata;

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 250 250" style="enable-background:new 0 0 250 250;" xml:space="preserve">
<style type="text/css">
.st0{fill:#DD0031;}
.st1{fill:#C3002F;}
.st2{fill:#FFFFFF;}
</style>
<g>
<polygon class="st0" points="125,30 125,30 125,30 31.9,63.2 46.1,186.3 125,230 125,230 125,230 203.9,186.3 218.1,63.2 "/>
<polygon class="st1" points="125,30 125,52.2 125,52.1 125,153.4 125,153.4 125,230 125,230 203.9,186.3 218.1,63.2 125,30 "/>
<path class="st2" d="M125,52.1L66.8,182.6h0h21.7h0l11.7-29.2h49.4l11.7,29.2h0h21.7h0L125,52.1L125,52.1L125,52.1L125,52.1
L125,52.1z M142,135.4H108l17-40.9L142,135.4z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 899 B

View file

@ -0,0 +1,170 @@
import UriInputField from '@/mdx-components/UriInputField';
import Tabs from '@mdx/components/Tabs';
import TabItem from '@mdx/components/TabItem';
import InlineNotification from '@/ds-components/InlineNotification';
import Steps from '@/mdx-components/Steps';
import Step from '@/mdx-components/Step';
import Checkpoint from '../../fragments/_checkpoint.md';
import { generateStandardSecret } from '@logto/shared/universal';
export const cookieEncryptionKey = generateStandardSecret();
<Steps>
<InlineNotification severity="alert">
Logto Nuxt SDK only works with Nuxt 3.
</InlineNotification>
<Step
title="Installation"
subtitle="Install Logto SDK"
>
<Tabs>
<TabItem value="npm" label="npm">
```bash
npm i @logto/nuxt
```
</TabItem>
<TabItem value="yarn" label="Yarn">
```bash
yarn add @logto/nuxt
```
</TabItem>
<TabItem value="pnpm" label="pnpm">
```bash
pnpm add @logto/nuxt
```
</TabItem>
</Tabs>
</Step>
<Step title="Register Logto module">
In your Nuxt config file (`next.config.ts`), add the Logto module:
```ts
export default defineNuxtConfig({
modules: ['@logto/nuxt'],
// ...other configurations
});
```
The minimal configuration for the module is as follows:
<pre>
<code className="language-tsx">
{`export default defineNuxtConfig({
modules: ['@logto/nuxt'],
runtimeConfig: {
logto: {
endpoint: '${props.endpoint}',
appId: '${props.app.id}',
appSecret: '${props.app.secret}',
cookieEncryptionKey: '${cookieEncryptionKey}', // Random-generated
},
},
// ...other configurations
});`}
</code>
</pre>
Since these information are sensitive, it's recommended to use environment variables (`.env`):
<pre>
<code className="language-bash">
{`NUXT_LOGTO_ENDPOINT=${props.endpoint}
NUXT_LOGTO_APP_ID=${props.app.id}
NUXT_LOGTO_APP_SECRET=${props.app.secret}
NUXT_LOGTO_COOKIE_ENCRYPTION_KEY=${cookieEncryptionKey} # Random-generated
`}
</code>
</pre>
See [runtime config](https://nuxt.com/docs/guide/going-further/runtime-config) for more information.
</Step>
<Step title="Configure your app">
<InlineNotification>
In the following steps, we assume your app is running on <code>http://localhost:3000</code>.
</InlineNotification>
First, let's enter your redirect URI. E.g. `http://localhost:3000/callback`. [Redirect URI](https://www.oauth.com/oauth2-servers/redirect-uris/) is an OAuth 2.0 concept which implies the location should redirect after authentication.
<UriInputField name="redirectUris" />
After signing out, it'll be great to redirect user back to your website. For example, add `http://localhost:3000` as the post sign-out redirect URI below.
<UriInputField name="postLogoutRedirectUris" />
When registering `@logto/nuxt` module, it will do the following:
- Add three routes for sign-in (`/sign-in`), sign-out (`/sign-out`), and callback (`/callback`).
- Import two composables: `useLogtoClient` and `useLogtoUser`.
These routes are configurable via `logto.pathnames` in the module options, for example:
```ts
export default defineNuxtConfig({
logto: {
pathnames: {
signIn: '/login',
signOut: '/logout',
callback: '/auth/callback',
},
},
// ...other configurations
});
```
Check out the [type definition file](https://github.com/logto-io/js/blob/HEAD/packages/nuxt/src/runtime/utils/types.ts) in the `@logto/nuxt` package for more information.
Note: If you configure the callback route to a different path, you need to update the redirect URI in Logto accordingly.
</Step>
<Step title="Implement sign-in and sign-out">
Since Nuxt pages will be hydrated and become a single-page application (SPA) after the initial load, we need to redirect the user to the sign-in or sign-out route when needed.
```html
<a :href="/sign-in">Sign in</a>
<br />
<a :href="/sign-out">Sign out</a>
```
</Step>
<Step title="Display user information">
To display the user's information, you can use the `useLogtoUser()` composable, which is availble on both server and client side:
```html
<script setup lang="ts">
const user = useLogtoUser();
</script>
<template>
<ul v-if="Boolean(user)">
<li v-for="(value, key) in user"><b>{{ key }}:</b> {{ value }}</li>
</ul>
<!-- Simplified button for sign-in and sign-out -->
<a :href="`/sign-${ user ? 'out' : 'in' }`"> Sign {{ user ? 'out' : 'in' }} </a>
</template>
```
</Step>
<Step title="Checkpoint: Test your app">
<Checkpoint />
</Step>
</Steps>

View file

@ -0,0 +1,3 @@
{
"order": 2
}

View file

@ -0,0 +1,20 @@
import { ApplicationType } from '@logto/schemas';
import { type GuideMetadata } from '../types';
const metadata: Readonly<GuideMetadata> = Object.freeze({
name: 'Nuxt',
description:
'Nuxt is an open source framework that makes web development intuitive and powerful.',
target: ApplicationType.Traditional,
sample: {
repo: 'js',
path: 'packages/nuxt',
},
fullGuide: {
title: 'Full Nuxt guide',
url: 'https://docs.logto.io/sdk/nuxt',
},
});
export default metadata;

View file

@ -0,0 +1,3 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M281.44 397.667H438.32C443.326 397.667 448.118 395.908 452.453 393.427C456.789 390.946 461.258 387.831 463.76 383.533C466.262 379.236 468.002 374.36 468 369.399C467.998 364.437 466.266 359.563 463.76 355.268L357.76 172.947C355.258 168.65 352.201 165.534 347.867 163.053C343.532 160.573 337.325 158.813 332.32 158.813C327.315 158.813 322.521 160.573 318.187 163.053C313.852 165.534 310.795 168.65 308.293 172.947L281.44 219.587L227.733 129.13C225.229 124.834 222.176 120.307 217.84 117.827C213.504 115.346 208.713 115 203.707 115C198.701 115 193.909 115.346 189.573 117.827C185.238 120.307 180.771 124.834 178.267 129.13L46.8267 355.268C44.3208 359.563 44.0022 364.437 44 369.399C43.9978 374.36 44.3246 379.235 46.8267 383.533C49.3288 387.83 53.7979 390.946 58.1333 393.427C62.4688 395.908 67.2603 397.667 72.2667 397.667H171.2C210.401 397.667 238.934 380.082 258.827 346.787L306.88 263.4L332.32 219.587L410.053 352.44H306.88L281.44 397.667ZM169.787 352.44H100.533L203.707 174.36L256 263.4L221.361 323.784C208.151 345.387 193.089 352.44 169.787 352.44Z" fill="#00DC82"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,204 @@
import UriInputField from '@/mdx-components/UriInputField';
import Tabs from '@mdx/components/Tabs';
import TabItem from '@mdx/components/TabItem';
import InlineNotification from '@/ds-components/InlineNotification';
import Steps from '@/mdx-components/Steps';
import Step from '@/mdx-components/Step';
import Checkpoint from '../../fragments/_checkpoint.md';
import { generateStandardSecret } from '@logto/shared/universal';
export const cookieEncryptionKey = generateStandardSecret();
<Steps>
<Step
title="Installation"
subtitle="Install Logto SDK"
>
<Tabs>
<TabItem value="npm" label="npm">
```bash
npm i @logto/sveltekit
```
</TabItem>
<TabItem value="yarn" label="Yarn">
```bash
yarn add @logto/sveltekit
```
</TabItem>
<TabItem value="pnpm" label="pnpm">
```bash
pnpm add @logto/sveltekit
```
</TabItem>
</Tabs>
</Step>
<Step title="Add Logto hook">
In your `hooks.server.ts` file, add the following code to inject the Logto hook into your server:
<pre>
<code className="language-tsx">
{`import { handleLogto } from '@logto/sveltekit';
export const handle = handleLogto(
{
endpoint: '${props.endpoint}',
appId: '${props.app.id}',
appSecret: '${props.app.secret}',
},
{
encryptionKey: '${cookieEncryptionKey}', // Random-generated
}
);`}
</code>
</pre>
Since these information are sensitive, it's recommended to use environment variables:
<pre>
<code className="language-ts">
{`import { handleLogto } from '@logto/sveltekit';
import { env } from '$env/dynamic/private';
export const handle = handleLogto(
{
endpoint: env.LOGTO_ENDPOINT,
appId: env.LOGTO_APP_ID,
appSecret: env.LOGTO_APP_SECRET,
},
{
encryptionKey: env.LOGTO_COOKIE_ENCRYPTION_KEY,
}
);`}
</code>
</pre>
If you have multiple hooks, you can use [the sequence() helper function](https://kit.svelte.dev/docs/modules#sveltejs-kit-hooks) to chain them:
```ts
import { sequence } from '@sveltejs/kit/hooks';
export const handle = sequence(handleLogto, handleOtherHook);
```
Now you can access the Logto client in the `locals` object. For TypeScript, you can add the type to `app.d.ts`:
```ts
import type { LogtoClient, UserInfoResponse } from '@logto/sveltekit';
declare global {
namespace App {
interface Locals {
logtoClient: LogtoClient;
user?: UserInfoResponse;
}
}
}
```
We'll discuss the `user` object later.
</Step>
<Step title="Implement sign-in and sign-out">
<InlineNotification>
In the following steps, we assume your app is running on <code>http://localhost:3000</code>.
</InlineNotification>
First, let's enter your redirect URI. E.g. `http://localhost:3000/callback`. [Redirect URI](https://www.oauth.com/oauth2-servers/redirect-uris/) is an OAuth 2.0 concept which implies the location should redirect after authentication.
<UriInputField name="redirectUris" />
After signing out, it'll be great to redirect user back to your website. For example, add `http://localhost:3000` as the post sign-out redirect URI below.
<UriInputField name="postLogoutRedirectUris" />
In the page where you want to implement sign-in and sign-out, define the following actions:
<pre>
<code className="language-ts">
{`// +page.server.ts
import type { Actions } from './$types';
export const actions: Actions = {
signIn: async ({ locals }) => {
await locals.logtoClient.signIn('${props.redirectUris[0] ?? 'http://localhost:3000/callback'}');
},
signOut: async ({ locals }) => {
await locals.logtoClient.signOut('${props.postLogoutRedirectUris[0] ?? 'http://localhost:3000'}');
},
};
`}
</code>
</pre>
Then use these actions in your Svelte component:
```html
{/* +page.svelte */}
<form method="POST" action="?/{data.user ? 'signOut' : 'signIn'}">
<button type="submit">Sign {data.user ? 'out' : 'in'}</button>
</form>
```
</Step>
<Step title="Implement sign-in and sign-out">
Since Nuxt pages will be hydrated and become a single-page application (SPA) after the initial load, we need to redirect the user to the sign-in or sign-out route when needed.
```html
<a :href="/sign-in">Sign in</a>
<br />
<a :href="/sign-out">Sign out</a>
```
</Step>
<Step title="Display user information">
To display the user's information, you can inject the `locals.user` object into the layout, thus making it available to all pages:
```ts
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async ({ locals }) => {
return { user: locals.user };
};
```
In your Svelte component:
```html
<script>
/** @type {import('./$types').PageData} */
export let data;
</script>
{#if data.user}
<ul>
{#each Object.entries(data.user) as [key, value]}
<li>{key}: {value}</li>
{/each}
</ul>
{/if}
```
</Step>
<Step title="Checkpoint: Test your app">
<Checkpoint />
</Step>
</Steps>

View file

@ -0,0 +1,3 @@
{
"order": 1.2
}

View file

@ -0,0 +1,20 @@
import { ApplicationType } from '@logto/schemas';
import { type GuideMetadata } from '../types';
const metadata: Readonly<GuideMetadata> = Object.freeze({
name: 'SvelteKit',
description:
'SvelteKit is a framework for rapidly developing robust, performant web applications using Svelte.',
target: ApplicationType.Traditional,
sample: {
repo: 'js',
path: 'packages/sveltekit-sample',
},
fullGuide: {
title: 'Full SvelteKit guide',
url: 'https://docs.logto.io/sdk/sveltekit',
},
});
export default metadata;

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M416.9 93.1c-41.1-58.9-122.4-76.3-181.2-38.9L132.5 120c-28.2 17.7-47.6 46.5-53.5 79.3-4.9 27.3-.6 55.5 12.3 80-8.8 13.4-14.9 28.5-17.7 44.2-5.9 33.4 1.8 67.8 21.6 95.4 41.2 58.9 122.4 76.3 181.2 38.9L379.6 392c28.2-17.7 47.6-46.5 53.5-79.3 4.9-27.3.6-55.5-12.3-80 8.8-13.4 14.9-28.4 17.7-44.2 5.8-33.4-1.9-67.8-21.6-95.4" style="fill:#ff3e00"/><path d="M225.6 424.5c-33.3 8.6-68.4-4.4-88-32.6-11.9-16.6-16.5-37.3-13-57.4.6-3.3 1.4-6.5 2.5-9.6l1.9-5.9 5.3 3.9c12.2 9 25.9 15.8 40.4 20.2l3.8 1.2-.4 3.8c-.5 5.4 1 10.9 4.2 15.3 5.9 8.5 16.5 12.4 26.5 9.8 2.2-.6 4.4-1.5 6.3-2.8l103.2-65.8c5.1-3.2 8.6-8.4 9.7-14.4 1.1-6.1-.3-12.3-3.9-17.3-5.9-8.5-16.5-12.4-26.5-9.8-2.2.6-4.4 1.5-6.3 2.8L252 291c-6.5 4.1-13.5 7.2-21 9.2-33.3 8.6-68.4-4.4-88-32.6-11.9-16.6-16.5-37.3-13-57.4 3.5-19.7 15.2-37 32.2-47.7l103.2-65.8c6.5-4.1 13.5-7.2 21-9.2 33.3-8.6 68.4 4.4 88 32.6 11.9 16.6 16.5 37.3 13 57.4-.6 3.3-1.4 6.5-2.5 9.6L383 193l-5.3-3.9c-12.2-9-25.9-15.8-40.4-20.2l-3.8-1.2.4-3.8c.5-5.4-1-10.9-4.2-15.3-5.9-8.5-16.5-12.4-26.5-9.8-2.2.6-4.4 1.5-6.3 2.8l-103.2 65.8c-5.1 3.2-8.6 8.4-9.7 14.4-1.1 6.1.3 12.3 3.9 17.3 5.9 8.5 16.5 12.4 26.5 9.8 2.2-.6 4.4-1.5 6.3-2.8L260 221c6.5-4.1 13.5-7.2 21-9.2 33.3-8.6 68.4 4.4 88 32.6 11.9 16.6 16.5 37.3 13 57.4-3.5 19.7-15.2 37-32.2 47.7l-103.2 65.8c-6.5 4.1-13.6 7.2-21 9.2" style="fill:#fff"/></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -23,6 +23,11 @@
flex: 1;
overflow: hidden;
overflow-wrap: break-word;
// Cancel the margin from MDX generated paragraphs
&:has(> p) {
margin: _.unit(-4) 0;
}
}
&.info {