Flutter认证授权插件katana_auth的使用

Flutter认证授权插件katana_auth的使用

Masamune logo

Katana Auth

Follow on Twitter Follow on Threads Maintained with Melos

GitHub Sponsor


[GitHub]

[YouTube]

[Packages]

[Twitter]

[Threads]

[LinkedIn]

[mathru.net]


简介

FirebaseAuthentication用于处理认证问题非常有用。

可以轻松实现各种类型的认证,包括通过电子邮件地址、电话号码和社交网络账户进行认证。

然而,即使以后可能会使用Firebase认证,也可能有以下需求:在创建应用程序的原型时,不连接到服务器的情况下实现认证;或者在测试代码中实现认证。

因此,我实现了一个包,可以通过适配器切换Firebase和本地认证,就像我在katana_model中做的那样。

此外,该接口得到了改进,使得根据应用程序的不同,可以轻松地切换Google登录和Apple登录。


安装

导入以下包:

flutter pub add katana_auth

如果使用Firestore,一起导入以下包:

flutter pub add katana_auth_firebase

实现

准备工作

始终将AuthAdapterScope小部件放置在应用程序根附近。

传递一个AuthAdapter,如RuntimeAuthAdapter作为适配器参数。

// main.dart
import 'package:flutter/material.dart';
import 'package:katana_auth/katana_auth.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) {
    return AuthAdapterScope(
      adapter: const RuntimeAuthAdapter(),
      child: MaterialApp(
        home: const AuthPage(),
        title: "Flutter Demo",
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
      ),
    );
  }
}

创建认证对象

为了执行认证,首先创建一个Authentication并将其保存在某个地方。

Authentication对象可以获取以下数据来检查认证状态。

  • isSignedIn:如果已认证,则返回true
  • isAnonymously:对于匿名认证返回true
  • userId:返回用户ID。
  • userEmail:如果执行了电子邮件认证等,则返回用户的电子邮件地址。
  • userPhoneNumber:如果执行了电话号码验证,则返回用户的电话号码。

此外,由于Authentication继承了ChangeNotifier,可以使用addListener或riverpod的ChangeNotifierProvider等来监控更新。

// auth_page.dart
import 'package:flutter/material.dart';
import 'package:katana_auth/katana_auth.dart';

class AuthPage extends StatefulWidget {
  const AuthPage({super.key});

  [@override](/user/override)
  State<StatefulWidget> createState() => AuthPageState();
}

class AuthPageState extends State<AuthPage> {
  final auth = Authentication();

  [@override](/user/override)
  void initState() {
    super.initState();
    auth.addListener(_handledOnUpdate);
  }

  void _handledOnUpdate() {
    setState(() {});
  }

  [@override](/user/override)
  void dispose() {
    super.dispose();
    auth.removeListener(_handledOnUpdate);
    auth.dispose();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("App Demo")),
      body: ListView(
        children: [
          ListTile(
            title: Text("SignedIn: ${auth.isSignedIn}"),
          ),
          ListTile(
            title: Text("Anonymously: ${auth.isAnonymously}"),
          ),
          ListTile(
            title: Text("ID: ${auth.userId}"),
          ),
          ListTile(
            title: Text("Email: ${auth.userEmail}"),
          ),
          ListTile(
            title: Text("Phone: ${auth.userPhoneNumber}"),
          ),
          ListTile(
            title: Text("Providers: ${auth.activeProviderIds?.join("\n")}"),
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          Navigator.of(context).push(MaterialPageRoute(
            builder: (_) {
              return AuthControlPage(auth: auth);
            },
          ));
        },
        child: const Icon(Icons.person),
      ),
    );
  }
}

用户注册与登录

使用auth.register方法来注册用户。

必须传递RegisterAuthProvider作为参数。

RegisterAuthProvider应该使用带有register方法的AuthQuery获得。

AuthQuery将在下面讨论。)

await auth.register(
  EmailAndPasswordAuthQuery.register(
    email: "test@email.com",
    password: "12345678",
  ),
);

