+
Now, you can use the `getAccessToken` method to retrieve an access token for your API:
-
-
+
{`const Content = () => {
const { getAccessToken, isAuthenticated } = useLogto();
@@ -75,13 +70,11 @@ Now, you can use the `getAccessToken` method to retrieve an access token for you
}
}, [isAuthenticated, getAccessToken]);
};`}
-
-
+
Lastly, include this access token in the `Authorization` header when making requests to your API:
-
-
+
{`const Content = () => {
const { getAccessToken, isAuthenticated } = useLogto();
@@ -97,8 +90,7 @@ Lastly, include this access token in the `Authorization` header when making requ
}
}, [isAuthenticated, getAccessToken]);
};`}
-
-
+
@@ -150,8 +142,7 @@ const extractBearerTokenFromHeaders = ({ authorization }: IncomingHttpHeaders) =
Subsequently, create a middleware to verify the access token:
-
-
+
{`import { createRemoteJWKSet, jwtVerify } from 'jose';
// Generate a JWKS using jwks_uri obtained from the Logto server
@@ -181,8 +172,7 @@ export const authMiddleware = async (req, res, next) => {
return next();
};`}
-
-
+
You can now employ this middleware to protect your API endpoints:
@@ -210,8 +200,7 @@ To address this, we can employ role-based access control (RBAC). In Logto, you c
After defining roles and permissions, you can add the `scopes` option to the `LogtoProvider` component:
-
-
+
{``}
-
-
+
Logto will then only issue an access token with the appropriate scope(s) to the user. For instance, if a user only has the `read:products` scope, the access token will solely contain that scope:
diff --git a/packages/console/src/assets/docs/guides/api-python/README.mdx b/packages/console/src/assets/docs/guides/api-python/README.mdx
index 621abc9275c..d166c36f691 100644
--- a/packages/console/src/assets/docs/guides/api-python/README.mdx
+++ b/packages/console/src/assets/docs/guides/api-python/README.mdx
@@ -7,9 +7,7 @@ import { appendPath } from '@silverhand/essentials';
-```python
-"""requires-auth.py
-"""
+```python title="requires-auth.py"
def get_auth_token():
auth = request.headers.get("Authorization", None)
@@ -41,30 +39,22 @@ pip install python-jose[ecdsa]
### Retrieve Logto's OIDC configurations
-
You will need a JWK public key set and the token issuer to verify the signature and source of the received JWS token.
-All the latest public Logto Authorization Configurations can be found at {appendPath(props.endpoint, '/oidc/.well-known/openid-configuration')}.
+All the latest public Logto Authorization Configurations can be found at {appendPath(props.endpoint, '/oidc/.well-known/openid-configuration').href}.
e.g. You can locate the following two fields in the response body if you request the above endpoint.
-
+
+
+
- For 🔐 RBAC, scope validation is also required.
+ For 🔐 RBAC, scope validation is also required.
diff --git a/packages/console/src/assets/docs/guides/api-spring-boot/README.mdx b/packages/console/src/assets/docs/guides/api-spring-boot/README.mdx
index 161da637afb..50b6ee27737 100644
--- a/packages/console/src/assets/docs/guides/api-spring-boot/README.mdx
+++ b/packages/console/src/assets/docs/guides/api-spring-boot/README.mdx
@@ -1,5 +1,5 @@
-import Tabs from '@mdx/components/Tabs';
-import TabItem from '@mdx/components/TabItem';
+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';
@@ -35,9 +35,10 @@ dependencies {
Since Spring Boot and Spring Security have built-in support for both OAuth2 resource server and JWT validation,
you DO NOT need to add additional libraries from Logto to integrate.
- See [Spring Security OAuth 2.0 Resource Server](https://docs.spring.io/spring-security/reference/servlet/oauth2/resource-server/index.html)
- and [Spring Security Architecture](https://spring.io/guides/topicals/spring-security-architecture)
- for more details.
+See [Spring Security OAuth 2.0 Resource Server](https://docs.spring.io/spring-security/reference/servlet/oauth2/resource-server/index.html)
+and [Spring Security Architecture](https://spring.io/guides/topicals/spring-security-architecture)
+for more details.
+
@@ -50,22 +51,20 @@ and signed with [JWK](https://datatracker.ietf.org/doc/html/rfc7517)
Before moving on, you will need to get an issuer and a JWKS URI to verify the issuer and the signature of the Bearer Token (`access_token`).
-
-All the Logto Authorization server configurations can be found by requesting {appendPath(props.endpoint, '/oidc/.well-known/openid-configuration')}, including the issuer, jwks_uri and other authorization configs.
-
+All the Logto Authorization server configurations can be found by requesting{' '}
+{appendPath(props.endpoint, '/oidc/.well-known/openid-configuration').href}, including the{' '}
+issuer, jwks_uri and other authorization configs.
An example of the response:
-
+
@@ -73,10 +72,8 @@ An example of the response:
Use an `application.yml` file (instead of the default `application.properties`) to configure the server port, audience, and OAuth2 resource server.
-
+
- `audience`: The unique API identifier of your protected API resource.
- `spring.security.oauth2.resourceserver.jwt.issuer-uri`: The iss claim value and the issuer URI in the JWT issued by Logto.
@@ -102,8 +98,7 @@ spring:
Provide your own `AudienceValidator` class that implements the `OAuth2TokenValidator` interface to validate whether the required audience is present in the JWT.
-```java
-// path/to/project/src/main/java/io/logto/springboot/sample/validator/AudienceValidator.java
+```java title="validator/AudienceValidator.java"
package io.logto.springboot.sample.validator;
import org.springframework.security.oauth2.core.OAuth2Error;
@@ -113,8 +108,6 @@ import org.springframework.security.oauth2.jwt.Jwt;
public class AudienceValidator implements OAuth2TokenValidator {
- private final OAuth2Error oAuth2Error = new OAuth2Error("invalid_token", "Required audience not found", null);
-
private final String audience;
public AudienceValidator(String audience) {
@@ -124,18 +117,21 @@ public class AudienceValidator implements OAuth2TokenValidator {
@Override
public OAuth2TokenValidatorResult validate(Jwt jwt) {
if (!jwt.getAudience().contains(audience)) {
- return OAuth2TokenValidatorResult.failure(oAuth2Error);
+ return OAuth2TokenValidatorResult.failure(new OAuth2Error("invalid_token", "Required audience not found", null));
}
+ // Optional: For RBAC validate the scopes of the JWT.
+ String scopes = jwt.getClaimAsString("scope");
+ if (scopes == null || !scopes.contains("read:profile")) {
+ return OAuth2TokenValidatorResult.failure(new OAuth2Error("invalid_token", "Insufficient permission", null));
+ }
+
+
return OAuth2TokenValidatorResult.success();
}
}
```
-
- For 🔐 RBAC, scope validation is also required.
-
-
@@ -144,8 +140,7 @@ Spring Security makes it easy to configure your application as a resource server
You need to provide instances of `JwtDecoder` and `SecurityFilterChain` (as Spring beans), and add the `@EnableWebSecurity` annotation.
-```java
-// path/to/project/src/main/java/io/logto/springboot/sample/configuration/SecurityConfiguration.java
+```java title="configuration/SecurityConfiguration.java"
package io.logto.springboot.sample.configuration;
import com.nimbusds.jose.JOSEObjectType;
@@ -154,17 +149,19 @@ import com.nimbusds.jose.proc.SecurityContext;
import io.logto.springboot.sample.validator.AudienceValidator;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
+import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
-import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtValidators;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
-import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
+import org.springframework.security.web.DefaultSecurityFilterChain;
+@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
@@ -180,6 +177,8 @@ public class SecurityConfiguration {
@Bean
public JwtDecoder jwtDecoder() {
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri(jwksUri)
+ // Logto uses the ES384 algorithm to sign the JWTs by default.
+ .jwsAlgorithm(ES384)
// The decoder should support the token type: Access Token + JWT.
.jwtProcessorCustomizer(customizer -> customizer.setJWSTypeVerifier(
new DefaultJOSEObjectTypeVerifier(new JOSEObjectType("at+jwt"))))
@@ -194,14 +193,17 @@ public class SecurityConfiguration {
}
@Bean
- public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
- http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt).cors().and()
- .authorizeRequests(customizer -> customizer
- // Only authenticated requests can access your protected APIs
- .mvcMatchers("/", "/secret").authenticated()
- // Anyone can access the public profile.
- .mvcMatchers("/profile").permitAll()
- );
+ public DefaultSecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+ http
+ .securityMatcher("/api/**")
+ .oauth2ResourceServer(oauth2 -> oauth2
+ .jwt(Customizer.withDefaults()))
+ .authorizeHttpRequests(requests -> requests
+ // Allow all requests to the public APIs.
+ .requestMatchers("/api/.wellknown/**").permitAll()
+ // Require jwt token validation for the protected APIs.
+ .anyRequest().authenticated());
+
return http.build();
}
}
@@ -213,8 +215,7 @@ public class SecurityConfiguration {
Add a controller to provide the protected and public APIs:
-```java
-// path/to/project/src/main/java/io/logto/springboot/sample/controller/ProtectedController.java
+```java title="controller/ProtectedController.java"
package io.logto.springboot.sample.controller;
import org.springframework.web.bind.annotation.CrossOrigin;
@@ -226,20 +227,14 @@ import org.springframework.web.bind.annotation.RestController;
@CrossOrigin(origins = "*")
@RestController
public class ProtectedController {
-
- @GetMapping("/")
- public String protectedRoot() {
- return "Protected root.";
- }
-
- @GetMapping("/secret")
- public String protectedSecret() {
- return "Protected secret.";
+ @GetMapping("/api/profile")
+ public String protectedProfile() {
+ return "Protected profile.";
}
- @GetMapping("/profile")
- public String publicProfile() {
- return "Public profile.";
+ @GetMapping("/api/.wellknown/config.json")
+ public String publicConfig() {
+ return "Public config.";
}
}
```
@@ -272,12 +267,10 @@ gradlew.bat bootRun
Request your protected API with the Access Token as the Bearer token in the Authorization header, e.g. execute the `curl` command.
-
+
If successful, you will get a response with 200 status:
@@ -298,7 +291,7 @@ WWW-Authenticate: Bearer error="invalid_token", error_description="An error occu
-
+
- [Protect your API](https://docs.logto.io/docs/recipes/protect-your-api/)
- [Spring Security OAuth 2.0 Resource Server](https://docs.spring.io/spring-security/reference/servlet/oauth2/resource-server/index.html)
diff --git a/packages/console/src/assets/docs/guides/generate-metadata.js b/packages/console/src/assets/docs/guides/generate-metadata.js
index a0be81e5738..d1ca726d995 100644
--- a/packages/console/src/assets/docs/guides/generate-metadata.js
+++ b/packages/console/src/assets/docs/guides/generate-metadata.js
@@ -20,8 +20,7 @@ const data = await Promise.all(
return;
}
- // Add `.png` later
- const logo = ['logo.svg'].find((logo) => existsSync(`${directory}/${logo}`));
+ const logo = ['logo.webp', 'logo.svg', 'logo.png'].find((logo) => existsSync(`${directory}/${logo}`));
const config = existsSync(`${directory}/config.json`)
? await import(`./${directory}/config.json`, { assert: { type: 'json' } }).then(
@@ -42,20 +41,31 @@ const metadata = data
.sort((a, b) => a.order - b.order);
const camelCase = (value) => value.replaceAll(/-./g, (x) => x[1].toUpperCase());
-const filename = 'index.ts';
+const filename = 'index.tsx';
await fs.writeFile(
filename,
"// This is a generated file, don't update manually.\n\nimport { lazy } from 'react';\n\nimport { type Guide } from './types';\n"
);
-for (const { name } of metadata) {
+for (const { name, logo } of metadata) {
// eslint-disable-next-line no-await-in-loop
await fs.appendFile(filename, `import ${camelCase(name)} from './${name}/index';\n`);
+
+ if (logo && !logo.endsWith('.svg')) {
+ // eslint-disable-next-line no-await-in-loop
+ await fs.appendFile(filename, `import ${camelCase(name)}Logo from './${name}/${logo}';\n`);
+ }
}
await fs.appendFile(filename, '\n');
-await fs.appendFile(filename, 'const guides: Readonly = Object.freeze([');
+await fs.appendFile(filename, 'export const guides: Readonly = Object.freeze([');
+
+const getLogo = ({ name, logo }) => {
+ if (!logo) return 'undefined';
+ if (logo.endsWith('.svg')) return `lazy(async () => import('./${name}/${logo}'))`;
+ return `({ className }: { readonly className?: string }) => `;
+};
for (const { name, logo, order } of metadata) {
// eslint-disable-next-line no-await-in-loop
@@ -65,11 +75,11 @@ for (const { name, logo, order } of metadata) {
{
order: ${order},
id: '${name}',
- Logo: ${logo ? `lazy(async () => import('./${name}/${logo}'))` : 'undefined'},
+ Logo: ${getLogo({ name, logo })},
Component: lazy(async () => import('./${name}/README.mdx')),
metadata: ${camelCase(name)},
},`
);
}
-await fs.appendFile(filename, ']);\n\nexport default guides;\n');
+await fs.appendFile(filename, ']);\n');
diff --git a/packages/console/src/assets/docs/guides/index.ts b/packages/console/src/assets/docs/guides/index.tsx
similarity index 92%
rename from packages/console/src/assets/docs/guides/index.ts
rename to packages/console/src/assets/docs/guides/index.tsx
index a9a4f38a577..d83d09817d4 100644
--- a/packages/console/src/assets/docs/guides/index.ts
+++ b/packages/console/src/assets/docs/guides/index.tsx
@@ -12,6 +12,7 @@ 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 spaChromeExtension from './spa-chrome-extension/index';
import spaReact from './spa-react/index';
import spaVanilla from './spa-vanilla/index';
import spaVue from './spa-vue/index';
@@ -33,11 +34,19 @@ 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 webRuby from './web-ruby/index';
+import webRubyLogo from './web-ruby/logo.webp';
import webSveltekit from './web-sveltekit/index';
import webWordpress from './web-wordpress/index';
-const guides: Readonly = Object.freeze([
+export const guides: Readonly = Object.freeze([
+ {
+ order: 1,
+ id: 'web-next-app-router',
+ Logo: lazy(async () => import('./web-next-app-router/logo.svg')),
+ Component: lazy(async () => import('./web-next-app-router/README.mdx')),
+ metadata: webNextAppRouter,
+ },
{
order: 1.1,
id: 'native-expo',
@@ -52,6 +61,13 @@ const guides: Readonly = Object.freeze([
Component: lazy(async () => import('./spa-angular/README.mdx')),
metadata: spaAngular,
},
+ {
+ order: 1.1,
+ id: 'spa-chrome-extension',
+ Logo: lazy(async () => import('./spa-chrome-extension/logo.svg')),
+ Component: lazy(async () => import('./spa-chrome-extension/README.mdx')),
+ metadata: spaChromeExtension,
+ },
{
order: 1.1,
id: 'spa-react',
@@ -59,13 +75,6 @@ const guides: Readonly = Object.freeze([
Component: lazy(async () => import('./spa-react/README.mdx')),
metadata: spaReact,
},
- {
- order: 1.1,
- id: 'web-next-app-router',
- Logo: lazy(async () => import('./web-next-app-router/logo.svg')),
- Component: lazy(async () => import('./web-next-app-router/README.mdx')),
- metadata: webNextAppRouter,
- },
{
order: 1.2,
id: 'm2m-general',
@@ -115,13 +124,6 @@ const guides: Readonly = Object.freeze([
Component: lazy(async () => import('./web-java-spring-boot/README.mdx')),
metadata: webJavaSpringBoot,
},
- {
- order: 1.5,
- id: 'web-gpt-plugin',
- Logo: lazy(async () => import('./web-gpt-plugin/logo.svg')),
- Component: lazy(async () => import('./web-gpt-plugin/README.mdx')),
- metadata: webGptPlugin,
- },
{
order: 1.6,
id: 'spa-vue',
@@ -164,6 +166,15 @@ const guides: Readonly = Object.freeze([
Component: lazy(async () => import('./web-php/README.mdx')),
metadata: webPhp,
},
+ {
+ order: 2,
+ id: 'web-ruby',
+ Logo: ({ className }: { readonly className?: string }) => (
+
+ ),
+ Component: lazy(async () => import('./web-ruby/README.mdx')),
+ metadata: webRuby,
+ },
{
order: 2.1,
id: 'spa-webflow',
@@ -192,13 +203,6 @@ const guides: Readonly = Object.freeze([
Component: lazy(async () => import('./native-capacitor/README.mdx')),
metadata: nativeCapacitor,
},
- {
- order: 4,
- id: 'web-remix',
- Logo: lazy(async () => import('./web-remix/logo.svg')),
- Component: lazy(async () => import('./web-remix/README.mdx')),
- metadata: webRemix,
- },
{
order: 5,
id: 'native-flutter',
@@ -241,6 +245,13 @@ const guides: Readonly = Object.freeze([
Component: lazy(async () => import('./web-outline/README.mdx')),
metadata: webOutline,
},
+ {
+ order: 999,
+ id: 'web-gpt-plugin',
+ Logo: lazy(async () => import('./web-gpt-plugin/logo.svg')),
+ Component: lazy(async () => import('./web-gpt-plugin/README.mdx')),
+ metadata: webGptPlugin,
+ },
{
order: Number.POSITIVE_INFINITY,
id: 'api-express',
@@ -270,5 +281,3 @@ const guides: Readonly = Object.freeze([
metadata: thirdPartyOidc,
},
]);
-
-export default guides;
diff --git a/packages/console/src/assets/docs/guides/m2m-general/README.mdx b/packages/console/src/assets/docs/guides/m2m-general/README.mdx
index 97be012e070..21392ee3b24 100644
--- a/packages/console/src/assets/docs/guides/m2m-general/README.mdx
+++ b/packages/console/src/assets/docs/guides/m2m-general/README.mdx
@@ -1,10 +1,12 @@
-import Tabs from '@mdx/components/Tabs';
-import TabItem from '@mdx/components/TabItem';
+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 ApplicationCredentials from '@/mdx-components/ApplicationCredentials';
import AppIdentifierSrc from './assets/api-identifier.png';
+import AssignM2mRolesModalSrc from './assets/assign-m2m-roles-modal.png';
+import AssignM2mRolesPageSrc from './assets/assign-m2m-roles-page.png';
import LogtoManagementApiSrc from './assets/logto-management-api.png';
@@ -19,6 +21,14 @@ There are two common use cases of using machine-to-machine apps in Logto:
1. **Accessing Logto Management API**: In this case, you need to assign a M2M role that include the `all` permission from the built-in Logto Management API to your M2M app.
2. **Accessing your API resource**: In this case, you need to assign M2M roles that include permissions from your API resources to your M2M app.
+During the M2M app creation process, you’ll be directed to a page where you can assign machine-to-machine (M2M) roles to your applications:
+
+
+
+Or you can also assign these roles on the M2M app detail page.
+
+
+
@@ -67,6 +77,10 @@ The resource API indicator is in the pattern of `https://[your-tenant-id].logto.
Before accessing Logto Management API, make sure your M2M app has been assigned with M2M roles that include the `all` permission from this built-in “Logto Management API” resource.
+
+Logto also provides a pre-configured “Logto Management API access” M2M role for new created tenants, which the Logto Management API resource’s all permission has already assigned to. You can use it directly without manually setting permissions. This pre-configured role can also be edited and deleted as needed.
+
+
Now, compose all we have and send the request:
diff --git a/packages/console/src/assets/docs/guides/m2m-general/assets/assign-m2m-roles-modal.png b/packages/console/src/assets/docs/guides/m2m-general/assets/assign-m2m-roles-modal.png
new file mode 100644
index 00000000000..2ce64235f35
Binary files /dev/null and b/packages/console/src/assets/docs/guides/m2m-general/assets/assign-m2m-roles-modal.png differ
diff --git a/packages/console/src/assets/docs/guides/m2m-general/assets/assign-m2m-roles-page.png b/packages/console/src/assets/docs/guides/m2m-general/assets/assign-m2m-roles-page.png
new file mode 100644
index 00000000000..15ed9e768e5
Binary files /dev/null and b/packages/console/src/assets/docs/guides/m2m-general/assets/assign-m2m-roles-page.png differ
diff --git a/packages/console/src/assets/docs/guides/m2m-general/index.ts b/packages/console/src/assets/docs/guides/m2m-general/index.ts
index 00c1a10c629..4ad62a3e5b1 100644
--- a/packages/console/src/assets/docs/guides/m2m-general/index.ts
+++ b/packages/console/src/assets/docs/guides/m2m-general/index.ts
@@ -7,10 +7,7 @@ const metadata: Readonly = Object.freeze({
description: 'Enables direct communication between machines.',
target: ApplicationType.MachineToMachine,
isFeatured: true,
- fullGuide: {
- title: 'Full machine-to-machine integration tutorial',
- url: 'https://docs.logto.io/quick-starts/m2m',
- },
+ fullGuide: 'm2m',
});
export default metadata;
diff --git a/packages/console/src/assets/docs/guides/native-android/README.mdx b/packages/console/src/assets/docs/guides/native-android/README.mdx
index b6a966f5613..80020ff5ea7 100644
--- a/packages/console/src/assets/docs/guides/native-android/README.mdx
+++ b/packages/console/src/assets/docs/guides/native-android/README.mdx
@@ -2,6 +2,8 @@ import UriInputField from '@/mdx-components/UriInputField';
import InlineNotification from '@/ds-components/InlineNotification';
import Steps from '@/mdx-components/Steps';
import Step from '@/mdx-components/Step';
+import RedirectUrisNative from '../../fragments/_redirect-uris-native.mdx';
+import Checkpoint from '../../fragments/_checkpoint.md';
@@ -14,17 +16,17 @@ import Step from '@/mdx-components/Step';
Before you install Logto Android SDK, ensure `mavenCentral()` is added to your repository configuration in the Gradle project build file:
-```kotlin
-dependencyResolutionManagement {
+
+{`dependencyResolutionManagement {
repositories {
mavenCentral()
}
-}
-```
+}`}
+
Add Logto Android SDK to your dependencies:
-```kotlin
+```kotlin title="build.gradle.kts"
dependencies {
implementation("io.logto.sdk:android:1.1.3")
}
@@ -32,7 +34,7 @@ dependencies {
Since the SDK needs internet access, you need to add the following permission to your `AndroidManifest.xml` file:
-```xml
+```xml title="AndroidManifest.xml"
@@ -46,18 +48,14 @@ Since the SDK needs internet access, you need to add the following permission to
-
+We use Kotlin in this example, but the concepts are the same for Java.
Create a `LogtoViewModel.kt` and init `LogtoClient` in this view model:
-
-
- {`//...with other imports
+```kotlin title="LogtoViewModel.kt"
+//...with other imports
import io.logto.sdk.android.LogtoClient
import io.logto.sdk.android.type.LogtoConfig
@@ -85,13 +83,12 @@ class LogtoViewModel(application: Application) : AndroidViewModel(application) {
}
}
}
-}`}
-
-
+}
+```
then, create a `LogtoViewModel` for your `MainActivity.kt`:
-```kotlin
+```kotlin title="MainActivity.kt"
//...with other imports
class MainActivity : AppCompatActivity() {
private val logtoViewModel: LogtoViewModel by viewModels { LogtoViewModel.Factory }
@@ -101,12 +98,9 @@ class MainActivity : AppCompatActivity() {
-
+
-Before starting, you need to add a redirect URI in the Admin Console for your application.
+
In Android, the redirect URI follows the pattern: `$(LOGTO_REDIRECT_SCHEME)://$(YOUR_APP_PACKAGE)/callback`:
@@ -115,98 +109,15 @@ In Android, the redirect URI follows the pattern: `$(LOGTO_REDIRECT_SCHEME)://$(
Assuming you treat `io.logto.android` as the custom `LOGTO_REDIRECT_SCHEME`, and `io.logto.sample` is your app package name, the Redirect URI should be `io.logto.android://io.logto.sample/callback`.
-You can add the redirect URI in the following input field:
-
-
-
-After the redirect URI is configured, we add a `signIn` method to your `LogtoViewModel.kt`, which will call `logtoClient.signIn` API to invoke the Logto sign-in page:
-
-
-
-Now setup on-click listener for the sign-in button in your `MainActivity.kt` to call the `signIn` method:
-
-```kotlin
-//...with other imports
-class MainActivity : AppCompatActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- //...other codes
-
- // Assume you have a button with id `sign_in_button` in your layout
- val signInButton = findViewById
-
-
-Similar to sign-in, we add a `signOut` method to `LogtoViewModel.kt` to call `logtoClient.signOut` API:
+
-```kotlin
-//...with other imports
-class LogtoViewModel(application: Application) : AndroidViewModel(application) {
- // ...other codes
- fun signOut() {
- logtoClient.signOut { logtoException ->
- logtoException?.let { println(it) }
- }
- }
-}
-```
-
-After you signed out, the Logto SDK will clear all local credentials even though Logto exceptions occurred when calling `logtoClient.signOut` API.
+After the redirect URI is configured, we can use `logtoClient.signIn` to sign in the user and `logtoClient.signOut` to sign out the user.
-Then, we can add a button to call the `signOut` method in `MainActivity.kt`:
+Now let's use them in your `LogtoViewModel.kt`:
-```kotlin
-//...with other imports
-class MainActivity : AppCompatActivity() {
- //...other codes
- override fun onCreate(savedInstanceState: Bundle?) {
- //...other codes
- //...sign-in button codes
-
- // Assume you have a button with id `sign_out_button` in your layout
- val signOutButton = findViewById
-
-
-
-In Logto SDK, we can use `logtoClient.isAuthenticated` to check the authentication status, if the user is signed in, the value will be `true`, otherwise, the value will be `false`.
-
-Now, let's add a live data to `LogtoViewModel.kt` to observe the authentication status, and update the status when the user signed in or signed out:
-
-
-
+
{`//...with other imports
class LogtoViewModel(application: Application) : AndroidViewModel(application) {
// ...other codes
@@ -232,22 +143,31 @@ class LogtoViewModel(application: Application) : AndroidViewModel(application) {
}
}
}`}
-
-
+
-Then, we observe the `authenticated` live data in `MainActivity.kt`, when the user is signed in, we hide the sign-in button and show the sign-out button and vice versa:
+Now setup on-click listener for the sign-in button and sign-out button in your `MainActivity.kt`:
-```kotlin
+```kotlin title="MainActivity.kt"
//...with other imports
class MainActivity : AppCompatActivity() {
- //...other codes
override fun onCreate(savedInstanceState: Bundle?) {
//...other codes
+
+ // Assume you have a button with id "sign_in_button" in your layout
val signInButton = findViewById
-
+
+
+
+
+
+
+
+
+
+To display the user's information, you can use the `getIdTokenClaims` method to get user information. For example, you can get user information in a ViewModel and then display it in your activity:
-Now, you can test your application:
+```kotlin title="LogtoViewModel.kt"
+//...with other imports
+class LogtoViewModel(application: Application) : AndroidViewModel(application) {
+ // ...other codes
+
+ // Add a live data to observe the id token claims
+ private val _idTokenClaims = MutableLiveData()
+ val idTokenClaims: LiveData
+ get() = _idTokenClaims
-1. Run your application, you will see the sign-in button.
-2. Click the sign-in button, the Logto SDK will navigate to the Logto sign-in page.
-3. After you signed in, you will be redirect back to your application and see the sign-out button.
-4. Click the sign-out button, you will see the sign-in button again.
+ fun getIdTokenClaims() {
+ logtoClient.getIdTokenClaims { logtoException, idTokenClaims ->
+ logtoException?.let { _logtoException.postValue(it) } ?: _idTokenClaims.postValue(idTokenClaims)
+ }
+ }
+}
+```
+
+```kotlin title="MainActivity.kt"
+//...with other imports
+class MainActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ //...other codes
+
+ // Assume you have a text View with id `user_info_text_view` in your layout
+ val userInfoResponseTextView: TextView = findViewById(R.id.user_info_text_view)
+ logtoViewModel.userInfoResponse.observe(this) { userInfoResponse ->
+ userInfoResponseTextView.text = if (userInfoResponse !== null) {
+ val json = Gson().toJson(userInfoResponse, UserInfoResponse::class.java)
+ JSONObject(json).toString(2)
+ } else {
+ ""
+ }
+ }
+ }
+}
+```
diff --git a/packages/console/src/assets/docs/guides/native-android/index.ts b/packages/console/src/assets/docs/guides/native-android/index.ts
index de711ff81c1..a248a0d35e1 100644
--- a/packages/console/src/assets/docs/guides/native-android/index.ts
+++ b/packages/console/src/assets/docs/guides/native-android/index.ts
@@ -10,10 +10,7 @@ const metadata: Readonly = Object.freeze({
repo: 'kotlin',
path: 'android-sample-kotlin',
},
- fullGuide: {
- title: 'Full Android SDK tutorial',
- url: 'https://docs.logto.io/quick-starts/android',
- },
+ fullGuide: 'android',
});
export default metadata;
diff --git a/packages/console/src/assets/docs/guides/native-capacitor/README.mdx b/packages/console/src/assets/docs/guides/native-capacitor/README.mdx
index ab26eae2e78..72879aa4977 100644
--- a/packages/console/src/assets/docs/guides/native-capacitor/README.mdx
+++ b/packages/console/src/assets/docs/guides/native-capacitor/README.mdx
@@ -1,25 +1,27 @@
import UriInputField from '@/mdx-components/UriInputField';
import Steps from '@/mdx-components/Steps';
import Step from '@/mdx-components/Step';
+import NpmLikeInstallation from '@/mdx-components/NpmLikeInstallation';
import capaticorIos from './assets/capacitor-ios.webp';
import logtoSignInPage from './assets/logto-sign-in-page.webp';
import logtoSignOutPage from './assets/logto-sign-out-page.webp';
+import RedirectUrisNative from '../../fragments/_redirect-uris-native.mdx';
+
+export const defaultRedirectUri = 'com.example.app://callback';
+export const defaultPostSignOutUri = 'com.example.app://callback/sign-out';
-
+
To get started, you need to create a Capacitor project. You can follow the [official guide](https://capacitorjs.com/docs/getting-started) to create one.
This tutorial is framework-agnostic, so you can use any UI framework you prefer and update the code accordingly.
-```bash
-# or yarn, pnpm
-npm install @logto/capacitor
-# Install peer dependencies
-npm install @capacitor/browser @capacitor/app @capacitor/preferences
-```
+First, install Logto Capacitor SDK and its peer dependencies:
+
+
@@ -27,42 +29,36 @@ npm install @capacitor/browser @capacitor/app @capacitor/preferences
Add the following code to your Capacitor project:
-
-
+
{`import LogtoClient from '@logto/capacitor';
const logtoClient = new LogtoClient({
endpoint: '${props.endpoint}',
appId: '${props.app.id}',
});`}
-
-
+
-
+
+
+
-Now, let's configure the redirect URI. The redirect URI is used to redirect the user back to your application after the authentication flow.
+Ensure that the URI redirects to the Capacitor app. The value may vary depending on your Capacitor app configuration. For more details, see [Capacitor Deep Links](https://capacitorjs.com/docs/guides/deep-links).
-Ensure that the URI redirects to the Capacitor app, for example, `com.example.app://callback`. The value may vary depending on your Capacitor app configuration. For more details, see [Capacitor Deep Links](https://capacitorjs.com/docs/guides/deep-links).
+Remember to click on **Save changes** after updating the redirect URI.
-
+
-Remember to click on **Save changes** after updating the redirect URI. Then, add the following code to the `onClick` handler of the sign-in button:
+
-
-
-
-
-
+
Run the Capacitor app and click the sign-in button. A browser window will open, redirecting to the Logto sign-in page.
@@ -80,7 +76,7 @@ Run the Capacitor app and click the sign-in button. A browser window will open,
-
+
Since Capacitor leverages the Safari View Controller on iOS and Chrome Custom Tabs on Android, the authentication state can be persisted for a while. However, sometimes the user may want to sign out of the application immediately. In this case, we can use the `signOut` method to sign out the user:
@@ -108,19 +104,17 @@ The user needs to click "Done" to close the web view and return to the Capacitor
Ensure that the post sign-out redirect URI redirects to the Capacitor app. Then add the following code to the `onClick` handler of the sign-out button:
-
+
-Run the Capacitor app again and click the sign-in button. If everything goes well, when the authentication flow is completed, the Capacitor app will receive the sign-in result and print the user claims in the console.
+Run the Capacitor app click the sign-in button. If everything goes well, when the authentication flow is completed, the Capacitor app will receive the sign-in result and print the user claims in the console.
diff --git a/packages/console/src/assets/docs/guides/native-expo/README.mdx b/packages/console/src/assets/docs/guides/native-expo/README.mdx
index bd20cce05ff..661fbaa8a09 100644
--- a/packages/console/src/assets/docs/guides/native-expo/README.mdx
+++ b/packages/console/src/assets/docs/guides/native-expo/README.mdx
@@ -1,46 +1,19 @@
import UriInputField from '@/mdx-components/UriInputField';
-import Tabs from '@mdx/components/Tabs';
-import TabItem from '@mdx/components/TabItem';
+import NpmLikeInstallation from '@/mdx-components/NpmLikeInstallation';
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 RedirectUrisNative, { defaultRedirectUri } from '../../fragments/_redirect-uris-native.mdx';
-
-
-
-
-```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`.
@@ -56,8 +29,7 @@ If you're installing this in a [bare React Native app](https://docs.expo.dev/bar
Import and use `LogtoProvider` to provide a Logto context:
-
+
-
+
-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
+ ```json title="app.json"
{
"expo": {
"scheme": "io.logto"
@@ -100,8 +69,7 @@ The redirect URI is used to redirect the user back to your app after they sign i
You can use `useLogto` hook to sign in and sign out:
-
+
-
+
-
+For Expo projects, if you encounter the error `Unable to resolve "@logto/client/shim" from "node_modules/@logto/rn/lib/index.js"`, you can resolve it by adding the following to your `metro.config.js` file:
+
+```js title="metro.config.js"
+const config = {
+ // ...
+ resolver: {
+ unstable_enablePackageExports: true,
+ },
+};
+
+module.exports = config;
+```
+
+This error indicates that `@logto/rn` package is not able to resolve the `@logto/client/shim` module.
+
+As the node exports were used in the`@logto/client` package, and package exports are not enabled by default in Metro bundler, you need to enable them manually.
+
+See the [React Native package exports support](https://reactnative.dev/blog/2023/06/21/package-exports-support#enabling-package-exports-beta) for more details.
diff --git a/packages/console/src/assets/docs/guides/native-expo/index.ts b/packages/console/src/assets/docs/guides/native-expo/index.ts
index 2ea3ef2620c..353c64cc661 100644
--- a/packages/console/src/assets/docs/guides/native-expo/index.ts
+++ b/packages/console/src/assets/docs/guides/native-expo/index.ts
@@ -10,10 +10,7 @@ const metadata: Readonly = Object.freeze({
repo: 'react-native',
path: 'packages/rn-sample',
},
- fullGuide: {
- title: 'Full Expo (React Native) guide',
- url: 'https://docs.logto.io/quick-starts/expo',
- },
+ fullGuide: 'expo',
});
export default metadata;
diff --git a/packages/console/src/assets/docs/guides/native-flutter/README.mdx b/packages/console/src/assets/docs/guides/native-flutter/README.mdx
index c192872ef98..1a4f16d6b3e 100644
--- a/packages/console/src/assets/docs/guides/native-flutter/README.mdx
+++ b/packages/console/src/assets/docs/guides/native-flutter/README.mdx
@@ -1,12 +1,22 @@
import UriInputField from '@/mdx-components/UriInputField';
import Steps from '@/mdx-components/Steps';
import Step from '@/mdx-components/Step';
-import Tabs from '@mdx/components/Tabs';
-import TabItem from '@mdx/components/TabItem';
+import Tabs from '@/mdx-components/Tabs';
+import TabItem from '@/mdx-components/TabItem';
import InlineNotification from '@/ds-components/InlineNotification';
+import Checkpoint from '../../fragments/_checkpoint.md';
+import RedirectUrisNative, { defaultRedirectUri } from '../../fragments/_redirect-uris-native.mdx';
-
+
+
+
+
+The Logto Flutter SDK is compatible with Android and iOS platforms only.
+
+For Dart v2.x users, please use Logto Flutter SDK v1.x. Logto Flutter SDK v2.x requires Dart v3.0.0 or higher.
+
+
@@ -16,290 +26,246 @@ You can install the `logto_dart_sdk package` directly using the pub package mana
Run the following command under your project root:
```sh
- flutter pub get logto_dart_sdk
+flutter pub get logto_dart_sdk
```
-
+
If you prefer to fork your own version of the SDK, you can clone the repository directly from GitHub.
```sh
- git clone https://github.com/logto-io/dart
+git clone https://github.com/logto-io/dart
```
-
-
-
+### Dependencies and Android settings
-
+flutter_secure_storage
-
-
We use [flutter_secure_storage](https://pub.dev/packages/flutter_secure_storage) to implement the cross-platform persistent secure token storage.
- Keychain is used for iOS
- AES encryption is used for Android.
-### Config Android version:
+**Config Android version**
-In `[project]/android/app/build.gradle` set minSdkVersion to >= 18.
+Set the `android:minSdkVersion` to 18 in your project's `android/app/build.gradle` file.
-```gradle
+```kotlin title="android/app/build.gradle"
android {
- ...
-
- defaultConfig {
- ...
- minSdkVersion 18
- ...
- }
+ {/* ... */}
+ defaultConfig {
+ {/* ... */}
+ minSdkVersion 18
+ }
}
```
+**Disable autobackup**
-### Disable autobackup:
-
-
- By default Android backups data on Google Drive. It can cause exception
- `java.security.InvalidKeyException:Failed` to unwrap key.
-
- You will need to either disable `autobackup` or exclude `sharedprefs` used by the
- FlutterSecureStorage plugin.
-
-
-1. To disable `autobackup`, go to your app manifest file and set the boolean value `android:allowBackup`:
+By default Android may backup data on Google Drive automatically. It can cause the exception `java.security.InvalidKeyException: Failed to unwrap key`.
- ```xml
-
- ...
-
- ...
-
-
+To avoid this, you can disable auto backup for your app or exclude `sharedprefs` from the `FlutterSecureStorage`.
- ```
+1. To disable auto backup, go to your app manifest file and set the `android:allowBackup` and `android:fullBackupContent` attributes to `false`.
-2. To exclude `sharedprefs` for FlutterSecureStorage.
+ ```xml title="AndroidManifest.xml"
+
+
+
+
+
+
+ ```
- If you need to enable the `android:fullBackupContent` for your app. Set up a backup rule to [exclude](https://developer.android.com/guide/topics/data/autobackup#IncludingFiles) the prefs used by the plugin:
+2. Exclude `sharedprefs` from `FlutterSecureStorage`.
- ```xml
-
-
- ```
+ If you need to keep the `android:fullBackupContent` for your app rather than disabling it, you can exclude the `sharedprefs` directory from the backup.
- ```xml
-
-
-
-
- ```
+ See more details in the [Android documentation](https://developer.android.com/identity/data/autobackup#IncludingFiles).
-Please check [flutter_secure_storage](https://pub.dev/packages/flutter_secure_storage#configure-android-version) for more details.
+ In your `AndroidManifest.xml` file, add the `android:fullBackupContent` attribute to the `` element, as shown in the following example. This attribute points to an XML file that contains backup rules.
-
+ ```xml title="AndroidManifest.xml"
+
+
+
+
+
+
+ ```
-
+ Create an XML file called `@xml/backup_rules` in the `res/xml/` directory. In this file, add rules with the `` and `` elements. The following sample backs up all shared preferences except device.xml:
-
+ ```xml title="res/xml/backup_rules.xml"
+
+
+
+
+ ```
-flutter_web_auth
-
-
-
-[flutter_web_auth](https://pub.dev/packages/flutter_web_auth) is used behind Logto's flutter SDK. We rely on its webview-based interaction interface to open Logto's authorization pages.
-
-
+Please check [flutter_secure_storage](https://pub.dev/packages/flutter_secure_storage#configure-android-version) for more details.
-Under the hood, this plugin uses `ASWebAuthenticationSession` on iOS 12+ and macOS 10.15+,
-`SFAuthenticationSession` on iOS 11, Chrome Custom Tabs on Android and opens a new window on Web.
-You can build it with iOS 8+, but it is currently only supported by iOS 11 or higher.
+
-
+
-Andorid:
+flutter_web_auth
-In order to capture the callback url from Logto's sign-in web page, you will need to register your sign-in redirectUri to the `AndroidManifest.xml`.
+[flutter_web_auth](https://pub.dev/packages/flutter_web_auth) is used behind Logto's flutter SDK. We rely on its webview-based interaction interface to authenticate users.
-```xml
-
-
-
-
-
-
-
-
-```
+This plugin uses `ASWebAuthenticationSession` on iOS 12+ and macOS 10.15+, `SFAuthenticationSession` on iOS 11, `Chrome Custom Tabs` on Android and opens a new window on Web.
-
-
-
+
-
+
-Import the `logto_dart_sdk` package and initialize the `LogtoClient` instance at the root of your application.
+Import the `logto_dart_sdk` package and initialize the `LogtoClient` instance at the root state of your application.
-
-
-
-
-### Configure Redirect URI
-
-
- In the following steps, we assume your app has configured using `io.logo` as your schema .
-
+class _MyHomePageState extends State {
+ final logtoConfig = LogtoConfig(
+ endpoint: 'your_logto_endpoint', // Replace with your Logto endpoint
+ appId: 'your_app_id', // Replace with your App ID
+ );
-Let's switch to the Application details page of Logto Admin Console. Add a Redirect URI `io.logto://callback` and click "Save changes".
+ late LogtoClient logtoClient;
-
+ void _init() async {
+ logtoClient = LogtoClient(
+ config: logtoConfig,
+ httpClient: http.Client(), // Optional: Custom HTTP client
+ );
+ }
-### Implement a sign-in method
+ @override
+ void initState() {
+ super.initState();
+ _init();
+ }
-
+ // ...
+}`}
+
-
+
-### Implement a sign-out method
+
-```dart
-void signOut() async {
- await logtoClient.signOut();
-}
-```
+- For iOS, the redirect URI scheme does not really matter since the iOS will listen to the redirect URI regardless of if it's registered.
-
- The `signOut` method will clear the user's session and remove the token from the secure storage.
-
+- For Android, in order to capture the callback url from Logto's sign-in web page, you will need to register your sign-in redirectUri to the `AndroidManifest.xml`.
-
-
-
-
-In Logto SDK, you can use `logtoClient.isAuthenticated` to check the authentication status, if the
-user is signed in, the value will be `true`, otherwise, the value will be `false`.
-
-```dart
- bool isAuthenticated = await logtoClient.isAuthenticated;
+```xml title="AndroidManifest.xml"
+
+
+
+
+
+
+
+
```
-
-
+
-Now let's wrap up the implementation and test your application.
+Let's implement the `signIn` and `signOut` buttons in your application.
-
- // Update the authentication state
- void render() {
- setState(() async {
- isAuthenticated = await logtoClient.isAuthenticated;
- });
- }
+
- // LogtoConfig
- final logtoConfig = const LogtoConfig(
- endpoint: '${props.endpoint}',
- appId: '${props.app.id}',
- );
+In Logto SDK, you can use `logtoClient.isAuthenticated` to check the authentication status, if the user is signed in, the value will be `true`, otherwise, the value will be `false`.
- void _init() {
- logtoClient = LogtoClient(
- config: logtoConfig,
- httpClient: http.Client(), // Optional http client
- );
- render();
- }
+Define a boolean variable `isAuthenticated` in the state of your application to keep track of the authentication status.
- @override
- void initState() {
- super.initState();
- _init();
+```dart title="lib/main.dart"
+class _MyHomePageState extends State{
+ bool isAuthenticated = false;
+
+ // ...
+
+ void render() async {
+ if (await logtoClient.isAuthenticated()) {
+ setState(() {
+ isAuthenticated = true;
+ });
+
+ return;
+ }
+
+ setState(() {
+ isAuthenticated = false;
+ });
}
- @override
+ @overwrite
Widget build(BuildContext context) {
- Widget signInButton = TextButton(
+ // ...
+
+ Widget signInButton = TextButton(
onPressed: () async {
- await logtoClient.signIn('${props.redirectUris[0] ?? 'io.logto://callback'}');
+ await logtoClient.signIn(redirectUri);
render();
},
child: const Text('Sign In'),
@@ -321,7 +287,6 @@ class _MyHomePageState extends State {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
- SelectableText('My Demo App'),
isAuthenticated ? signOutButton : signInButton,
],
),
@@ -329,9 +294,13 @@ class _MyHomePageState extends State {
);
}
}
-`}
-
-
+```
+
+
+
+
+
+
diff --git a/packages/console/src/assets/docs/guides/native-ios-swift/README.mdx b/packages/console/src/assets/docs/guides/native-ios-swift/README.mdx
index 67cf5da0fc1..126993641bf 100644
--- a/packages/console/src/assets/docs/guides/native-ios-swift/README.mdx
+++ b/packages/console/src/assets/docs/guides/native-ios-swift/README.mdx
@@ -1,9 +1,11 @@
import UriInputField from '@/mdx-components/UriInputField';
-import Tabs from '@mdx/components/Tabs';
-import TabItem from '@mdx/components/TabItem';
+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 RedirectUrisNative, { defaultRedirectUri } from '../../fragments/_redirect-uris-native.mdx';
@@ -22,20 +24,20 @@ Since Xcode 11, you can [directly import a swift package](https://developer.appl
We do not support **Carthage** and **CocoaPods** at the time due to some technical issues.
-
+Carthage
Carthage [needs a `xcodeproj` file to build](https://github.com/Carthage/Carthage/issues/1226#issuecomment-290931385), but `swift package generate-xcodeproj` will report a failure since we are using binary targets
for native social plugins. We will try to find a workaround later.
-
+
-
+CocoaPods
CocoaPods [does not support local dependency](https://github.com/CocoaPods/CocoaPods/issues/3276) and monorepo, thus it's hard to create a `.podspec` for this repo.
-
+
@@ -44,8 +46,9 @@ CocoaPods [does not support local dependency](https://github.com/CocoaPods/Cocoa
subtitle="1 step"
>
-
-
+You can initialize `LogtoClient` in a proper place of your app that can be accessed globally:
+
+
{`import Logto
import LogtoClient
@@ -54,8 +57,7 @@ let config = try? LogtoConfig(
appId: "${props.app.id}"
)
let logtoClient = LogtoClient(useConfig: config)`}
-
-
+
By default, we store credentials like ID Token and Refresh Token in Keychain. Thus the user doesn't need to sign in again when he returns.
@@ -70,52 +72,93 @@ let config = try? LogtoConfig(
-
-
-### Configure Redirect URI
+
-First, let’s configure your redirect URI scheme. E.g. `io.logto://callback`
-
-
+
- The Redirect URI in iOS SDK is only for internal use. There's NO NEED to add a{' '}
-
- Custom URL Scheme
- {' '}
- until a connector asks.
+ The Redirect URI in iOS SDK is only for internal use. There's NO NEED to add a [Custom URL Scheme](https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app) until a connector asks.
-Go back to Xcode, use the following code to implement sign-in:
-
-
-
- {`do {
- try await client.signInWithBrowser(redirectUri: "${
- props.redirectUris[0] ?? 'io.logto://callback'
- }")
- print(client.isAuthenticated) // true
-} catch let error as LogtoClientErrors.SignIn {
- // error occured during sign in
+
+
+
+
+You can use `client.signInWithBrowser(redirectUri:)` to sign in the user and `client.signOut()` to sign out the user.
+
+For example, in a SwiftUI app:
+
+
+ {`struct ContentView: View {
+ @State var isAuthenticated: Bool
+
+ init() {
+ isAuthenticated = client.isAuthenticated
+ }
+
+ var body: some View {
+ VStack {
+ if isAuthenticated {
+ Button("Sign Out") {
+ Task { [self] in
+ await client.signOut()
+ isAuthenticated = false
+ }
+ }
+ } else {
+ Button("Sign In") {
+ Task { [self] in
+ do {
+ try await client.signInWithBrowser(redirectUri: "${
+ props.redirectUris[0] ?? defaultRedirectUri
+ }")
+ isAuthenticated = true
+ } catch let error as LogtoClientErrors.SignIn {
+ // error occured during sign in
+ } catch {
+ // other errors
+ }
+ }
+ }
+ }
+ }
+ }
}`}
-
-
+
-
+
-Calling `.signOut()` will clean all the Logto data in Keychain, if they exist.
+
-```swift
-await client.signOut()
-```
+
+
+
+
+To display the user's information, you can use the `client.getIdTokenClaims()` method. For example, in a SwiftUI app:
+
+
+ {`struct ContentView: View {
+ @State var isAuthenticated: Bool
+ @State var name: String?
+
+ init() {
+ isAuthenticated = client.isAuthenticated
+ name = try? client.getIdTokenClaims().name
+ }
+
+ var body: some View {
+ VStack {
+ if isAuthenticated {
+ Text("Welcome, \(name)")
+ } else {
+ Text("Please sign in")
+ }
+ }
+ }
+}`}
+
diff --git a/packages/console/src/assets/docs/guides/native-ios-swift/index.ts b/packages/console/src/assets/docs/guides/native-ios-swift/index.ts
index 80838026a19..51b991d5965 100644
--- a/packages/console/src/assets/docs/guides/native-ios-swift/index.ts
+++ b/packages/console/src/assets/docs/guides/native-ios-swift/index.ts
@@ -10,7 +10,7 @@ const metadata: Readonly = Object.freeze({
repo: 'swift',
path: 'Demos/SwiftUI%20Demo',
},
- isFeatured: true,
+ fullGuide: 'swift',
});
export default metadata;
diff --git a/packages/console/src/assets/docs/guides/spa-angular/README.mdx b/packages/console/src/assets/docs/guides/spa-angular/README.mdx
index 095f3e08643..bc74fb49dc7 100644
--- a/packages/console/src/assets/docs/guides/spa-angular/README.mdx
+++ b/packages/console/src/assets/docs/guides/spa-angular/README.mdx
@@ -1,65 +1,34 @@
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 NpmLikeInstallation from '@/mdx-components/NpmLikeInstallation';
import Checkpoint from '../../fragments/_checkpoint.md';
+import RedirectUrisWeb, {defaultRedirectUri, defaultPostSignOutUri} from '../../fragments/_redirect-uris-web.mdx';
-
-
-```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.
+In your Angular project, add the auth provider your `app.config.ts`:
-
-
-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`:
-
-
+
-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.
+In the component where you want to implement sign-in and sign-out, inject the `OidcSecurityService` and use it to sign in and sign out.
-```ts
+```ts title="app/app.component.ts"
import { OidcSecurityService } from 'angular-auth-oidc-client';
export class AppComponent implements OnInit {
@@ -114,11 +82,17 @@ Then, in the template, add buttons to sign in and sign out:
-
+
+
+
+
+
+
+
The `OidcSecurityService` provides a convenient way to subscribe to the authentication state:
-```ts
+```ts title="app/app.component.ts"
import { OidcSecurityService } from 'angular-auth-oidc-client';
import type { UserInfoResponse } from '@logto/js';
@@ -148,10 +122,11 @@ export class AppComponent implements OnInit {
And use it in the template:
-```html
+```html title="app/app.component.html"
Sign in
{{ userData | json }}
+
ID token: {{ idToken }}
Access token: {{ accessToken }}
Sign out
@@ -160,12 +135,4 @@ And use it in the template:
-
-
-
-
-
-
diff --git a/packages/console/src/assets/docs/guides/spa-angular/index.ts b/packages/console/src/assets/docs/guides/spa-angular/index.ts
index ef02c1ced7d..3073c73b72a 100644
--- a/packages/console/src/assets/docs/guides/spa-angular/index.ts
+++ b/packages/console/src/assets/docs/guides/spa-angular/index.ts
@@ -10,10 +10,7 @@ const metadata: Readonly = Object.freeze({
repo: 'js',
path: 'packages/angular-sample',
},
- fullGuide: {
- title: 'Full Angular guide',
- url: 'https://docs.logto.io/quick-starts/angular',
- },
+ fullGuide: 'angular',
});
export default metadata;
diff --git a/packages/console/src/assets/docs/guides/spa-chrome-extension/README.mdx b/packages/console/src/assets/docs/guides/spa-chrome-extension/README.mdx
new file mode 100644
index 00000000000..98d22ecee13
--- /dev/null
+++ b/packages/console/src/assets/docs/guides/spa-chrome-extension/README.mdx
@@ -0,0 +1,238 @@
+import UriInputField from '@/mdx-components/UriInputField';
+import InlineNotification from '@/ds-components/InlineNotification';
+import Steps from '@/mdx-components/Steps';
+import Step from '@/mdx-components/Step';
+import NpmLikeInstallation from '@/mdx-components/NpmLikeInstallation';
+
+import RegardingRedirectBasedSignIn from '../../fragments/_regarding-redirect-based-sign-in.md';
+
+import extensionPopup from './extension-popup.webp';
+
+
+
+
+
+
+
+
+
+
+
+Assuming you put a "Sign in" button in your Chrome extension's popup, the authentication flow will look like this:
+
+```mermaid
+sequenceDiagram
+ participant A as Extension popup
+ participant B as Extension service worker
+ participant C as Logto sign-in experience
+
+ A->>B: Invokes sign-in
+ B->>C: Redirects to Logto
+ C->>C: User signs in
+ C->>B: Redirects back to extension
+ B->>A: Notifies the popup
+```
+
+For other interactive pages in your extension, you just need to replace the `Extension popup` participant with the page's name. In this tutorial, we will focus on the popup page.
+
+
+
+
+
+
+
+### Update the `manifest.json`
+
+Logto SDK requires the following permissions in the `manifest.json`:
+
+```json title="manifest.json"
+{
+ "permissions": ["identity", "storage"],
+ "host_permissions": ["https://*.logto.app/*"]
+}
+```
+
+- `permissions.identity`: Required for the Chrome Identity API, which is used to sign in and sign out.
+- `permissions.storage`: Required for storing the user's session.
+- `host_permissions`: Required for the Logto SDK to communicate with the Logto APIs.
+
+
+If you are using a custom domain on Logto Cloud, you need to update the `host_permissions` to match your domain.
+
+
+### Set up a background script (service worker)
+
+In your Chrome extension's background script, initialize the Logto SDK:
+
+```js title="service-worker.js"
+import LogtoClient from '@logto/chrome-extension';
+
+export const logtoClient = new LogtoClient({
+ endpoint: ''
+ appId: '',
+});
+```
+
+Replace `` and `` with the actual values. You can find these values in the application page you just created in the Logto Console.
+
+If you don't have a background script, you can follow the [official guide](https://developer.chrome.com/docs/extensions/develop/concepts/service-workers/basics) to create one.
+
+
+**Why do we need a background script?**
+
+Normal extension pages like the popup or options page can't run in the background, and they have the possibility to be closed during the authentication process. A background script ensures the authentication process can be properly handled.
+
+
+Then, we need to listen to the message from other extension pages and handle the authentication process:
+
+```js title="service-worker.js"
+chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
+ // In the below code, since we return `true` for each action, we need to call `sendResponse`
+ // to notify the sender. You can also handle errors here, or use other ways to notify the sender.
+
+ if (message.action === 'signIn') {
+ const redirectUri = chrome.identity.getRedirectURL('/callback');
+ logtoClient.signIn(redirectUri).finally(sendResponse);
+ return true;
+ }
+
+ if (message.action === 'signOut') {
+ const redirectUri = chrome.identity.getRedirectURL();
+ logtoClient.signOut(redirectUri).finally(sendResponse);
+ return true;
+ }
+
+ return false;
+});
+```
+
+You may notice there are two redirect URIs used in the code above. They are both created by `chrome.identity.getRedirectURL`, which is a [built-in Chrome API](https://developer.chrome.com/docs/extensions/reference/api/identity#method-getRedirectURL) to generate a redirect URL for auth flows. The two URIs will be:
+
+- `https://.chromiumapp.org/callback` for sign-in.
+- `https://.chromiumapp.org/` for sign-out.
+
+Note that these URIs are not accessible, and they are only used for Chrome to trigger specific actions for the authentication process.
+
+
+
+
+
+As we mentioned in the previous step, we need to update the Logto application settings to allow the redirect URIs we just created (`https://.chromiumapp.org/callback`):
+
+
+
+And the post sign-out redirect URI (`https://.chromiumapp.org/`):
+
+
+
+Finally, the CORS allowed origins should include the extension's origin (`chrome-extension://`). The SDK in Chrome extension will use this origin to communicate with the Logto APIs.
+
+
+
+Don't forget to replace `` with your actual extension ID and click the "Save" button.
+
+
+
+
+
+We're almost there! Let's add the sign-in and sign-out buttons and other necessary logic to the popup page.
+
+In the `popup.html` file:
+
+```html title="popup.html"
+Sign inSign out
+```
+
+In the `popup.js` file (assuming `popup.js` is included in the `popup.html`):
+
+```js title="popup.js"
+document.getElementById('sign-in').addEventListener('click', async () => {
+ await chrome.runtime.sendMessage({ action: 'signIn' });
+ // Sign-in completed (or failed), you can update the UI here.
+});
+
+document.getElementById('sign-out').addEventListener('click', async () => {
+ await chrome.runtime.sendMessage({ action: 'signOut' });
+ // Sign-out completed (or failed), you can update the UI here.
+});
+```
+
+
+
+
+
+Now you can test the authentication flow in your Chrome extension:
+
+1. Open the extension popup.
+2. Click on the "Sign in" button.
+3. You will be redirected to the Logto sign-in page.
+4. Sign in with your Logto account.
+5. You will be redirected back to the Chrome.
+
+
+
+
+
+Since Chrome provide unified storage APIs, rather than the sign-in and sign-out flow, all other Logto SDK methods can be used in the popup page directly.
+
+In your `popup.js`, you can reuse the `LogtoClient` instance created in the background script, or create a new one with the same configuration:
+
+```js title="popup.js"
+import LogtoClient from '@logto/chrome-extension';
+
+const logtoClient = new LogtoClient({
+ endpoint: ''
+ appId: '',
+});
+
+// Or reuse the logtoClient instance created in the background script
+import { logtoClient } from './service-worker.js';
+```
+
+Then you can create a function to load the authentication state and user's profile:
+
+```js title="popup.js"
+const loadAuthenticationState = async () => {
+ const isAuthenticated = await logtoClient.isAuthenticated();
+ // Update the UI based on the authentication state
+
+ if (isAuthenticated) {
+ const user = await logtoClient.getIdTokenClaims(); // { sub: '...', email: '...', ... }
+ // Update the UI with the user's profile
+ }
+};
+```
+
+You can also combine the `loadAuthenticationState` function with the sign-in and sign-out logic:
+
+```js title="popup.js"
+document.getElementById('sign-in').addEventListener('click', async () => {
+ await chrome.runtime.sendMessage({ action: 'signIn' });
+ await loadAuthenticationState();
+});
+
+document.getElementById('sign-out').addEventListener('click', async () => {
+ await chrome.runtime.sendMessage({ action: 'signOut' });
+ await loadAuthenticationState();
+});
+```
+
+Here's an example of the popup page with the authentication state:
+
+
+
+
+
+
+
+- **Service worker bundling**: If you use a bundler like Webpack or Rollup, you need to explicitly set the target to `browser` or similar to avoid unnecessary bundling of Node.js modules.
+- **Module resolution**: Logto Chrome extension SDK is an ESM-only module.
+
+See our [sample project](https://github.com/logto-io/js/tree/HEAD/packages/chrome-extension-sample) for a complete example with TypeScript, Rollup, and other configurations.
+
+
+
+
diff --git a/packages/console/src/assets/docs/guides/spa-chrome-extension/config.json b/packages/console/src/assets/docs/guides/spa-chrome-extension/config.json
new file mode 100644
index 00000000000..4721ad2f793
--- /dev/null
+++ b/packages/console/src/assets/docs/guides/spa-chrome-extension/config.json
@@ -0,0 +1,3 @@
+{
+ "order": 1.1
+}
diff --git a/packages/console/src/assets/docs/guides/spa-chrome-extension/extension-popup.webp b/packages/console/src/assets/docs/guides/spa-chrome-extension/extension-popup.webp
new file mode 100644
index 00000000000..9e136c06e25
Binary files /dev/null and b/packages/console/src/assets/docs/guides/spa-chrome-extension/extension-popup.webp differ
diff --git a/packages/console/src/assets/docs/guides/spa-chrome-extension/index.ts b/packages/console/src/assets/docs/guides/spa-chrome-extension/index.ts
new file mode 100644
index 00000000000..90ad8eb36eb
--- /dev/null
+++ b/packages/console/src/assets/docs/guides/spa-chrome-extension/index.ts
@@ -0,0 +1,16 @@
+import { ApplicationType } from '@logto/schemas';
+
+import { type GuideMetadata } from '../types';
+
+const metadata: Readonly = Object.freeze({
+ name: 'Chrome extension',
+ description: 'Build a Chrome extension with Logto.',
+ target: ApplicationType.SPA,
+ sample: {
+ repo: 'js',
+ path: 'packages/chrome-extension-sample',
+ },
+ fullGuide: 'chrome-extension',
+});
+
+export default metadata;
diff --git a/packages/console/src/assets/docs/guides/spa-chrome-extension/logo.svg b/packages/console/src/assets/docs/guides/spa-chrome-extension/logo.svg
new file mode 100644
index 00000000000..4fc53255e9c
--- /dev/null
+++ b/packages/console/src/assets/docs/guides/spa-chrome-extension/logo.svg
@@ -0,0 +1,26 @@
+
diff --git a/packages/console/src/assets/docs/guides/spa-react/README.mdx b/packages/console/src/assets/docs/guides/spa-react/README.mdx
index 8f684b45e82..ce910680ca0 100644
--- a/packages/console/src/assets/docs/guides/spa-react/README.mdx
+++ b/packages/console/src/assets/docs/guides/spa-react/README.mdx
@@ -1,9 +1,11 @@
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 NpmLikeInstallation from '@/mdx-components/NpmLikeInstallation';
+
+import Checkpoint from '../../fragments/_checkpoint.md';
+import RedirectUrisWeb, { defaultRedirectUri, defaultPostSignOutUri } from '../../fragments/_redirect-uris-web.mdx';
@@ -11,37 +13,16 @@ import Step from '@/mdx-components/Step';
title="Installation"
subtitle="Install Logto SDK for your project"
>
-
-
-
-```bash
-npm i @logto/react
-```
-
-
-
-
-```bash
-yarn add @logto/react
-```
-
-
-
-```bash
-pnpm add @logto/react
-```
+
-
-
-
+
-Import and use `LogtoProvider` to provide a Logto context:
+Import and use `LogtoProvider` to provide a Logto context to your app:
-
+
-
-
-
- In the following steps, we assume your app is running on http://localhost:3000.
-
-
-### Configure Redirect URI
+
-First, let’s enter your redirect URI. E.g. `http://localhost:3000/callback`.
+
-
-
-### Implement a sign-in button
-
-We provide two hooks `useHandleSignInCallback()` and `useLogto()` which can help you easily manage the authentication flow.
-
-Go back to your IDE/editor, use the following code to implement the sign-in button:
-
-
+
-### Handle redirect
+
-We're almost there! In the last step, we use `http://localhost:3000/callback` as the Redirect URI, and now we need to handle it properly.
+After the user signs in, Logto will redirect the user back to the redirect URI configured above. However, there are still things to do to make your application work properly.
-First let's create a callback component:
+First let's create a callback page:
-```tsx
+```tsx title="pages/Callback/index.tsx"
import { useHandleSignInCallback } from '@logto/react';
const Callback = () => {
@@ -118,92 +63,99 @@ const Callback = () => {
if (isLoading) {
return
Redirecting...
;
}
+
+ return null;
};
```
-Finally insert the code below to create a `/callback` route which does NOT require authentication:
+Then, insert the code below to create a `/callback` route which does NOT require authentication:
-```tsx
+```tsx title="App.tsx"
// Assuming react-router
} />
```
-
-
-Calling `.signOut()` will clear all the Logto data in memory and localStorage 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, and use it as the parameter when calling `.signOut()`.
+We provide a hook `useLogto()` which can help you easily manage the authentication flow.
-
+
+ Before calling `.signIn()`, make sure you have correctly configured Redirect URI in Admin Console.
+
-### Implement a sign-out button
+
+ {`import { useLogto } from '@logto/react';
-
+
+
+Calling `.signOut()` will clear all the Logto data in memory and localStorage if they exist.
+
+
+
+
+
+
-
+
-In Logto SDK, generally we can use `logtoClient.isAuthenticated` to check the authentication status, if the user is signed in, the value will be `true`, otherwise, the value will be `false`.
+To display the user's information, you can use the `getIdTokenClaims()` method. For example, in your Home page:
-In Logto React SDK, the `isAuthenticated` status can be checked by using the `useLogto` hook. In the example code below, we can use it to programmatically show and hide the sign-in and sign-out buttons. And also use `getIdTokenClaims` to get the id of the currently logged-in user.
+```tsx title="pages/Home/index.tsx"
+import { useLogto, type IdTokenClaims } from '@logto/react';
+import { useEffect, useState } from 'react';
-```tsx
const Home = () => {
- const { isAuthenticated, getIdTokenClaims, signIn, signOut } = useLogto();
- const [userId, setUserId] = useState('');
+ const { isAuthenticated, getIdTokenClaims } = useLogto();
+ const [user, setUser] = useState();
useEffect(() => {
(async () => {
if (isAuthenticated) {
const claims = await getIdTokenClaims();
- setUserId(claims.sub);
+ setUser(claims);
}
})();
- }, [isAuthenticated]);
+ }, [getIdTokenClaims, isAuthenticated]);
return (
-
- {userId &&
Logged in as {userId}
}
- {isAuthenticated ? (
- Sign Out
- ) : (
- signIn('http://localhost:3000/callback')}>Sign In
- )}
-
{typeof value === 'string' ? value : JSON.stringify(value)}
+
+ ))}
+
+
+ )}
);
-};
+}
```
-
-
-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 ID and the sign-out button.
-4. Click the sign-out button to sign-out.
-
-
-
diff --git a/packages/console/src/assets/docs/guides/spa-react/index.ts b/packages/console/src/assets/docs/guides/spa-react/index.ts
index 53fdeeff520..b560d8dd4ee 100644
--- a/packages/console/src/assets/docs/guides/spa-react/index.ts
+++ b/packages/console/src/assets/docs/guides/spa-react/index.ts
@@ -11,10 +11,7 @@ const metadata: Readonly = Object.freeze({
path: 'packages/react-sample',
},
isFeatured: true,
- fullGuide: {
- title: 'Full React SDK tutorial',
- url: 'https://docs.logto.io/quick-starts/react',
- },
+ fullGuide: 'react',
});
export default metadata;
diff --git a/packages/console/src/assets/docs/guides/spa-vanilla/README.mdx b/packages/console/src/assets/docs/guides/spa-vanilla/README.mdx
index d0848d2b299..cff41a900ec 100644
--- a/packages/console/src/assets/docs/guides/spa-vanilla/README.mdx
+++ b/packages/console/src/assets/docs/guides/spa-vanilla/README.mdx
@@ -1,10 +1,13 @@
import UriInputField from '@/mdx-components/UriInputField';
-import Tabs from '@mdx/components/Tabs';
-import TabItem from '@mdx/components/TabItem';
+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 RedirectUrisWeb, { defaultRedirectUri, defaultPostSignOutUri } from '../../fragments/_redirect-uris-web.mdx';
+
-
+
```bash
-yarn add @logto/browser
+pnpm add @logto/browser
```
-
+
```bash
-pnpm add @logto/browser
+yarn add @logto/browser
```
+
+
+
+```html
+
+```
@@ -40,133 +51,108 @@ pnpm add @logto/browser
Import and init `LogtoClient` with configs:
-
-
+
{`import LogtoClient from '@logto/browser';
const logtoClient = new LogtoClient({
endpoint: '${props.endpoint}',
appId: '${props.app.id}',
});`}
-
-
+
-
-
-
- In the following steps, we assume your app is running on http://localhost:3000.
-
-
-### Configure Redirect URI
-
-First, let’s enter your redirect URI. E.g. `http://localhost:3000/callback`.
-
-
-
-### Implement a sign-in button
-
-Go back to your IDE/editor, use the following code to implement the sign-in button:
-
-
-
- {`
- Sign In
-`}
-
-
-
-### Handle redirect
+
-We're almost there! In the last step, we use `http://localhost:3000/callback` as the Redirect URI, and now we need to handle it properly.
-
-Insert the code below in your `/callback` route:
-
-```ts
-await logtoClient.handleSignInCallback(window.location.href);
-
-if (!logtoClient.isAuthenticated) {
- // Handle failed sign-in
- alert('Failed to sign in');
- return;
-}
-
-// Handle successful sign-in. E.g. redirect to home page.
-window.location.assign('http://localhost:3000/');
-```
-
-Now you can test the sign-in flow.
+
-
-
-Calling `.signOut()` will clear all the Logto data in memory and localStorage 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, and use it as the parameter when calling `.signOut()`.
+There are still things to do after the user is redirected back to your application from Logto. Let's handle it properly.
-
+```ts title="pages/Callback.js"
+const callbackHandler = async (logtoClient) => {
+ await logtoClient.handleSignInCallback(window.location.href);
-### Implement a sign-out button
+ if (!logtoClient.isAuthenticated) {
+ // Handle failed sign-in
+ alert('Failed to sign in');
+ return;
+ }
-
-
- {`
- Sign Out
-`}
-
-
+ // Handle successful sign-in
+ window.location.assign('/');
+};
+```
-
-
-In Logto SDK, generally we can use `logtoClient.isAuthenticated` to check the authentication status, if the user is signed in, the value will be `true`, otherwise, the value will be `false`.
+
-In your vanilla JS app, you can use the `isAuthenticated` status to programmatically show and hide the sign-in and sign-out buttons. Let's see how to do it.
+`logtoClient` provides `signIn` and `signOut` methods to help you easily manage the authentication flow.
-```ts
-const redirectUrl = 'http://localhost:3000/callback';
-const baseUrl = 'http://localhost:3000';
+
+ {`const isAuthenticated = await logtoClient.isAuthenticated();
-// Conditional rendering of sign-in and sign-out buttons
-const isAuthenticated = await logtoClient.isAuthenticated();
-
-// Assuming there's a div with id 'container' in your HTML
-const container = document.querySelector('#container');
-
-const onClickSignIn = () => logtoClient.signIn(redirectUrl);
-const onClickSignOut = () => logtoClient.signOut(baseUrl);
+const onClickSignIn = () => {
+ logtoClient.signIn('${props.redirectUris[0] ?? defaultRedirectUri}');
+};
+const onClickSignOut = () => {
+ logtoClient.signOut('${props.postLogoutRedirectUris[0] ?? defaultPostSignOutUri}');
+};
const button = document.createElement('button');
button.innerHTML = isAuthenticated ? 'Sign Out' : 'Sign In';
button.addEventListener('click', isAuthenticated ? onClickSignOut : onClickSignIn);
-container.append(button);
-```
+document.body.appendChild(button);`}
+
+
+Calling `.signOut()` will clear all the Logto data in memory and `localStorage` if they exist.
-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 ID and the sign-out button.
-4. Click the sign-out button to sign-out.
+
+
+
+
+To display the user's information, you can use the `logtoClient.getIdTokenClaims()` method. For example, in your Home page:
+
+```js title="pages/Home.js"
+const userInfo = await logtoClient.getIdTokenClaims();
+
+// Generate display table for ID token claims
+const table = document.createElement('table');
+const thead = document.createElement('thead');
+const tr = document.createElement('tr');
+const thName = document.createElement('th');
+const thValue = document.createElement('th');
+thName.innerHTML = 'Name';
+thValue.innerHTML = 'Value';
+tr.append(thName, thValue);
+thead.append(tr);
+table.append(thead);
+
+const tbody = document.createElement('tbody');
+
+for (const [key, value] of Object.entries(userInfo)) {
+ const tr = document.createElement('tr');
+ const tdName = document.createElement('td');
+ const tdValue = document.createElement('td');
+ tdName.innerHTML = key;
+ tdValue.innerHTML = typeof value === 'string' ? value : JSON.stringify(value);
+ tr.append(tdName, tdValue);
+ tbody.append(tr);
+}
+
+table.append(tbody);
+```
diff --git a/packages/console/src/assets/docs/guides/spa-vanilla/index.ts b/packages/console/src/assets/docs/guides/spa-vanilla/index.ts
index 48198f3cc2d..a75ff94be90 100644
--- a/packages/console/src/assets/docs/guides/spa-vanilla/index.ts
+++ b/packages/console/src/assets/docs/guides/spa-vanilla/index.ts
@@ -10,10 +10,7 @@ const metadata: Readonly = Object.freeze({
repo: 'js',
path: 'packages/browser-sample',
},
- fullGuide: {
- title: 'Full vanilla JS SDK tutorial',
- url: 'https://docs.logto.io/quick-starts/vanilla-js',
- },
+ fullGuide: 'vanilla-js',
});
export default metadata;
diff --git a/packages/console/src/assets/docs/guides/spa-vue/README.mdx b/packages/console/src/assets/docs/guides/spa-vue/README.mdx
index b9a63997747..a522828ac43 100644
--- a/packages/console/src/assets/docs/guides/spa-vue/README.mdx
+++ b/packages/console/src/assets/docs/guides/spa-vue/README.mdx
@@ -1,9 +1,12 @@
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 NpmLikeInstallation from '@/mdx-components/NpmLikeInstallation';
+
+import Checkpoint from '../../fragments/_checkpoint.md';
+import RedirectUrisWeb from '../../fragments/_redirect-uris-web.mdx';
+import { defaultRedirectUri, defaultPostSignOutUri } from '../../fragments/_redirect-uris-web.mdx';
@@ -11,29 +14,9 @@ import Step from '@/mdx-components/Step';
title="Installation"
subtitle="Install Logto SDK for your project"
>
-
-
-
-```bash
-npm i @logto/vue
-```
-
-
-
-
-```bash
-yarn add @logto/vue
-```
-
-
+
-```bash
-pnpm add @logto/vue
-```
-
-
-
- We only support Vue 3 Composition API at this point. Will add support to Vue Options API and
- possibly Vue 2 in future releases.
+ Logto Vue SDK is built with Vue 3 composition API. Therefore, only Vue 3 is supported at the moment. Contact us if you want to add support for Vue 2.
Import and use `createLogto` to install Logto plugin:
-
+
-
-
-
- In the following steps, we assume your app is running on http://localhost:3000.
-
-
-### Configure Redirect URI
-
-First, let’s enter your redirect URI. E.g. `http://localhost:3000/callback`.
+
-
+
-### Implement a sign-in button
-
-We provide two composables `useHandleSignInCallback()` and `useLogto()`, which can help you easily manage the authentication flow.
-
-Go back to your IDE/editor, use the following code to implement the sign-in button:
+
-
-
-{``}
+There are still things to do after the user is redirected back to your application from Logto. Let's handle it properly.
-
-
+First let's create a callback page:
-```html
-
-
-
Signed in
-
-
- Sign In
-
-
-```
+```ts title="views/CallbackView.vue"
+import { useHandleSignInCallback } from '@logto/vue';
+import router from '@/router';
-### Handle redirect
-
-We're almost there! In the last step, we use `http://localhost:3000/callback` as the Redirect URI, and now we need to handle it properly.
-
-First let's create a callback component:
-
-```html
-
-
+const { isLoading } = useHandleSignInCallback(() => {
+ // Do something when finished, e.g. redirect to home page
+});
```
```html
@@ -132,9 +75,9 @@ First let's create a callback component:
```
-Finally insert the code below to create a `/callback` route which does NOT require authentication:
+Insert the code below in your `/callback` route which does NOT require authentication:
-```ts
+```ts title="router/index.ts"
// Assuming vue-router
const router = createRouter({
routes: [
@@ -149,80 +92,70 @@ const router = createRouter({
-
+
-Calling `.signOut()` will clear all the Logto data in memory and localStorage if they exist.
+We provide a composable `useLogto()` which can help you easily manage the authentication flow.
-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, and use it as the parameter when calling `.signOut()`.
+
+ {`import { useLogto } from '@logto/vue';
-
+const { signIn, signOut, isAuthenticated } = useLogto();
-### Implement a sign-out button
+const onClickSignIn = () => signIn('${props.redirectUris[0] || defaultRedirectUri}');
+const onClickSignOut = () => signOut('${props.postLogoutRedirectUris[0] || defaultPostSignOutUri}');
+`}
+
-
-
-{``}
+
-
-
+
-```html
-
- Sign Out
-
-```
+
-
-
-In Logto SDK, generally we can use `logtoClient.isAuthenticated` to check the authentication status, if the user is signed in, the value will be `true`, otherwise, the value will be `false`.
+
-In Logto Vue SDK, the `isAuthenticated` status can be checked by using the `useLogto` composable. In the example code below, we can use it to programmatically show and hide the sign-in and sign-out buttons. Also we'll use `getIdTokenClaims` to get the ID of the currently logged-in user.
+To display the user's information, you can use the `getIdTokenClaims()` method. For example, in your Home page:
-```tsx
-import { useLogto } from "@logto/vue";
-import { ref } from "vue";
+```ts title="views/HomeView.vue"
+import { useLogto, type IdTokenClaims } from '@logto/vue';
+import { ref } from 'vue';
-const { isAuthenticated, getIdTokenClaims, signIn, signOut } = useLogto();
-const userId = ref();
+const { isAuthenticated, getIdTokenClaims } = useLogto();
+const user = ref();
if (isAuthenticated.value) {
(async () => {
const claims = await getIdTokenClaims();
- userId.value = claims.sub;
+ user.value = claims;
})();
}
```
```html
-
Logged in as {{ userId }}
- Sign In
- Sign Out
+
+
+
+
+
Name
+
Value
+
+
+
+
+
{{ key }}
+
{{ typeof value === "string" ? value : JSON.stringify(value) }}
+
+
+
+
```
-
-
-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 ID and the sign-out button.
-4. Click the sign-out button to sign-out.
-
-
-
diff --git a/packages/console/src/assets/docs/guides/spa-vue/index.ts b/packages/console/src/assets/docs/guides/spa-vue/index.ts
index 71f6553bcd8..195ceebed9e 100644
--- a/packages/console/src/assets/docs/guides/spa-vue/index.ts
+++ b/packages/console/src/assets/docs/guides/spa-vue/index.ts
@@ -12,10 +12,7 @@ const metadata: Readonly = Object.freeze({
path: 'packages/vue-sample',
},
isFeatured: true,
- fullGuide: {
- title: 'Full Vue SDK tutorial',
- url: 'https://docs.logto.io/quick-starts/vue',
- },
+ fullGuide: 'vue',
});
export default metadata;
diff --git a/packages/console/src/assets/docs/guides/spa-webflow/README.mdx b/packages/console/src/assets/docs/guides/spa-webflow/README.mdx
index 1ad4cf7cf04..ceca5e9683c 100644
--- a/packages/console/src/assets/docs/guides/spa-webflow/README.mdx
+++ b/packages/console/src/assets/docs/guides/spa-webflow/README.mdx
@@ -1,6 +1,6 @@
import UriInputField from '@/mdx-components/UriInputField';
-import Tabs from '@mdx/components/Tabs';
-import TabItem from '@mdx/components/TabItem';
+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';
@@ -22,8 +22,7 @@ In this step, we'll add global-level custom code to your Webflow site. Since NPM
Open the "Site settings" page, and navigate to the "Custom code" section. Add the following code to the "Head code" section.
-
-
+
{``}
-
-
+
@@ -59,15 +57,13 @@ First, let’s enter your redirect URI. E.g. `https://your-awesome-site.webflow.
Return to your Webflow designer, drag and drop a "Sign in" button to the home page, and assign it an ID “sign-in” for later reference using `getElementById()`.
-
-
+
{``}
-
-
+
### Handle redirect
@@ -99,13 +95,11 @@ After signing out, it'll be great to redirect user back to your website. Let's a
Return to the Webflow designer, and add a “Sign out” button on your home page. Similarly, assign an ID “sign-out” to the button, and add the following code to the page-level custom code.
-
+
diff --git a/packages/console/src/assets/docs/guides/types.ts b/packages/console/src/assets/docs/guides/types.ts
index 5f11825cd8b..0b18523c99e 100644
--- a/packages/console/src/assets/docs/guides/types.ts
+++ b/packages/console/src/assets/docs/guides/types.ts
@@ -31,18 +31,24 @@ export type GuideMetadata = {
/** Indicate whether the application is for third-party use */
isThirdParty?: boolean;
- /** The related complete guide for this guide which will be displayed in the 'Further readings' section. */
- fullGuide?: {
+ /** The related complete guide url relative to the quick starts page (https://docs.logto.io/quick-starts). */
+ fullGuide?: string;
+
+ /** The related URLs to add to the further readings section. */
+ furtherReadings?: Array<{
title: string;
- url: string;
- };
+ url: URL;
+ }>;
};
/** The guide instance to build in the console. */
export type Guide = {
+ order: number;
/** The unique identifier of the guide. */
id: string;
- Logo: LazyExoticComponent;
+ Logo:
+ | LazyExoticComponent
+ | ((props: { readonly className?: string }) => JSX.Element);
Component: LazyExoticComponent>;
metadata: Readonly;
};
diff --git a/packages/console/src/assets/docs/guides/web-dotnet-core-blazor-server/README.mdx b/packages/console/src/assets/docs/guides/web-dotnet-core-blazor-server/README.mdx
index 1258ea8a865..20508bf92da 100644
--- a/packages/console/src/assets/docs/guides/web-dotnet-core-blazor-server/README.mdx
+++ b/packages/console/src/assets/docs/guides/web-dotnet-core-blazor-server/README.mdx
@@ -1,52 +1,35 @@
-import UriInputField from '@/mdx-components/UriInputField';
import Steps from '@/mdx-components/Steps';
import Step from '@/mdx-components/Step';
+import SignInAndSignOutFlows from '../web-dotnet-core-mvc/fragments/_sign-in-and-sign-out-flows.mdx';
+import ConfigureRedirectUris from '../web-dotnet-core-mvc/fragments/_configure-redirect-uris.mdx';
+import Installation from '../web-dotnet-core-mvc/fragments/_installation.md';
+import AddAuthentication from '../web-dotnet-core-mvc/fragments/_add-authentication.mdx';
+import DisplayUserInformation from '../web-dotnet-core-mvc/fragments/_display-user-information.md';
+import Checkpoint from '../../fragments/_checkpoint.md';
-
+
-This tutorial will show you how to use Logto ASP.NET Core authentication middleware to protect your web application.
-
-
-
It assumes your website is hosted on {props.sampleUrls.origin}.
-
-
-### Installation
-
-```bash
-dotnet add package Logto.AspNetCore.Authentication
-```
+
-Open `Startup.cs` (or `Program.cs`) and add the following code to register Logto authentication middleware:
+
-
-
-{`using Logto.AspNetCore.Authentication;
+
-var builder = WebApplication.CreateBuilder(args);
+
-builder.Services.AddLogtoAuthentication(options =>
-{
- options.Endpoint = "${props.endpoint}";
- options.AppId = "${props.app.id}";
- options.AppSecret = "${props.app.secret}";
-});
+
-app.UseAuthentication();`}
-
-
+
-The `AddLogtoAuthentication` method will do the following things:
+
-- Set the default authentication scheme to `LogtoDefaults.CookieScheme`.
-- Set the default challenge scheme to `LogtoDefaults.AuthenticationScheme`.
-- Set the default sign-out scheme to `LogtoDefaults.AuthenticationScheme`.
-- Add cookie and OpenID Connect authentication handlers to the authentication scheme.
+
@@ -56,7 +39,7 @@ Since Blazor Server uses SignalR to communicate between the server and the clien
To make it right, we need to explicitly add two endpoints for sign-in and sign-out redirects:
-```csharp
+```csharp title="Program.cs"
app.MapGet("/SignIn", async context =>
{
if (!(context.User?.Identity?.IsAuthenticated ?? false))
@@ -82,35 +65,11 @@ Now we can redirect to these endpoints to trigger sign-in and sign-out.
-
-
-
-First, let's enter your redirect URI. E.g. {props.sampleUrls.origin + 'Callback'} (replace the endpoint with yours). This is where Logto will redirect users after they sign in.
-
-
-
-
-Remember to keep the path `/Callback` in the URI as it's the default value for the Logto authentication middleware.
-
----
-
-To clean up both ASP.NET session and Logto session, we can designate a post sign-out redierct URI. This is where Logto will redirect users after they sign out.
-
-
-For example, set the URI to {props.sampleUrls.origin + 'SignedOutCallback'} (replace the endpoint with yours):
-
-
-
-
-Remember to keep the path `/SignedOutCallback` in the URI as it's the default value for the Logto authentication middleware.
-
-
-
In the Razor component, add the following code:
-```cshtml
+```cshtml title="Components/Pages/Index.razor"
@using Microsoft.AspNetCore.Components.Authorization
@using System.Security.Claims
@inject AuthenticationStateProvider AuthenticationStateProvider
@@ -160,41 +119,13 @@ The page will show the "Sign in" button if the user is not authenticated, and sh
-
-
-Now you can run the web application and try to sign in and sign out with Logto:
-
-1. Open the web application in your browser, you should see "Is authenticated: False" and the "Sign in" button.
-2. Click the "Sign in" button, and you should be redirected to the Logto sign-in page.
-3. After you have signed in, you should be redirected back to the web application, and you should see "Is authenticated: True" and the "Sign out" button.
-4. Click the "Sign out" button, and you should be redirected to the Logto sign-out page, and then redirected back to the web application.
-
-
-
-
-
-To know if the user is authenticated, you can check the `User.Identity?.IsAuthenticated` property.
-
-To get the user profile claims, you can use the `User.Claims` property:
-
-```csharp
-var claims = User.Claims;
-
-// Get the user ID
-var userId = claims.FirstOrDefault(c => c.Type == LogtoParameters.Claims.Subject)?.Value;
-```
-
-See the [full tutorial](https://docs.logto.io/quick-starts/dotnet-core/blazor-server/) for more details.
-
-
-
-
+
Alternatively, you can use the `AuthorizeView` component to conditionally render content based on the user's authentication state. This component is useful when you want to show different content to authenticated and unauthenticated users.
In your Razor component, add the following code:
-```cshtml
+```cshtml title="Components/Pages/Index.razor"
@using Microsoft.AspNetCore.Components.Authorization
@* ... *@
@@ -214,7 +145,7 @@ In your Razor component, add the following code:
The `AuthorizeView` component requires a cascading parameter of type `Task`. A direct way to get this parameter is to add the `` component. However, due to the nature of Blazor Server, we cannot simply add the component to the layout or the root component (it may not work as expected). Instead, we can add the following code to the builder (`Program.cs` or `Startup.cs`) to provide the cascading parameter:
-```csharp
+```csharp title="Program.cs"
builder.Services.AddCascadingAuthenticationState();
```
@@ -222,4 +153,16 @@ Then you can use the `AuthorizeView` component in every component that needs it.
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/console/src/assets/docs/guides/web-dotnet-core-blazor-server/index.ts b/packages/console/src/assets/docs/guides/web-dotnet-core-blazor-server/index.ts
index 9deb50ecb89..1e01259c368 100644
--- a/packages/console/src/assets/docs/guides/web-dotnet-core-blazor-server/index.ts
+++ b/packages/console/src/assets/docs/guides/web-dotnet-core-blazor-server/index.ts
@@ -10,10 +10,7 @@ const metadata: Readonly = Object.freeze({
repo: 'csharp',
path: '/',
},
- fullGuide: {
- title: 'Full .NET Core (Blazor Server) integration tutorial',
- url: 'https://docs.logto.io/quick-starts/dotnet-core/blazor-server',
- },
+ fullGuide: 'dotnet-core/blazor-server',
});
export default metadata;
diff --git a/packages/console/src/assets/docs/guides/web-dotnet-core-blazor-wasm/README.mdx b/packages/console/src/assets/docs/guides/web-dotnet-core-blazor-wasm/README.mdx
index 75718996401..1e1d62c3cb0 100644
--- a/packages/console/src/assets/docs/guides/web-dotnet-core-blazor-wasm/README.mdx
+++ b/packages/console/src/assets/docs/guides/web-dotnet-core-blazor-wasm/README.mdx
@@ -2,15 +2,14 @@ import UriInputField from '@/mdx-components/UriInputField';
import Steps from '@/mdx-components/Steps';
import Step from '@/mdx-components/Step';
+import Checkpoint from '../../fragments/_checkpoint.md';
+import RedirectUrisWeb, { defaultRedirectUri, defaultPostSignOutUri } from '../../fragments/_redirect-uris-web.mdx';
+
-This tutorial will show you how to use [Blorc.OpenIdConnect](https://github.com/WildGums/Blorc.OpenIdConnect) to add Logto authentication to a Blazor WebAssembly application.
-
-
-
It assumes your website is hosted on {props.sampleUrls.origin}.
-
+This guide will show you how to use [Blorc.OpenIdConnect](https://github.com/WildGums/Blorc.OpenIdConnect) to add Logto authentication to a Blazor WebAssembly application.
### Installation
@@ -26,7 +25,7 @@ dotnet add package Blorc.OpenIdConnect
Include `Blorc.Core/injector.js` the `index.html` file:
-```html
+```html title="index.html"
@@ -38,7 +37,7 @@ Include `Blorc.Core/injector.js` the `index.html` file:
Add the following code to the `Program.cs` file:
-```csharp
+```csharp title="Program.cs"
using Blorc.OpenIdConnect;
using Blorc.Services;
@@ -66,45 +65,29 @@ Note: There's no need to use the `Microsoft.AspNetCore.Components.WebAssembly.Au
-
-
-### Configure redirect URI
-
-
-First, let's enter your redirect URI. E.g. {props.sampleUrls.origin + 'Callback'} (replace the endpoint with yours). This is where Logto will redirect users after they sign in.
-
-
-
-
-### Configure post sign-out redirect URI
-
-To clean up both ASP.NET session and Logto session, we can designate a post sign-out redierct URI. This is where Logto will redirect users after they sign out.
+
-
-For example, set the URI to {props.sampleUrls.origin + 'SignedOutCallback'} (replace the endpoint with yours):
-
+
-
+
-### Configure application
+
Add the following code to the `appsettings.json` file:
-
+
@@ -114,7 +97,7 @@ Add the following code to the `appsettings.json` file:
In the Razor pages that require authentication, add the `AuthorizeView` component. Let's assume it's the `Home.razor` page:
-```cshtml
+```cshtml title="Pages/Home.razor"
@using Microsoft.AspNetCore.Components.Authorization
@page "/"
@@ -138,7 +121,7 @@ In the Razor pages that require authentication, add the `AuthorizeView` componen
In the `Home.razor.cs` file (create it if it doesn't exist), add the following code:
-```csharp
+```csharp title="Pages/Home.razor.cs"
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
@@ -175,11 +158,19 @@ public partial class Home : ComponentBase
Once the user is authenticated, the `User` property will be populated with the user information.
-### Display user information
+
+
+
+
+
+
+
+
+
Here are some examples of how to display user information in the `Home.razor` page:
-```cshtml
+```cshtml title="Pages/Home.razor"
@* Signed in view *@
@@ -194,23 +185,4 @@ For more properties and claims, check the `User` and `Profile` classes in the `B
-
-
-Now you can run the web application and try to sign in and sign out with Logto:
-
-1. Open the web application in your browser, you should see "Is authenticated: False" and the "Sign in" button.
-2. Click the "Sign in" button, and you should be redirected to the Logto sign-in page.
-3. After you have signed in, you should be redirected back to the web application, and you should see "Is authenticated: True" and the "Sign out" button.
-4. Click the "Sign out" button, and you should be redirected to the Logto sign-out page, and then redirected back to the web application.
-
-
-
-
-
-To get the user profile, you can use the `User?.Profile` property; to fetch the access token, you can use the `User?.AccessToken` property or add it to your HTTP client using `.AddAccessToken()`.
-
-See the [full tutorial](https://docs.logto.io/quick-starts/dotnet-core/blazor-wasm/) for more details.
-
-
-
diff --git a/packages/console/src/assets/docs/guides/web-dotnet-core-blazor-wasm/index.ts b/packages/console/src/assets/docs/guides/web-dotnet-core-blazor-wasm/index.ts
index 55770849b8c..1ad19eeeed5 100644
--- a/packages/console/src/assets/docs/guides/web-dotnet-core-blazor-wasm/index.ts
+++ b/packages/console/src/assets/docs/guides/web-dotnet-core-blazor-wasm/index.ts
@@ -10,10 +10,7 @@ const metadata: Readonly = Object.freeze({
repo: 'csharp',
path: '/',
},
- fullGuide: {
- title: 'Full .NET Core (Blazor WASM) integration tutorial',
- url: 'https://docs.logto.io/quick-starts/dotnet-core/blazor-wasm',
- },
+ fullGuide: 'dotnet-core/blazor-wasm',
});
export default metadata;
diff --git a/packages/console/src/assets/docs/guides/web-dotnet-core-mvc/README.mdx b/packages/console/src/assets/docs/guides/web-dotnet-core-mvc/README.mdx
index 8fa99eea24c..27f31597d8f 100644
--- a/packages/console/src/assets/docs/guides/web-dotnet-core-mvc/README.mdx
+++ b/packages/console/src/assets/docs/guides/web-dotnet-core-mvc/README.mdx
@@ -2,143 +2,68 @@ import UriInputField from '@/mdx-components/UriInputField';
import Steps from '@/mdx-components/Steps';
import Step from '@/mdx-components/Step';
-
-
-
+import Checkpoint from '../../fragments/_checkpoint.md';
-This tutorial will show you how to use Logto ASP.NET Core authentication middleware to protect your web application.
+import SignInAndSignOutFlows from './fragments/_sign-in-and-sign-out-flows.mdx';
+import ConfigureRedirectUris from './fragments/_configure-redirect-uris.mdx';
+import Installation from './fragments/_installation.md';
+import AddAuthentication from './fragments/_add-authentication.mdx';
+import DisplayUserInformation from './fragments/_display-user-information.md';
-
-
It assumes your website is hosted on {props.sampleUrls.origin}.
-
+
-### Installation
+
-```bash
-dotnet add package Logto.AspNetCore.Authentication
-```
+
-Open `Startup.cs` (or `Program.cs`) and add the following code to register Logto authentication middleware:
-
-
+
-The `AddLogtoAuthentication` method will do the following things:
+
-- Set the default authentication scheme to `LogtoDefaults.CookieScheme`.
-- Set the default challenge scheme to `LogtoDefaults.AuthenticationScheme`.
-- Set the default sign-out scheme to `LogtoDefaults.AuthenticationScheme`.
-- Add cookie and OpenID Connect authentication handlers to the authentication scheme.
+
-
-
-
-First, let's enter your redirect URI. E.g. {props.sampleUrls.origin + 'Callback'} (replace the endpoint with yours). This is where Logto will redirect users after they sign in.
-
-
-
+
-Remember to keep the path `/Callback` in the URI as it's the default value for the Logto authentication middleware.
+
-To sign-in with Logto, you can use the `Challenge` method of `ControllerBase`:
+
-```csharp
-Challenge(new AuthenticationProperties
-{
- // The URI below is different from the redirect URI you entered above.
- // It's the URI where users will be redirected after successfully signed in.
- // You can change it to any path you want.
- RedirectUri = "/"
-});
-```
+
-For example, you can add the following code to the controller:
+First, add actions methods to your `Controller`, for example:
-```csharp
+```csharp title="Controllers/HomeController.cs"
public class HomeController : Controller
{
public IActionResult SignIn()
{
+ // This will redirect the user to the Logto sign-in page.
return Challenge(new AuthenticationProperties { RedirectUri = "/" });
}
-}
-```
-
-And then add the following code to your View:
-
-```html
-
Is authenticated: @User.Identity?.IsAuthenticated
-Sign in
-```
-
-
-
-
-
-To clean up both ASP.NET session and Logto session, we can designate a post sign-out redierct URI. This is where Logto will redirect users after they sign out.
-
-
-For example, set the URI to {props.sampleUrls.origin + 'SignedOutCallback'} (replace the endpoint with yours):
-
-
-
-
-Remember to keep the path `/SignedOutCallback` in the URI as it's the default value for the Logto authentication middleware.
-
-To sign-out with Logto, you can use the `SignOut` method of `ControllerBase`:
-
-```csharp
-SignOut(new AuthenticationProperties
-{
- // The URI below is different from the post sign-out redirect URI you entered above.
- // It's the URI where users will be redirected after successfully signed out.
- // You can change it to any path you want.
- RedirectUri = "/"
-});
-```
-
-The `SignOut` method will clear the authentication cookie and redirect the user to the Logto sign-out page.
-For example, you can add the following code to your controller:
-
-```csharp
-public class HomeController : Controller
-{
- // ...
// Use the `new` keyword to avoid conflict with the `ControllerBase.SignOut` method
new public IActionResult SignOut()
{
+ // This will clear the authentication cookie and redirect the user to the Logto sign-out page
+ // to clear the Logto session as well.
return SignOut(new AuthenticationProperties { RedirectUri = "/" });
}
}
```
-Then, update the form on your View:
+Then, add the links to your View:
-```html
+```cshtml title="Views/Home/Index.cshtml"
Is authenticated: @User.Identity?.IsAuthenticated
-@if (User.Identity?.IsAuthenticated == true)
-{
+@if (User.Identity?.IsAuthenticated == true) {
Sign out
} else {
Sign in
@@ -151,29 +76,13 @@ It will show the "Sign in" link if the user is not authenticated, and show the "
-Now you can run the web application and try to sign in and sign out with Logto:
-
-1. Open the web application in your browser, you should see "Is authenticated: False" and the "Sign in" link.
-2. Click the "Sign in" link, and you should be redirected to the Logto sign-in page.
-3. After you have signed in, you should be redirected back to the web application, and you should see "Is authenticated: True" and the "Sign out" link.
-4. Click the "Sign out" link, and you should be redirected to the Logto sign-out page, and then redirected back to the web application.
+
-
-
-To know if the user is authenticated, you can check the `User.Identity?.IsAuthenticated` property.
-
-To get the user profile claims, you can use the `User.Claims` property:
-
-```csharp
-var claims = User.Claims;
-
-// Get the user ID
-var userId = claims.FirstOrDefault(c => c.Type == LogtoParameters.Claims.Subject)?.Value;
-```
+
-See the [full tutorial](https://docs.logto.io/quick-starts/dotnet-core/mvc/) for more details.
+
diff --git a/packages/console/src/assets/docs/guides/web-dotnet-core-mvc/fragments/_add-authentication.mdx b/packages/console/src/assets/docs/guides/web-dotnet-core-mvc/fragments/_add-authentication.mdx
new file mode 100644
index 00000000000..34b1fe74690
--- /dev/null
+++ b/packages/console/src/assets/docs/guides/web-dotnet-core-mvc/fragments/_add-authentication.mdx
@@ -0,0 +1,23 @@
+Open `Startup.cs` (or `Program.cs`) and add the following code to register Logto authentication middleware:
+
+
+{`using Logto.AspNetCore.Authentication;
+
+var builder = WebApplication.CreateBuilder(args);
+
+builder.Services.AddLogtoAuthentication(options =>
+{
+ options.Endpoint = "${props.endpoint}";
+ options.AppId = "${props.app.id}";
+ options.AppSecret = "${props.app.secret}";
+});
+
+app.UseAuthentication();`}
+
+
+The `AddLogtoAuthentication` method will do the following things:
+
+- Set the default authentication scheme to `LogtoDefaults.CookieScheme`.
+- Set the default challenge scheme to `LogtoDefaults.AuthenticationScheme`.
+- Set the default sign-out scheme to `LogtoDefaults.AuthenticationScheme`.
+- Add cookie and OpenID Connect authentication handlers to the authentication scheme.
diff --git a/packages/console/src/assets/docs/guides/web-dotnet-core-mvc/fragments/_configure-redirect-uris.mdx b/packages/console/src/assets/docs/guides/web-dotnet-core-mvc/fragments/_configure-redirect-uris.mdx
new file mode 100644
index 00000000000..6c1b0820946
--- /dev/null
+++ b/packages/console/src/assets/docs/guides/web-dotnet-core-mvc/fragments/_configure-redirect-uris.mdx
@@ -0,0 +1,28 @@
+import UriInputField from '@/mdx-components/UriInputField';
+
+export const defaultBaseUrl = 'http://localhost:5000/';
+
+First, let's configure the **Logto redirect URI**. For example, if your website is hosted on {defaultBaseUrl}, add {defaultBaseUrl + 'Callback'} as the redirect URI below:
+
+
+
+Now let's configure the **Logto post sign-out redirect URI**. For example, set the URI to {defaultBaseUrl + 'SignedOutCallback'}:
+
+
+
+#### Change the default paths
+
+The **Logto redirect URI** has a default path of `/Callback`, and the **Logto post sign-out redirect URI** has a default path of `/SignedOutCallback`.
+
+You can leave them as are if there's no special requirement. If you want to change it, you can set the `CallbackPath` and `SignedOutCallbackPath` property for `LogtoOptions`:
+
+```csharp title="Program.cs"
+builder.Services.AddLogtoAuthentication(options =>
+{
+ // Other configurations...
+ options.CallbackPath = "/Foo";
+ options.SignedOutCallbackPath = "/Bar";
+});
+```
+
+Remember to update the values in the redirect URI fields accordingly.
diff --git a/packages/console/src/assets/docs/guides/web-dotnet-core-mvc/fragments/_display-user-information.md b/packages/console/src/assets/docs/guides/web-dotnet-core-mvc/fragments/_display-user-information.md
new file mode 100644
index 00000000000..229c20ebe8f
--- /dev/null
+++ b/packages/console/src/assets/docs/guides/web-dotnet-core-mvc/fragments/_display-user-information.md
@@ -0,0 +1,12 @@
+To know if the user is authenticated, you can check the `User.Identity?.IsAuthenticated` property.
+
+To get the user profile claims, you can use the `User.Claims` property:
+
+```csharp title="Controllers/HomeController.cs"
+var claims = User.Claims;
+
+// Get the user ID
+var userId = claims.FirstOrDefault(c => c.Type == LogtoParameters.Claims.Subject)?.Value;
+```
+
+See [`LogtoParameters.Claims`](https://github.com/logto-io/csharp/blob/master/src/Logto.AspNetCore.Authentication/LogtoParameters.cs) for the list of claim names and their meanings.
diff --git a/packages/console/src/assets/docs/guides/web-dotnet-core-mvc/fragments/_installation.md b/packages/console/src/assets/docs/guides/web-dotnet-core-mvc/fragments/_installation.md
new file mode 100644
index 00000000000..7037dce9bb0
--- /dev/null
+++ b/packages/console/src/assets/docs/guides/web-dotnet-core-mvc/fragments/_installation.md
@@ -0,0 +1,5 @@
+Install the Logto SDK to your project:
+
+```bash showLineNumbers={false}
+dotnet add package Logto.AspNetCore.Authentication
+```
diff --git a/packages/console/src/assets/docs/guides/web-dotnet-core-mvc/fragments/_sign-in-and-sign-out-flows.mdx b/packages/console/src/assets/docs/guides/web-dotnet-core-mvc/fragments/_sign-in-and-sign-out-flows.mdx
new file mode 100644
index 00000000000..090a75062e7
--- /dev/null
+++ b/packages/console/src/assets/docs/guides/web-dotnet-core-mvc/fragments/_sign-in-and-sign-out-flows.mdx
@@ -0,0 +1,37 @@
+import RegardingRedirectBasedSignIn from '../../../fragments/_regarding-redirect-based-sign-in.md';
+
+Before we proceed, there are two confusing terms in the .NET Core authentication middleware that we need to clarify:
+
+1. **CallbackPath**: The URI that Logto will redirect the user back to after the user has signed in (the "redirect URI" in Logto)
+2. **RedirectUri**: The URI that will be redirected to after necessary actions have been taken in the Logto authentication middleware.
+
+The sign-in process can be illustrated as follows:
+
+```mermaid
+graph LR
+ subgraph Your app
+ A
+ C
+ D
+ end
+ subgraph Logto
+ B
+ end
+ A(Sign-in path) -->|Redirect to| B(Logto)
+ B -->|Redirect to| C(CallbackPath)
+ C -->|Redirect to| D(RedirectUri)
+```
+
+
+
+Similarly, .NET Core also has **SignedOutCallbackPath** and **RedirectUri** for the sign-out flow.
+
+For the sack of clarity, we'll refer them as follows:
+
+| Term we use | .NET Core term |
+| -------------------------------- | --------------------- |
+| Logto redirect URI | CallbackPath |
+| Logto post sign-out redirect URI | SignedOutCallbackPath |
+| Application redirect URI | RedirectUri |
+
+
diff --git a/packages/console/src/assets/docs/guides/web-dotnet-core-mvc/index.ts b/packages/console/src/assets/docs/guides/web-dotnet-core-mvc/index.ts
index 315a8943e86..ac60c2b4048 100644
--- a/packages/console/src/assets/docs/guides/web-dotnet-core-mvc/index.ts
+++ b/packages/console/src/assets/docs/guides/web-dotnet-core-mvc/index.ts
@@ -10,10 +10,7 @@ const metadata: Readonly = Object.freeze({
repo: 'csharp',
path: '/',
},
- fullGuide: {
- title: 'Full .NET Core (MVC) integration tutorial',
- url: 'https://docs.logto.io/quick-starts/dotnet-core/mvc',
- },
+ fullGuide: 'dotnet-core/mvc',
});
export default metadata;
diff --git a/packages/console/src/assets/docs/guides/web-dotnet-core/README.mdx b/packages/console/src/assets/docs/guides/web-dotnet-core/README.mdx
index 245a9fe3a51..dee9d255372 100644
--- a/packages/console/src/assets/docs/guides/web-dotnet-core/README.mdx
+++ b/packages/console/src/assets/docs/guides/web-dotnet-core/README.mdx
@@ -2,82 +2,46 @@ import UriInputField from '@/mdx-components/UriInputField';
import Steps from '@/mdx-components/Steps';
import Step from '@/mdx-components/Step';
-
-
-
-
-This tutorial will show you how to use Logto ASP.NET Core authentication middleware to protect your web application.
+import SignInAndSignOutFlows from '../web-dotnet-core-mvc/fragments/_sign-in-and-sign-out-flows.mdx';
+import ConfigureRedirectUris from '../web-dotnet-core-mvc/fragments/_configure-redirect-uris.mdx';
+import Installation from '../web-dotnet-core-mvc/fragments/_installation.md';
+import AddAuthentication from '../web-dotnet-core-mvc/fragments/_add-authentication.mdx';
+import DisplayUserInformation from '../web-dotnet-core-mvc/fragments/_display-user-information.md';
+import Checkpoint from '../../fragments/_checkpoint.md';
-
-
It assumes your website is hosted on {props.sampleUrls.origin}.
-
+
-### Installation
+
-```bash
-dotnet add package Logto.AspNetCore.Authentication
-```
+
-Open `Startup.cs` (or `Program.cs`) and add the following code to register Logto authentication middleware:
-
-
+
-The `AddLogtoAuthentication` method will do the following things:
+
-- Set the default authentication scheme to `LogtoDefaults.CookieScheme`.
-- Set the default challenge scheme to `LogtoDefaults.AuthenticationScheme`.
-- Set the default sign-out scheme to `LogtoDefaults.AuthenticationScheme`.
-- Add cookie and OpenID Connect authentication handlers to the authentication scheme.
+
-
-
-
-First, let's enter your redirect URI. E.g. {props.sampleUrls.origin + 'Callback'} (replace the endpoint with yours). This is where Logto will redirect users after they sign in.
-
-
-
+
-Remember to keep the path `/Callback` in the URI as it's the default value for the Logto authentication middleware.
+
-To sign-in with Logto, you can use the `ChallengeAsync` method:
+
-```csharp
-await HttpContext.ChallengeAsync(new AuthenticationProperties
-{
- // The URI below is different from the redirect URI you entered above.
- // It's the URI where users will be redirected after successfully signed in.
- // You can change it to any path you want.
- RedirectUri = "/"
-});
-```
+
-For example, if you are using Razor Pages, you can add the following code to the `Index` page model:
+First, add the handler methods to your `PageModel`, for example:
-```csharp
+```csharp title="Pages/Index.cshtml.cs"
public class IndexModel : PageModel
{
- // ...
public async Task OnPostSignInAsync()
{
await HttpContext.ChallengeAsync(new AuthenticationProperties
@@ -85,52 +49,7 @@ public class IndexModel : PageModel
RedirectUri = "/"
});
}
-}
-```
-
-And then add the following code to the `Index` page:
-
-```html
-
Is authenticated: @User.Identity?.IsAuthenticated
-
-```
-
-
-
-
-To clean up both ASP.NET session and Logto session, we can designate a post sign-out redierct URI. This is where Logto will redirect users after they sign out.
-
-
-For example, set the URI to {props.sampleUrls.origin + 'SignedOutCallback'} (replace the endpoint with yours):
-
-
-
-
-Remember to keep the path `/SignedOutCallback` in the URI as it's the default value for the Logto authentication middleware.
-
-To sign-out with Logto, you can use the `SignOutAsync` method:
-
-```csharp
-await HttpContext.SignOutAsync(new AuthenticationProperties
-{
- // The URI below is different from the post sign-out redirect URI you entered above.
- // It's the URI where users will be redirected after successfully signed out.
- // You can change it to any path you want.
- RedirectUri = "/"
-});
-```
-
-The `SignOutAsync` method will clear the authentication cookie and redirect the user to the Logto sign-out page.
-
-For example, if you are using Razor Pages, you can add the following code to the `Index` page model:
-
-```csharp
-public class IndexModel : PageModel
-{
- // ...
public async Task OnPostSignOutAsync()
{
await HttpContext.SignOutAsync(new AuthenticationProperties
@@ -141,13 +60,12 @@ public class IndexModel : PageModel
}
```
-Then, update the form on your Razor page:
+Then, add the buttons to your Razor page:
-```html
+```cshtml title="Pages/Index.cshtml"