From 677054a24569a5f8cedd4dd8ac9b1e3fb74b36ca Mon Sep 17 00:00:00 2001 From: Gao Sun Date: Mon, 26 Feb 2024 11:37:53 +0800 Subject: [PATCH] docs(console): add guides (#5427) --- .changeset/short-boats-suffer.md | 5 + packages/console/.parcelrc | 3 + packages/console/.parcelrc.arm64 | 3 + .../src/assets/docs/fragments/_checkpoint.md | 6 + .../console/src/assets/docs/guides/index.ts | 32 +++ .../assets/docs/guides/native-expo/README.mdx | 172 +++++++++++++++ .../docs/guides/native-expo/config.json | 3 + .../assets/docs/guides/native-expo/index.ts | 19 ++ .../assets/docs/guides/native-expo/logo.svg | 1 + .../assets/docs/guides/spa-angular/README.mdx | 171 +++++++++++++++ .../docs/guides/spa-angular/config.json | 3 + .../assets/docs/guides/spa-angular/index.ts | 19 ++ .../assets/docs/guides/spa-angular/logo.svg | 16 ++ .../assets/docs/guides/web-nuxt/README.mdx | 170 +++++++++++++++ .../assets/docs/guides/web-nuxt/config.json | 3 + .../src/assets/docs/guides/web-nuxt/index.ts | 20 ++ .../src/assets/docs/guides/web-nuxt/logo.svg | 3 + .../docs/guides/web-sveltekit/README.mdx | 204 ++++++++++++++++++ .../docs/guides/web-sveltekit/config.json | 3 + .../assets/docs/guides/web-sveltekit/index.ts | 20 ++ .../assets/docs/guides/web-sveltekit/logo.svg | 1 + .../InlineNotification/index.module.scss | 5 + 22 files changed, 882 insertions(+) create mode 100644 .changeset/short-boats-suffer.md create mode 100644 packages/console/src/assets/docs/fragments/_checkpoint.md create mode 100644 packages/console/src/assets/docs/guides/native-expo/README.mdx create mode 100644 packages/console/src/assets/docs/guides/native-expo/config.json create mode 100644 packages/console/src/assets/docs/guides/native-expo/index.ts create mode 100644 packages/console/src/assets/docs/guides/native-expo/logo.svg create mode 100644 packages/console/src/assets/docs/guides/spa-angular/README.mdx create mode 100644 packages/console/src/assets/docs/guides/spa-angular/config.json create mode 100644 packages/console/src/assets/docs/guides/spa-angular/index.ts create mode 100644 packages/console/src/assets/docs/guides/spa-angular/logo.svg create mode 100644 packages/console/src/assets/docs/guides/web-nuxt/README.mdx create mode 100644 packages/console/src/assets/docs/guides/web-nuxt/config.json create mode 100644 packages/console/src/assets/docs/guides/web-nuxt/index.ts create mode 100644 packages/console/src/assets/docs/guides/web-nuxt/logo.svg create mode 100644 packages/console/src/assets/docs/guides/web-sveltekit/README.mdx create mode 100644 packages/console/src/assets/docs/guides/web-sveltekit/config.json create mode 100644 packages/console/src/assets/docs/guides/web-sveltekit/index.ts create mode 100644 packages/console/src/assets/docs/guides/web-sveltekit/logo.svg diff --git a/.changeset/short-boats-suffer.md b/.changeset/short-boats-suffer.md new file mode 100644 index 000000000..1fea5970a --- /dev/null +++ b/.changeset/short-boats-suffer.md @@ -0,0 +1,5 @@ +--- +"@logto/console": patch +--- + +add Angular, Nuxt, SvelteKit, Expo (React Native) guides diff --git a/packages/console/.parcelrc b/packages/console/.parcelrc index a4b11b773..bdafd2c2b 100644 --- a/packages/console/.parcelrc +++ b/packages/console/.parcelrc @@ -4,6 +4,9 @@ "raw:*": ["@parcel/transformer-raw"], "**/assets/**/*.svg": [ "@parcel/transformer-svg-react" + ], + "*.{md,mdx}": [ + "@parcel/transformer-mdx" ] }, "compressors": { diff --git a/packages/console/.parcelrc.arm64 b/packages/console/.parcelrc.arm64 index cd4477b67..0bc273f89 100644 --- a/packages/console/.parcelrc.arm64 +++ b/packages/console/.parcelrc.arm64 @@ -8,6 +8,9 @@ "raw:*": ["@parcel/transformer-raw"], "**/assets/**/*.svg": [ "@parcel/transformer-svg-react" + ], + "*.{md,mdx}": [ + "@parcel/transformer-mdx" ] }, "compressors": { diff --git a/packages/console/src/assets/docs/fragments/_checkpoint.md b/packages/console/src/assets/docs/fragments/_checkpoint.md new file mode 100644 index 000000000..2abf8cde5 --- /dev/null +++ b/packages/console/src/assets/docs/fragments/_checkpoint.md @@ -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. diff --git a/packages/console/src/assets/docs/guides/index.ts b/packages/console/src/assets/docs/guides/index.ts index 38b8fd109..fd12ff800 100644 --- a/packages/console/src/assets/docs/guides/index.ts +++ b/packages/console/src/assets/docs/guides/index.ts @@ -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 = 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 = 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 = 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', diff --git a/packages/console/src/assets/docs/guides/native-expo/README.mdx b/packages/console/src/assets/docs/guides/native-expo/README.mdx new file mode 100644 index 000000000..bd20cce05 --- /dev/null +++ b/packages/console/src/assets/docs/guides/native-expo/README.mdx @@ -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'; + + + + + + + + + +```bash +npm i @logto/rn +npm i expo-crypto expo-secure-store expo-web-browser @react-native-async-storage/async-storage +``` + + + + +```bash +yarn add @logto/rn +yarn add expo-crypto expo-secure-store expo-web-browser @react-native-async-storage/async-storage +``` + + + + +```bash +pnpm add @logto/rn +pnpm add expo-crypto expo-secure-store expo-web-browser @react-native-async-storage/async-storage +``` + + + + + +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`. + + + +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/). + + + + + + + +Import and use `LogtoProvider` to provide a Logto context: + +
+  
+    {`import { LogtoProvider, LogtoConfig } from '@logto/rn';
+
+const config: LogtoConfig = {
+  endpoint: '${props.endpoint}',
+  appId: '${props.app.id}',
+};
+
+const App = () => (
+  
+    
+  
+);`}
+  
+
+ +
+ + + +Add a native redirect URI (for example, `io.logto://callback`), then click "Save". + + + +- 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. + + + + + +You can use `useLogto` hook to sign in and sign out: + +
+  
+    {`import { useLogto } from '@logto/rn';
+import { Button } from 'react-native';
+
+const Content = () => {
+  const { signIn, signOut, isAuthenticated } = useLogto();
+
+  return (
+    
+ {isAuthenticated ? ( +
+ ); +};`} +
+
+ +
+ + + +To display the user's information, you can use the `getIdTokenClaims()` method: + +
+  
+    {`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 (
+    
+ {isAuthenticated ? ( + <> + {user?.name} + {user?.email} +
+ ); +};`} +
+
+ +
+ + + + + + + +
diff --git a/packages/console/src/assets/docs/guides/native-expo/config.json b/packages/console/src/assets/docs/guides/native-expo/config.json new file mode 100644 index 000000000..4721ad2f7 --- /dev/null +++ b/packages/console/src/assets/docs/guides/native-expo/config.json @@ -0,0 +1,3 @@ +{ + "order": 1.1 +} diff --git a/packages/console/src/assets/docs/guides/native-expo/index.ts b/packages/console/src/assets/docs/guides/native-expo/index.ts new file mode 100644 index 000000000..0ac4eff3c --- /dev/null +++ b/packages/console/src/assets/docs/guides/native-expo/index.ts @@ -0,0 +1,19 @@ +import { ApplicationType } from '@logto/schemas'; + +import { type GuideMetadata } from '../types'; + +const metadata: Readonly = 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; diff --git a/packages/console/src/assets/docs/guides/native-expo/logo.svg b/packages/console/src/assets/docs/guides/native-expo/logo.svg new file mode 100644 index 000000000..785dbbd16 --- /dev/null +++ b/packages/console/src/assets/docs/guides/native-expo/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/console/src/assets/docs/guides/spa-angular/README.mdx b/packages/console/src/assets/docs/guides/spa-angular/README.mdx new file mode 100644 index 000000000..095f3e086 --- /dev/null +++ b/packages/console/src/assets/docs/guides/spa-angular/README.mdx @@ -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'; + + + + + + + +```bash +npm i @logto/js angular-auth-oidc-client +``` + + + + +```bash +yarn add @logto/js angular-auth-oidc-client +``` + + + + +```bash +pnpm add @logto/js angular-auth-oidc-client +``` + + + + + + + + + In the following steps, we assume your app is running on http://localhost:3000. + + +### 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. + + + +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. + + + +### Configure Angular application + +Back to your Angular project, add the auth provider your `app.config.ts`: + +
+  
+    {`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
+  ],
+};`}
+  
+
+ +
+ + + +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 + +
+ +``` + +
+ + + +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 + + +
{{ userData | json }}
+

Access token: {{ accessToken }}

+ + +
+``` + +
+ + + + + + + +
diff --git a/packages/console/src/assets/docs/guides/spa-angular/config.json b/packages/console/src/assets/docs/guides/spa-angular/config.json new file mode 100644 index 000000000..4721ad2f7 --- /dev/null +++ b/packages/console/src/assets/docs/guides/spa-angular/config.json @@ -0,0 +1,3 @@ +{ + "order": 1.1 +} diff --git a/packages/console/src/assets/docs/guides/spa-angular/index.ts b/packages/console/src/assets/docs/guides/spa-angular/index.ts new file mode 100644 index 000000000..f344cdc6d --- /dev/null +++ b/packages/console/src/assets/docs/guides/spa-angular/index.ts @@ -0,0 +1,19 @@ +import { ApplicationType } from '@logto/schemas'; + +import { type GuideMetadata } from '../types'; + +const metadata: Readonly = 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; diff --git a/packages/console/src/assets/docs/guides/spa-angular/logo.svg b/packages/console/src/assets/docs/guides/spa-angular/logo.svg new file mode 100644 index 000000000..96301efe1 --- /dev/null +++ b/packages/console/src/assets/docs/guides/spa-angular/logo.svg @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/packages/console/src/assets/docs/guides/web-nuxt/README.mdx b/packages/console/src/assets/docs/guides/web-nuxt/README.mdx new file mode 100644 index 000000000..19f236c15 --- /dev/null +++ b/packages/console/src/assets/docs/guides/web-nuxt/README.mdx @@ -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(); + + + + + Logto Nuxt SDK only works with Nuxt 3. + + + + + + +```bash +npm i @logto/nuxt +``` + + + + +```bash +yarn add @logto/nuxt +``` + + + + +```bash +pnpm add @logto/nuxt +``` + + + + + + + +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: + +
+  
+    {`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
+});`}
+  
+
+ +Since these information are sensitive, it's recommended to use environment variables (`.env`): + +
+  
+    {`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
+`}
+  
+
+ +See [runtime config](https://nuxt.com/docs/guide/going-further/runtime-config) for more information. + +
+ + + + + In the following steps, we assume your app is running on http://localhost:3000. + + +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. + + + +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. + + + +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. + + + + + +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 +Sign in +
+Sign out +``` + +
+ + + +To display the user's information, you can use the `useLogtoUser()` composable, which is availble on both server and client side: + +```html + + +``` + + + + + + + + + +
diff --git a/packages/console/src/assets/docs/guides/web-nuxt/config.json b/packages/console/src/assets/docs/guides/web-nuxt/config.json new file mode 100644 index 000000000..c435a12c1 --- /dev/null +++ b/packages/console/src/assets/docs/guides/web-nuxt/config.json @@ -0,0 +1,3 @@ +{ + "order": 2 +} diff --git a/packages/console/src/assets/docs/guides/web-nuxt/index.ts b/packages/console/src/assets/docs/guides/web-nuxt/index.ts new file mode 100644 index 000000000..9185cbf74 --- /dev/null +++ b/packages/console/src/assets/docs/guides/web-nuxt/index.ts @@ -0,0 +1,20 @@ +import { ApplicationType } from '@logto/schemas'; + +import { type GuideMetadata } from '../types'; + +const metadata: Readonly = 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; diff --git a/packages/console/src/assets/docs/guides/web-nuxt/logo.svg b/packages/console/src/assets/docs/guides/web-nuxt/logo.svg new file mode 100644 index 000000000..ead151fc7 --- /dev/null +++ b/packages/console/src/assets/docs/guides/web-nuxt/logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/console/src/assets/docs/guides/web-sveltekit/README.mdx b/packages/console/src/assets/docs/guides/web-sveltekit/README.mdx new file mode 100644 index 000000000..0060639c4 --- /dev/null +++ b/packages/console/src/assets/docs/guides/web-sveltekit/README.mdx @@ -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(); + + + + + + + +```bash +npm i @logto/sveltekit +``` + + + + +```bash +yarn add @logto/sveltekit +``` + + + + +```bash +pnpm add @logto/sveltekit +``` + + + + + + + +In your `hooks.server.ts` file, add the following code to inject the Logto hook into your server: + +
+  
+    {`import { handleLogto } from '@logto/sveltekit';
+
+export const handle = handleLogto(
+  {
+    endpoint: '${props.endpoint}',
+    appId: '${props.app.id}',
+    appSecret: '${props.app.secret}',
+  },
+  {
+    encryptionKey: '${cookieEncryptionKey}', // Random-generated
+  }
+);`}
+  
+
+ +Since these information are sensitive, it's recommended to use environment variables: + +
+  
+    {`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,
+  }
+);`}
+  
+
+ +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. + +
+ + + + + In the following steps, we assume your app is running on http://localhost:3000. + + +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. + + + +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. + + + +In the page where you want to implement sign-in and sign-out, define the following actions: + +
+  
+    {`// +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'}');
+  },
+};
+`}
+  
+
+ +Then use these actions in your Svelte component: + +```html +{/* +page.svelte */} +
+ +
+``` + +
+ + + +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 +Sign in +
+Sign out +``` + +
+ + + +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 + + +{#if data.user} +
    + {#each Object.entries(data.user) as [key, value]} +
  • {key}: {value}
  • + {/each} +
+{/if} +``` + +
+ + + + + + + +
diff --git a/packages/console/src/assets/docs/guides/web-sveltekit/config.json b/packages/console/src/assets/docs/guides/web-sveltekit/config.json new file mode 100644 index 000000000..228d0926b --- /dev/null +++ b/packages/console/src/assets/docs/guides/web-sveltekit/config.json @@ -0,0 +1,3 @@ +{ + "order": 1.2 +} diff --git a/packages/console/src/assets/docs/guides/web-sveltekit/index.ts b/packages/console/src/assets/docs/guides/web-sveltekit/index.ts new file mode 100644 index 000000000..9f945df26 --- /dev/null +++ b/packages/console/src/assets/docs/guides/web-sveltekit/index.ts @@ -0,0 +1,20 @@ +import { ApplicationType } from '@logto/schemas'; + +import { type GuideMetadata } from '../types'; + +const metadata: Readonly = 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; diff --git a/packages/console/src/assets/docs/guides/web-sveltekit/logo.svg b/packages/console/src/assets/docs/guides/web-sveltekit/logo.svg new file mode 100644 index 000000000..0ab1ab0c3 --- /dev/null +++ b/packages/console/src/assets/docs/guides/web-sveltekit/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/console/src/ds-components/InlineNotification/index.module.scss b/packages/console/src/ds-components/InlineNotification/index.module.scss index c3afe35b6..77146d811 100644 --- a/packages/console/src/ds-components/InlineNotification/index.module.scss +++ b/packages/console/src/ds-components/InlineNotification/index.module.scss @@ -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 {