FlutterでAndroidでのAuth0のログイン・ログアウトを実装してみた
こんにちは、ゲームソリューション部のsoraです。
今回は、FlutterでAndroidでのAuth0のログイン・ログアウトを実装してみたことについて書いていきます。
主に利用するパッケージ
flutter_appauth
Auth0のログイン・ログアウトを実装するために必要
※公式パッケージとしてflutter_auth0がありますが、リダイレクトURLの設定が上手くいかなかったため、今回は使用しません。(ちなみにlikesも公式の方が少なかったです。)
flutter_riverpod
Riverpodを使用するために必要
今回使用したpubspec.yaml
の関係する部分を一部抜粋して記載します。
# (一部抜粋)
dependencies:
flutter:
sdk: flutter
flutter_appauth:
http:
flutter_riverpod:
url_launcher:
cupertino_icons: ^1.0.8
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
Auth0での設定
Auth0にてApplications
からアプリケーションを作成します。Application TypeはNativeです。
Application URLsのAllowed Callback URLsとAllowed Logout URLsには、{scheme}://callback
を入れます。
(今回の私のアプリだと、com.example.appauth2://callback
になります。)
またConnectionsのDatabase
タブで、使用するデータベースやソーシャルログインを指定できるため、必要に応じて設定します。
今回は、Auth0内のDatabaseを作成して使用することにしました。
Flutterコード
Flutterのコードは以下です。
ログインログアウトのボタンが2つある簡単な画面です。
Auth0の設定部分で指定している_domain
などは、Auth0の管理画面から確認できます。
AuthorizationTokenRequest
のscopesは、このアプリがどの情報にアクセスする可能かの許可範囲を指定しています。
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_appauth/flutter_appauth.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
final authProvider = Provider<AuthService>((ref) => AuthService());
void main() {
runApp(
ProviderScope(
child: MyApp(),
),
);
}
class AuthService {
final FlutterAppAuth _appAuth = FlutterAppAuth();
// Auth0の設定
final String _clientId = '{clientId}';
final String _domain = '{domain}';
// schemeはbuild.gradleのnamespaceにある"com.example.test"のような形式
final String _redirectUri = 'com.example.appauth2://callback';
final String _issuer = 'https://{$_domain}';
Future<void> login() async {
try {
final AuthorizationTokenResponse? result =
await _appAuth.authorizeAndExchangeCode(
AuthorizationTokenRequest(
_clientId,
_redirectUri,
issuer: _issuer,
scopes: ['openid', 'profile', 'email'],
),
);
if (result != null) {
final userInfoResponse = await http.get(
Uri.https('$_domain', '/userinfo'),
headers: {'Authorization': 'Bearer ${result.accessToken}'},
);
final userInfo = jsonDecode(userInfoResponse.body);
String? email = userInfo['email'];
print('ログイン成功: $email');
} else {
print('結果がnull');
}
} catch (e) {
print('ログインエラー: $e');
}
}
Future<void> logout() async {
try {
final url = Uri.https(
'$_domain',
'/v2/logout',
{
'returnTo': _redirectUri,
'client_id': _clientId,
},
);
if (await canLaunchUrl(url)) {
await launchUrl(url, mode: LaunchMode.externalApplication);
print('ログアウト成功');
} else {
throw 'ログアウトURLを開けませんでした';
}
} catch (e) {
print('ログアウトエラー: $e');
}
}
}
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
title: 'Auth0 test',
home: HomePage(),
);
}
}
class HomePage extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final auth = ref.read(authProvider);
return Scaffold(
appBar: AppBar(
title: Text('Auth0 test'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () async {
await auth.login();
},
child: Text('ログイン'),
),
ElevatedButton(
onPressed: () async {
await auth.logout();
},
child: Text('ログアウト'),
),
],
),
),
);
}
}
その他設定の修正
リダイレクト先のScheme設定
{project_name}/android/app/build.gradle
にflutter_appauth
でのリダイレクト先のSchemeを設定します。
defaultConfig {
applicationId = "com.example.appauth2"
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
// この部分を追加
manifestPlaceholders += [
'appAuthRedirectScheme': 'com.example.appauth2'
]
}
ディープリンクの設定
{project_name}/android/app/src/main/AndroidManifest.xml
に、アプリに外部からアクセスするときの入り口を設定します。
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:scheme="com.example.appauth2"
android:host="callback"
android:path="/" />
</intent-filter>
android:path="/"
を入れた場合、このアプリだとcom.example.appauth2://callback/
の完全一致が必要になります。
android:path="/"
を削除した場合、com.example.appauth2://callback
やcom.example.appauth2://callback/anything
なども受け付けるようになります。
後者の場合だと、他のアプリでも同じURLパターンを受け付ける可能性があり、アプリの挙動としてログインログアウト後にどのアプリで開くかの確認が入りました。
taskAffinityの削除
taskAffinityは、アクティビティのタスクスタックの所属を指定するものです。
デフォルトである空文字指定だと、アクティビティが独立したタスクを持つように強制されて、他のアクティビティとタスクを共有しないようになります。
今回のアプリでAuth0での認証を入れると、「ブラウザで認証⇒アプリに戻る」という流れになり、タスクの切り替えがうまくいかなくなります。
そのため、AndroidManifest.xml
にあるandroid:taskAffinity=""
を削除します。
(参考)
動作確認
コード作成と設定修正が終わったら、Androidでの動作確認をします。
ログインボタンを押すと、Auth0のログイン画面に遷移して、ログインできることが確認できました。
ログアウトボタンを押して、ログアウトできることも確認できました。
以下Flutterのログです。
Warningが出ていますが、ログアウトに成功して状態が存在しないことを示しているため正常です。
I/flutter (10046): ログイン成功: {emailアドレス}
I/UrlLauncher(10046): component name for https://{domain}/v2/logout?returnTo=com.example.appauth2...
I/flutter (10046): ログアウト成功
W/AppAuth (10046): No stored state - unable to handle response
最後に
今回は、FlutterでAndroidでのAuth0のログイン・ログアウトを実装してみたことを記事にしました。
どなたかの参考になると幸いです。