此外,使用auth.signIn来执行登录。

必须传递SignInAuthProvider作为参数,并应使用带有signIn方法的AuthQuery获得。

await auth.signIn(
  EmailAndPasswordAuthQuery.signIn(
    email: "test@email.com",
    password: "12345678",
  ),
);

使用auth.confirmSignIn来确认认证,例如进行邮件链接认证或SMS认证。

await auth.confirmSignIn(
  SmsAuthQuery.confirmSignIn(
    code: "012345",
  ),
);

更改用户信息

使用auth.change来更改用户信息。

必须传递ChangeAuthProvider作为参数,但可以根据要更改的内容改变AuthQuery方法。

  • EmailAndPasswordAuthQuery.changeEmail:更改电子邮件地址。
  • EmailAndPasswordAuthQuery.changePassword:更改密码。
  • SmsAuthQuery.changePhoneNumber:更改电话号码。
await auth.change(
  EmailAndPasswordAuthQuery.changeEmail(
    email: "changed@email.com"
  ),
);

(仅在登录后可用。)

注销

使用auth.signOut来注销。

仅在登录后可用,无需任何参数。

await auth.signOut();

AuthAdapter

通过在定义AuthAdapterScope时传递它,可以更改认证系统。

  • RuntimeAuthAdapter:仅在应用程序启动时工作的认证系统。当应用程序重新启动时,会重置认证信息。测试时使用此系统。
  • LocalAuthAdapter:仅在设备上本地工作的认证系统。即使应用程序重新启动,认证信息也会保留,但不能与其他设备共享。
  • FirebaseAuthAdapter:FirebaseAuthentication系统。允许终端之间共享认证信息;需要初始Firebase配置

AuthQuery

为每个认证提供商提供了AuthQuery

通过使用AuthQuery方法,可以使用Authentication类提供的认证功能。

(可用性受认证提供商限制。)

  • AnonymouslyAuthQuery:提供匿名认证的AuthQuery
  • EmailAndPasswordAuthQuery:提供通过电子邮件地址和密码进行认证的AuthQuery
  • EmailLinkAuthQuery:提供通过电子邮件链接进行认证的AuthQuery
  • SmsAuthQuery:提供通过SMS进行认证的AuthQuery
  • SnsSignInAuthProvider:提供通过社交网络账户进行认证的AuthQuery的抽象类。由于它是抽象类,实际的AuthQuery可以通过加载其他相关包来使用。

GitHub赞助者

欢迎赞助。感谢您的支持!

https://github.com/sponsors/mathrunet


示例代码

// Flutter imports:
import 'package:flutter/material.dart';

// Package imports:
import 'package:katana_auth/katana_auth.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) {
    return AuthAdapterScope(
      adapter: const RuntimeAuthAdapter(),
      child: MaterialApp(
        home: const AuthPage(),
        title: "Flutter Demo",
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
      ),
    );
  }
}

class AuthPage extends StatefulWidget {
  const AuthPage({super.key});

  [@override](/user/override)
  State<StatefulWidget> createState() => AuthPageState();
}

class AuthPageState extends State<AuthPage> {
  final auth = Authentication();

  [@override](/user/override)
  void initState() {
    super.initState();
    auth.addListener(_handledOnUpdate);
  }

  void _handledOnUpdate() {
    setState(() {});
  }

  [@override](/user/override)
  void dispose() {
    super.dispose();
    auth.removeListener(_handledOnUpdate);
    auth.dispose();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("App Demo")),
      body: ListView(
        children: [
          ListTile(
            title: Text("SignedIn: ${auth.isSignedIn}"),
          ),
          ListTile(
            title: Text("Anonymously: ${auth.isAnonymously}"),
          ),
          ListTile(
            title: Text("ID: ${auth.userId}"),
          ),
          ListTile(
            title: Text("Email: ${auth.userEmail}"),
          ),
          ListTile(
            title: Text("Phone: ${auth.userPhoneNumber}"),
          ),
          ListTile(
            title: Text("Providers: ${auth.activeProviderIds?.join("\n")}"),
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          Navigator.of(context).push(MaterialPageRoute(
            builder: (_) {
              return AuthControlPage(auth: auth);
            },
          ));
        },
        child: const Icon(Icons.person),
      ),
    );
  }
}

