0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-30 20:33:54 -05:00

docs(console): update flutter intergration guide (#6125)

improve content

---------

Co-authored-by: Gao Sun <gao@silverhand.io>
This commit is contained in:
simeng-li 2024-07-01 16:43:37 +08:00 committed by GitHub
parent 685a97476a
commit ac063f9140
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 172 additions and 195 deletions

View file

@ -4,9 +4,19 @@ import Step from '@/mdx-components/Step';
import Tabs from '@mdx/components/Tabs'; import Tabs from '@mdx/components/Tabs';
import TabItem from '@mdx/components/TabItem'; import TabItem from '@mdx/components/TabItem';
import InlineNotification from '@/ds-components/InlineNotification'; import InlineNotification from '@/ds-components/InlineNotification';
import Checkpoint from '../../fragments/_checkpoint.md';
import RedirectUrisNative, { defaultRedirectUri } from '../../fragments/_redirect-uris-native.mdx';
<Steps> <Steps>
<Step title="Install Logto SDK"> <Step title="Install SDK">
<InlineNotification severity="alert">
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.
</InlineNotification>
<Tabs> <Tabs>
@ -16,124 +26,165 @@ You can install the `logto_dart_sdk package` directly using the pub package mana
Run the following command under your project root: Run the following command under your project root:
```sh ```sh
flutter pub get logto_dart_sdk flutter pub get logto_dart_sdk
``` ```
</TabItem> </TabItem>
<TabItem value="github" label="github"> <TabItem value="github" label="GitHub">
If you prefer to fork your own version of the SDK, you can clone the repository directly from GitHub. If you prefer to fork your own version of the SDK, you can clone the repository directly from GitHub.
```sh ```sh
git clone https://github.com/logto-io/dart git clone https://github.com/logto-io/dart
``` ```
</TabItem> </TabItem>
</Tabs> </Tabs>
</Step> ### Dependencies and Android settings
<Step title="Dependency settings" subtitle="2 steps">
<details> <details>
<summary>flutter_secure_storage</summary> <summary>flutter_secure_storage</summary>
<p>
We use [flutter_secure_storage](https://pub.dev/packages/flutter_secure_storage) to implement the cross-platform persistent secure token 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 - Keychain is used for iOS
- AES encryption is used for Android. - 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 { android {
... {/* ... */}
defaultConfig {
defaultConfig { {/* ... */}
... minSdkVersion 18
minSdkVersion 18 }
...
}
} }
``` ```
**Disable autobackup**
### Disable autobackup: By default Android may backup data on Google Drive automatically. It can cause the exception `java.security.InvalidKeyException: Failed to unwrap key`.
<InlineNotification> To avoid this, you can disable auto backup for your app or exclude `sharedprefs` from the `FlutterSecureStorage`.
By default Android backups data on Google Drive. It can cause exception
`java.security.InvalidKeyException:Failed` to unwrap key.
<br />
You will need to either disable `autobackup` or exclude `sharedprefs` used by the
FlutterSecureStorage plugin.
</InlineNotification>
1. To disable `autobackup`, go to your app manifest file and set the boolean value `android:allowBackup`: 1. To disable auto backup, go to your app manifest file and set the `android:allowBackup` and `android:fullBackupContent` attributes to `false`.
```xml ```xml title="AndroidManifest.xml"
<manifest ... > <manifest>
... <!-- ...other attributes -->
<application <application
android:allowBackup="false" android:allowBackup="false"
android:fullBackupContent="false" android:fullBackupContent="false"
... >
> <!-- ... -->
... </application>
</application> </manifest>
</manifest> ```
``` 2. Exclude `sharedprefs` from `FlutterSecureStorage`.
2. To exclude `sharedprefs` for FlutterSecureStorage. If you need to keep the `android:fullBackupContent` for your app rather than disabling it, you can exclude the `sharedprefs` directory from the backup.
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: See more details in the [Android documentation](https://developer.android.com/identity/data/autobackup#IncludingFiles).
```xml In your `AndroidManifest.xml` file, add the `android:fullBackupContent` attribute to the `<application>` element, as shown in the following example. This attribute points to an XML file that contains backup rules.
<application ...
android:fullBackupContent="@xml/backup_rules">
</application>
```
```xml ```xml title="AndroidManifest.xml"
<?xml version="1.0" encoding="utf-8"?> <manifest>
<full-backup-content> <!-- ...other attributes -->
<exclude domain="sharedpref" path="FlutterSecureStorage"/> <application
</full-backup-content> android:fullBackupContent="@xml/backup_rules"
``` >
<!-- ... -->
</application>
</manifest>
```
Create an XML file called `@xml/backup_rules` in the `res/xml/` directory. In this file, add rules with the `<include>` and `<exclude>` elements. The following sample backs up all shared preferences except device.xml:
```xml title="res/xml/backup_rules.xml"
<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
<exclude domain="sharedpref" path="FlutterSecureStorage"/>
</full-backup-content>
```
Please check [flutter_secure_storage](https://pub.dev/packages/flutter_secure_storage#configure-android-version) for more details. Please check [flutter_secure_storage](https://pub.dev/packages/flutter_secure_storage#configure-android-version) for more details.
</p>
</details> </details>
<br />
<details> <details>
<summary>flutter_web_auth</summary> <summary>flutter_web_auth</summary>
<p> [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.
[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. 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.
<InlineNotification> </details>
Under the hood, this plugin uses `ASWebAuthenticationSession` on iOS 12+ and macOS 10.15+, </Step>
`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.
</InlineNotification> <Step title="Init Client" subtitle="1 step">
Andorid: Import the `logto_dart_sdk` package and initialize the `LogtoClient` instance at the root state of your application.
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`. <Code className="language-dart" title="lib/main.dart">
{`import 'package:logto_dart_sdk/logto_dart_sdk.dart';
import 'package:http/http.dart' as http;
```xml class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final logtoConfig = LogtoConfig(
endpoint: 'your_logto_endpoint', // Replace with your Logto endpoint
appId: 'your_app_id', // Replace with your App ID
);
late LogtoClient logtoClient;
void _init() async {
logtoClient = LogtoClient(
config: logtoConfig,
httpClient: http.Client(), // Optional: Custom HTTP client
);
}
@override
void initState() {
super.initState();
_init();
}
// ...
}`}
</Code>
</Step>
<Step title="Configure redirect URIs" subtitle="2 steps">
<RedirectUrisNative />
- 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.
- 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`.
```xml title="AndroidManifest.xml"
<activity android:name="com.linusu.flutter_web_auth.CallbackActivity" android:exported="true"> <activity android:name="com.linusu.flutter_web_auth.CallbackActivity" android:exported="true">
<intent-filter android:label="flutter_web_auth"> <intent-filter android:label="flutter_web_auth">
<action android:name="android.intent.action.VIEW"/> <action android:name="android.intent.action.VIEW"/>
@ -143,72 +194,40 @@ In order to capture the callback url from Logto's sign-in web page, you will nee
</intent-filter> </intent-filter>
</activity> </activity>
``` ```
</p>
</details>
</Step> </Step>
<Step title="Init LogtoClient" subtitle="1 step"> <Step title="Implement sign-in and sign-out" subtitle="2 steps">
Import the `logto_dart_sdk` package and initialize the `LogtoClient` instance at the root of your application. Let's implement the `signIn` and `signOut` buttons in your application.
<Code className="language-dart"> <Code className="language-dart" title="lib/main.dart">
{` {`class _MyHomePageState extends State<MyHomePage> {
import 'package:logto_dart_sdk/logto_dart_sdk.dart'; // ...
import 'package:http/http.dart' as http;
late LogtoClient logtoClient; final redirectUri = '${props.redirectUris[0] ?? defaultRedirectUri}';
// LogtoConfig @override
final logtoConfig = const LogtoConfig( Widget build(BuildContext context) {
endpoint: '${props.endpoint}', // Your Logto endpoint Widget signInButton = TextButton(
appId: '${props.app.id}', // Your App ID onPressed: () async {
); await logtoClient.signIn(redirectUri);
},
child: const Text('Sign In'),
);
void init() async { Widget signOutButton = TextButton(
logtoClient = LogtoClient( onPressed: () async {
config: logtoConfig, await logtoClient.signOut();
httpClient: http.Client(), // Optional http client },
); child: const Text('Sign Out'),
} );
`}
// ...
}
}`}
</Code> </Code>
</Step> <br />
<Step title="Sign In" subtitle="2 steps">
### Configure Redirect URI
<InlineNotification>
In the following steps, we assume your app has configured using `io.logo` as your schema .
</InlineNotification>
Let's switch to the Application details page of Logto Admin Console. Add a Redirect URI `io.logto://callback` and click "Save changes".
<UriInputField name="redirectUris" />
### Implement a sign-in method
<Code className="language-dart">
{`void signIn() async {
await logtoClient.signIn('${props.redirectUris[0] ?? 'io.logto://callback'}');
}
`}
</Code>
</Step>
<Step title="Sign Out" subtitle="1 step">
### Implement a sign-out method
```dart
void signOut() async {
await logtoClient.signOut();
}
```
<InlineNotification> <InlineNotification>
The `signOut` method will clear the user's session and remove the token from the secure storage. The `signOut` method will clear the user's session and remove the token from the secure storage.
@ -216,85 +235,39 @@ void signOut() async {
</Step> </Step>
<Step title="Handle authentication state" subtitle="1 step"> <Step title="Handle authentication status" subtitle="1 step">
In Logto SDK, you can use `logtoClient.isAuthenticated` to check the authentication status, if the 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`.
user is signed in, the value will be `true`, otherwise, the value will be `false`.
```dart Define a boolean variable `isAuthenticated` in the state of your application to keep track of the authentication status.
bool isAuthenticated = await logtoClient.isAuthenticated;
```
</Step> ```dart title="lib/main.dart"
class _MyHomePageState extends State<MyHomePage>{
bool isAuthenticated = false;
<Step title="Checkpoint: Test your application" subtitle="1 step"> // ...
void render() async {
if (await logtoClient.isAuthenticated()) {
setState(() {
isAuthenticated = true;
});
Now let's wrap up the implementation and test your application. return;
}
<Code className="language-dart"> setState(() {
{`import 'package:logto_dart_sdk/logto_dart_sdk.dart'; isAuthenticated = false;
import 'package:http/http.dart' as http;
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(title: 'Logto Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late LogtoClient logtoClient;
bool? isAuthenticated = false;
// Update the authentication state
void render() {
setState(() async {
isAuthenticated = await logtoClient.isAuthenticated;
}); });
} }
// LogtoConfig @overwrite
final logtoConfig = const LogtoConfig(
endpoint: '${props.endpoint}',
appId: '${props.app.id}',
);
void _init() {
logtoClient = LogtoClient(
config: logtoConfig,
httpClient: http.Client(), // Optional http client
);
render();
}
@override
void initState() {
super.initState();
_init();
}
@override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget signInButton = TextButton( // ...
Widget signInButton = TextButton(
onPressed: () async { onPressed: () async {
await logtoClient.signIn('${props.redirectUris[0] ?? 'io.logto://callback'}'); await logtoClient.signIn(redirectUri);
render(); render();
}, },
child: const Text('Sign In'), child: const Text('Sign In'),
@ -316,7 +289,6 @@ class _MyHomePageState extends State<MyHomePage> {
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ children: <Widget>[
SelectableText('My Demo App'),
isAuthenticated ? signOutButton : signInButton, isAuthenticated ? signOutButton : signInButton,
], ],
), ),
@ -324,8 +296,13 @@ class _MyHomePageState extends State<MyHomePage> {
); );
} }
} }
`} ```
</Code>
</Step>
<Step title="Checkpoint: Test your application">
<Checkpoint />
</Step> </Step>

View file

@ -5,7 +5,7 @@ import InlineNotification from '@/ds-components/InlineNotification';
import Steps from '@/mdx-components/Steps'; import Steps from '@/mdx-components/Steps';
import Step from '@/mdx-components/Step'; import Step from '@/mdx-components/Step';
import Checkpoint from '../../fragments/_checkpoint.md'; import Checkpoint from '../../fragments/_checkpoint.md';
import RedirectUrisNative from '../../fragments/_redirect-uris-native.mdx'; import RedirectUrisNative, { defaultRedirectUri } from '../../fragments/_redirect-uris-native.mdx';
<Steps> <Steps>
@ -110,7 +110,7 @@ For example, in a SwiftUI app:
Task { [self] in Task { [self] in
do { do {
try await client.signInWithBrowser(redirectUri: "${ try await client.signInWithBrowser(redirectUri: "${
props.redirectUris[0] ?? 'io.logto://callback' props.redirectUris[0] ?? defaultRedirectUri
}") }")
isAuthenticated = true isAuthenticated = true
} catch let error as LogtoClientErrors.SignIn { } catch let error as LogtoClientErrors.SignIn {