class AuthControlPage extends StatelessWidget {
  const AuthControlPage({
    required this.auth,
    super.key,
  });

  final Authentication auth;

  [@override](/user/override)
  Widget build(BuildContext context) {
    final navigator = Navigator.of(context);
    return Scaffold(
      appBar: AppBar(
        title: const Text("Auth Control"),
      ),
      body: ListView(
        children: [
          if (!auth.isSignedIn) ...[
            if (!auth.isWaitingConfirmation) ...[
              ListTile(
                title: const Text("SignIn anonymously"),
                onTap: () async {
                  await auth.signIn(AnonymouslyAuthQuery.signIn());
                  navigator.pop();
                },
              ),
              ListTile(
                title: const Text("Register with email and password"),
                onTap: () async {
                  await auth.register(
                    EmailAndPasswordAuthQuery.register(
                      email: "test@email.com",
                      password: "12345678",
                    ),
                  );
                  navigator.pop();
                },
              ),
              ListTile(
                title: const Text("SignIn with email and password"),
                onTap: () async {
                  await auth.signIn(
                    EmailAndPasswordAuthQuery.signIn(
                      email: "test@email.com",
                      password: "12345678",
                    ),
                  );
                  navigator.pop();
                },
              ),
              ListTile(
                title: const Text("SignIn with email link"),
                onTap: () async {
                  await auth.signIn(
                    EmailLinkAuthQuery.signIn(
                      email: "test@email.com",
                      url: "https://test.com",
                    ),
                  );
                  navigator.pop();
                },
              ),
              ListTile(
                title: const Text("SignIn with sms"),
                onTap: () async {
                  await auth.signIn(
                    SmsAuthQuery.signIn(
                      phoneNumber: "01234567890",
                      countryNumber: "81",
                    ),
                  );
                  navigator.pop();
                },
              ),
            ] else ...[
              ListTile(
                title: const Text("Confirm signIn with email link"),
                onTap: () async {
                  await auth.confirmSignIn(
                    EmailLinkAuthQuery.confirmSignIn(
                      url: "https://test.com",
                    ),
                  );
                  navigator.pop();
                },
              ),
              ListTile(
                title: const Text("Confirm signIn with sms"),
                onTap: () async {
                  await auth.confirmSignIn(
                    SmsAuthQuery.confirmSignIn(
                      code: "012345",
                    ),
                  );
                  navigator.pop();
                },
              ),
            ],
          ] else ...[
            if (!auth.isWaitingConfirmation) ...[
              ListTile(
                title: const Text("Change email"),
                onTap: () async {
                  await auth.change(
                    EmailAndPasswordAuthQuery.changeEmail(
                        email: "changed@email.com"),
                  );
                  navigator.pop();
                },
              ),
              ListTile(
                title: const Text("Change password"),
                onTap: () async {
                  await auth.change(
                    EmailAndPasswordAuthQuery.changePassword(password: ""),
                  );
                  navigator.pop();
                },
              ),
              ListTile(
                title: const Text("Change phoneNumber"),
                onTap: () async {
                  await auth.change(
                    SmsAuthQuery.changePhoneNumber(
                      phoneNumber: "1234567890",
                      countryNumber: "81",
                    ),
                  );
                  navigator.pop();
                },
              ),
              ListTile(
                title: const Text("SignOut"),
                onTap: () async {
                  await auth.signOut();
                  navigator.pop();
                },
              ),
            ] else ...[
              ListTile(
                title: const Text("Confirm changing Phone number with sms"),
                onTap: () async {
                  await auth.confirmChange(
                    SmsAuthQuery.confirmChangePhoneNumber(
                      code: "012345",
                    ),
                  );
                  navigator.pop();
                },
              ),
            ],
          ],
        ],
      ),
    );
  }
}

更多关于Flutter认证授权插件katana_auth的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter认证授权插件katana_auth的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter应用中使用katana_auth插件进行认证授权的示例代码。katana_auth是一个强大的认证插件,可以帮助你快速实现用户认证和授权功能。假设你已经创建了一个Flutter项目,以下是实现基本认证功能的步骤和代码示例。

1. 添加依赖

首先,在你的pubspec.yaml文件中添加katana_auth依赖:

dependencies:
  flutter:
    sdk: flutter
  katana_auth: ^latest_version # 请替换为最新版本号

然后运行flutter pub get来安装依赖。

2. 配置KatanaAuth

在你的Flutter应用中,你需要初始化KatanaAuth实例。通常在main.dart文件中进行配置。

import 'package:flutter/material.dart';
import 'package:katana_auth/katana_auth.dart';

void main() {
  // 初始化KatanaAuth
  KatanaAuth.instance.init(
    clientId: 'your-client-id', // 替换为你的客户端ID
    clientSecret: 'your-client-secret', // 替换为你的客户端密钥
    redirectUri: 'your-redirect-uri', // 替换为你的重定向URI
    authorizationEndpoint: 'your-authorization-endpoint', // 替换为你的授权端点
    tokenEndpoint: 'your-token-endpoint', // 替换为你的令牌端点
  );

  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: HomeScreen(),
    );
  }
}

3. 实现登录功能

在你的HomeScreen或其他相关页面中,实现登录功能。

import 'package:flutter/material.dart';
import 'package:katana_auth/katana_auth.dart';

class HomeScreen extends StatefulWidget {
  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  final _formKey = GlobalKey<FormState>();
  String _email = '';
  String _password = '';

  void _login() async {
    if (_formKey.currentState!.validate()) {
      _formKey.currentState!.save();

      try {
        // 发起认证请求
        var result = await KatanaAuth.instance.login(
          username: _email,
          password: _password,
        );

        // 处理认证结果
        if (result.isSuccess) {
          // 登录成功,可以保存令牌或使用令牌进行后续请求
          print('Access Token: ${result.data?.accessToken}');
          // 例如,你可以将令牌保存到本地存储或进行其他处理
        } else {
          // 登录失败,处理错误信息
          print('Error: ${result.error?.message}');
        }
      } catch (e) {
        print('Exception: $e');
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Login Screen'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Form(
          key: _formKey,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              TextFormField(
                decoration: InputDecoration(labelText: 'Email'),
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Please enter your email.';
                  }
                  return null;
                },
                onSaved: (value) {
                  _email = value!;
                },
              ),
              TextFormField(
                decoration: InputDecoration(labelText: 'Password'),
                obscureText: true,
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Please enter your password.';
                  }
                  return null;
                },
                onSaved: (value) {
                  _password = value!;
                },
              ),
              SizedBox(height: 20),
              ElevatedButton(
                onPressed: _login,
                child: Text('Login'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

4. 处理登出和令牌刷新

你还可以实现登出功能以及令牌刷新功能。以下是如何实现登出的示例代码:

void _logout() async {
  try {
    await KatanaAuth.instance.logout();
    // 处理登出后的逻辑,例如清除本地存储的令牌信息
    print('User logged out successfully.');
  } catch (e) {
    print('Exception during logout: $e');
  }
}

令牌刷新通常会在令牌即将过期时自动进行,但你也可以手动触发刷新:

void _refreshToken() async {
  try {
    var result = await KatanaAuth.instance.refreshToken();
    if (result.isSuccess) {
      // 令牌刷新成功,可以更新本地存储的令牌信息
      print('New Access Token: ${result.data?.accessToken}');
    } else {
      // 令牌刷新失败,处理错误信息
      print('Error refreshing token: ${result.error?.message}');
    }
  } catch (e) {
    print('Exception during token refresh: $e');
  }
}

以上示例展示了如何在Flutter应用中使用katana_auth插件进行基本的认证授权操作。根据你的具体需求,你可以进一步扩展和自定义这些功能。

回到顶部