body method
Defines the actual body code. path is passed relative to lib, baseName is the filename, and className is the filename converted to Pascal case.
実際の本体コードを定義します。pathにlibからの相対パス、baseNameにファイル名が渡され、classNameにファイル名をパスカルケースに変換した値が渡されます。
Implementation
@override
String body(String baseName, String className) {
return r"""
Masamuneフレームワークでは`appAuth`を用いた認証機能を提供しています。
## 概要
`appAuth`は`main.dart`で定義されている認証オブジェクトで、ユーザー認証を管理します。
**対応する認証方式**:
- **メールアドレス・パスワード認証** - 登録、ログイン、パスワードリセット
- **メールリンク認証** - パスワード不要のメール認証
- **SMS認証** - 電話番号とSMSコードによる認証
- **匿名認証** - 一時的なユーザーID認証
- **SNS認証** - Apple、Google、GitHub、Facebookアカウント認証(別ドキュメント参照)
- **デバッグ認証** - 開発用の直接ユーザーID指定認証
**重要**: デフォルトでは認証データはアプリのメモリにのみ保存され、アプリ再起動時にリセットされます。永続化するにはFirebase認証アダプターが必要です。
## 基本的な使い方
### appAuthの利用
`appAuth`は`lib/main.dart`でトップレベルに定義されている認証オブジェクトです。アプリ内のどこからでも`appAuth`を使用して認証機能を利用できます。
```dart
import 'package:masamune/masamune.dart';
class AuthPage extends PageScopedWidget {
@override
Widget build(BuildContext context, PageRef ref) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (appAuth.isSignedIn)
Text("ログイン済み: ${appAuth.userId}")
else
Text("未ログイン"),
],
),
),
);
}
}
```
### 認証状態の確認
`appAuth`では以下のプロパティで認証状態を確認できます。
```dart
// ログイン状態の確認
if (appAuth.isSignedIn) {
print("ログイン中");
}
// 匿名ログイン状態の確認
if (appAuth.isAnonymously) {
print("匿名ログイン中");
}
// ユーザー情報の取得
print("ユーザーID: ${appAuth.userId}");
print("メールアドレス: ${appAuth.userEmail}");
print("電話番号: ${appAuth.userPhoneNumber}");
```
## メールアドレス・パスワード認証
メールアドレスとパスワードによる認証方式です。最初にユーザー登録が必要です。
### ユーザー登録
```dart
import 'package:katana_auth/katana_auth.dart';
class RegisterPage extends PageScopedWidget {
@override
Widget build(BuildContext context, PageRef ref) {
final form = ref.page.form(RegisterValue.form(RegisterValue(
email: "",
password: "",
)));
return Scaffold(
appBar: AppBar(title: Text("ユーザー登録")),
body: Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
FormTextField(
form: form,
keyboardType: TextInputType.emailAddress,
name: "email",
hintText: "メールアドレス",
),
SizedBox(height: 16),
FormTextField(
form: form,
obscureText: true,
name: "password",
hintText: "パスワード",
),
SizedBox(height: 24),
FormButton(
form: form,
onPressed: () async {
try {
await appAuth.register(
EmailAndPasswordAuthQuery.register(
email: form.value.email,
password: form.value.password,
),
);
context.navigator.pop();
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("登録に失敗しました: $e")),
);
}
},
child: Text("登録"),
),
],
),
),
);
}
}
```
### ログイン
```dart
class LoginPage extends PageScopedWidget {
@override
Widget build(BuildContext context, PageRef ref) {
final form = ref.page.form(LoginValue.form(LoginValue(
email: "",
password: "",
)));
return Scaffold(
appBar: AppBar(title: Text("ログイン")),
body: Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
FormTextField(
form: form,
keyboardType: TextInputType.emailAddress,
name: "email",
hintText: "メールアドレス",
),
SizedBox(height: 16),
FormTextField(
form: form,
obscureText: true,
name: "password",
hintText: "パスワード",
),
SizedBox(height: 24),
FormButton(
form: form,
onPressed: () async {
try {
await appAuth.signIn(
EmailAndPasswordAuthQuery.signIn(
email: form.value.email,
password: form.value.password,
),
);
context.navigator.pop();
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("ログインに失敗しました: $e")),
);
}
},
child: Text("ログイン"),
),
],
),
),
);
}
}
```
### ログアウト
```dart
class ProfilePage extends PageScopedWidget {
@override
Widget build(BuildContext context, PageRef ref) {
return Scaffold(
appBar: AppBar(title: Text("プロフィール")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("ユーザーID: ${appAuth.userId}"),
Text("Email: ${appAuth.userEmail}"),
SizedBox(height: 24),
ElevatedButton(
onPressed: () async {
await appAuth.signOut();
context.navigator.pop();
},
child: Text("ログアウト"),
),
],
),
),
);
}
}
```
### パスワードリセット
パスワードを忘れた場合、リセットメールを送信できます。
```dart
class PasswordResetPage extends PageScopedWidget {
@override
Widget build(BuildContext context, PageRef ref) {
final form = ref.page.form(ResetValue.form(ResetValue(email: "")));
return Scaffold(
appBar: AppBar(title: Text("パスワードリセット")),
body: Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
FormTextField(
form: form,
keyboardType: TextInputType.emailAddress,
name: "email",
hintText: "メールアドレス",
),
SizedBox(height: 24),
FormButton(
form: form,
onPressed: () async {
try {
await appAuth.reset(
EmailAndPasswordAuthQuery.reset(
email: form.value.email,
locale: Locale("ja"), // 日本語メール
),
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("リセットメールを送信しました")),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("送信に失敗しました: $e")),
);
}
},
child: Text("リセットメールを送信"),
),
],
),
),
);
}
}
```
### メールアドレスの変更
ログイン後、メールアドレスを変更できます。
```dart
class ChangeEmailPage extends PageScopedWidget {
@override
Widget build(BuildContext context, PageRef ref) {
final form = ref.page.form(ChangeEmailValue.form(ChangeEmailValue(
newEmail: "",
)));
return Scaffold(
appBar: AppBar(title: Text("メールアドレス変更")),
body: Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
Text("現在のメールアドレス: ${appAuth.userEmail}"),
SizedBox(height: 16),
FormTextField(
form: form,
keyboardType: TextInputType.emailAddress,
name: "newEmail",
hintText: "新しいメールアドレス",
),
SizedBox(height: 24),
FormButton(
form: form,
onPressed: () async {
try {
await appAuth.changeEmail(
EmailAndPasswordAuthQuery.changeEmail(
email: form.value.newEmail,
locale: Locale("ja"),
),
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("メールアドレスを変更しました")),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("変更に失敗しました: $e")),
);
}
},
child: Text("変更"),
),
],
),
),
);
}
}
```
### パスワードの変更
ログイン後、パスワードを変更できます。
```dart
class ChangePasswordPage extends PageScopedWidget {
@override
Widget build(BuildContext context, PageRef ref) {
final form = ref.page.form(ChangePasswordValue.form(ChangePasswordValue(
newPassword: "",
)));
return Scaffold(
appBar: AppBar(title: Text("パスワード変更")),
body: Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
FormTextField(
form: form,
obscureText: true,
name: "newPassword",
hintText: "新しいパスワード",
),
SizedBox(height: 24),
FormButton(
form: form,
onPressed: () async {
try {
await appAuth.changePassword(
EmailAndPasswordAuthQuery.changePassword(
password: form.value.newPassword,
locale: Locale("ja"),
),
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("パスワードを変更しました")),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("変更に失敗しました: $e")),
);
}
},
child: Text("変更"),
),
],
),
),
);
}
}
```
### 再認証
アカウント削除やセキュリティが必要な操作の前に、再認証が必要になる場合があります。
```dart
class DeleteAccountPage extends PageScopedWidget {
@override
Widget build(BuildContext context, PageRef ref) {
final form = ref.page.form(ReAuthValue.form(ReAuthValue(
password: "",
)));
return Scaffold(
appBar: AppBar(title: Text("アカウント削除")),
body: Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
Text("アカウントを削除するには、パスワードを再入力してください"),
SizedBox(height: 16),
FormTextField(
form: form,
obscureText: true,
name: "password",
hintText: "パスワード",
),
SizedBox(height: 24),
FormButton(
form: form,
onPressed: () async {
try {
// 再認証
await appAuth.reauth(
EmailAndPasswordAuthQuery.reauth(
password: form.value.password,
),
);
// アカウント削除
await appAuth.delete();
context.navigator.pop();
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("削除に失敗しました: $e")),
);
}
},
child: Text("削除"),
),
],
),
),
);
}
}
```
## メールリンク認証
パスワードを使わずにメールのリンクで認証する方式です。
### リンク送信
```dart
class EmailLinkSignInPage extends PageScopedWidget {
@override
Widget build(BuildContext context, PageRef ref) {
final form = ref.page.form(EmailLinkValue.form(EmailLinkValue(
email: "",
)));
return Scaffold(
appBar: AppBar(title: Text("メールリンクログイン")),
body: Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
FormTextField(
form: form,
keyboardType: TextInputType.emailAddress,
name: "email",
hintText: "メールアドレス",
),
SizedBox(height: 24),
FormButton(
form: form,
onPressed: () async {
try {
await appAuth.signIn(
EmailLinkAuthQuery.signIn(
email: form.value.email,
url: "https://example.com/auth", // 認証後のリダイレクトURL
locale: Locale("ja"),
),
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("認証メールを送信しました")),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("送信に失敗しました: $e")),
);
}
},
child: Text("認証メールを送信"),
),
],
),
),
);
}
}
```
### リンク確認
メールのリンクをクリックした後、認証を完了します。
```dart
class EmailLinkConfirmPage extends PageScopedWidget {
@override
Widget build(BuildContext context, PageRef ref) {
ref.page.on(
initOrUpdate: () async {
// URLからリンクを取得
final url = "取得したリンクURL";
try {
await appAuth.confirmSignIn(
EmailLinkAuthQuery.confirmSignIn(url: url),
);
context.navigator.pushReplacementNamed("/home");
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("認証に失敗しました: $e")),
);
}
},
);
return Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
}
```
## SMS認証
電話番号とSMSコードによる認証方式です。
### SMS送信
```dart
class SmsSignInPage extends PageScopedWidget {
@override
Widget build(BuildContext context, PageRef ref) {
final form = ref.page.form(SmsValue.form(SmsValue(
phoneNumber: "",
)));
return Scaffold(
appBar: AppBar(title: Text("SMS認証")),
body: Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
FormTextField(
form: form,
keyboardType: TextInputType.phone,
name: "phoneNumber",
hintText: "電話番号(例: 09012345678)",
),
SizedBox(height: 24),
FormButton(
form: form,
onPressed: () async {
try {
await appAuth.signIn(
SmsAuthQuery.signIn(
phoneNumber: form.value.phoneNumber,
countryNumber: "81", // 日本: 81
locale: Locale("ja"),
),
);
// 確認画面へ遷移
context.navigator.pushNamed("/sms-confirm");
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("SMS送信に失敗しました: $e")),
);
}
},
child: Text("SMSを送信"),
),
],
),
),
);
}
}
```
### コード確認
```dart
class SmsConfirmPage extends PageScopedWidget {
@override
Widget build(BuildContext context, PageRef ref) {
final form = ref.page.form(SmsCodeValue.form(SmsCodeValue(
code: "",
)));
return Scaffold(
appBar: AppBar(title: Text("SMS認証コード入力")),
body: Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
Text("SMSで送られたコードを入力してください"),
SizedBox(height: 16),
FormPinField(
form: form,
name: "code",
length: 6,
),
SizedBox(height: 24),
FormButton(
form: form,
onPressed: () async {
try {
await appAuth.confirmSignIn(
SmsAuthQuery.confirmSignIn(code: form.value.code),
);
context.navigator.pushReplacementNamed("/home");
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("認証に失敗しました: $e")),
);
}
},
child: Text("認証"),
),
],
),
),
);
}
}
```
### 電話番号の変更
```dart
class ChangePhoneNumberPage extends PageScopedWidget {
@override
Widget build(BuildContext context, PageRef ref) {
final form = ref.page.form(ChangePhoneValue.form(ChangePhoneValue(
phoneNumber: "",
)));
return Scaffold(
appBar: AppBar(title: Text("電話番号変更")),
body: Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
Text("現在の電話番号: ${appAuth.userPhoneNumber}"),
SizedBox(height: 16),
FormTextField(
form: form,
keyboardType: TextInputType.phone,
name: "phoneNumber",
hintText: "新しい電話番号",
),
SizedBox(height: 24),
FormButton(
form: form,
onPressed: () async {
try {
await appAuth.changePhoneNumber(
SmsAuthQuery.changePhoneNumber(
phoneNumber: form.value.phoneNumber,
countryNumber: "81",
locale: Locale("ja"),
),
);
// 確認画面へ遷移
context.navigator.pushNamed("/change-phone-confirm");
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("SMS送信に失敗しました: $e")),
);
}
},
child: Text("変更"),
),
],
),
),
);
}
}
```
## 匿名認証
ユーザーIDのみを作成し、一時的にアプリを利用できるようにします。ログアウトやアプリ再インストールで再ログインはできません。
```dart
class AnonymousSignInPage extends PageScopedWidget {
@override
Widget build(BuildContext context, PageRef ref) {
return Scaffold(
appBar: AppBar(title: Text("匿名ログイン")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (appAuth.isSignedIn && appAuth.isAnonymously)
Column(
children: [
Text("匿名ユーザーとしてログイン中"),
Text("User ID: ${appAuth.userId}"),
SizedBox(height: 16),
ElevatedButton(
onPressed: () async {
await appAuth.signOut();
},
child: Text("ログアウト"),
),
],
)
else
ElevatedButton(
onPressed: () async {
try {
await appAuth.signIn(AnonymouslyAuthQuery.signIn());
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("ログインに失敗しました: $e")),
);
}
},
child: Text("匿名でログイン"),
),
],
),
),
);
}
}
```
## デバッグ認証
開発時にユーザーIDを直接指定してログインできます。本番環境では使用しないでください。
```dart
class DebugSignInPage extends PageScopedWidget {
@override
Widget build(BuildContext context, PageRef ref) {
final form = ref.page.form(DebugValue.form(DebugValue(
userId: "",
)));
return Scaffold(
appBar: AppBar(title: Text("デバッグログイン")),
body: Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
FormTextField(
form: form,
name: "userId",
hintText: "ユーザーID",
),
SizedBox(height: 24),
FormButton(
form: form,
onPressed: () async {
try {
await appAuth.signIn(
DirectAuthQuery.signIn(userId: form.value.userId),
);
context.navigator.pop();
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("ログインに失敗しました: $e")),
);
}
},
child: Text("ログイン"),
),
],
),
),
);
}
}
```
## SNS認証
Apple、Google、GitHub、Facebookなどのアカウントでログインする方法については、[SNSログイン機能](plugins/sns_auth.md)を参照してください。
## 認証アダプターの切り替え
### RuntimeAuthAdapter(デフォルト)
メモリのみに認証状態を保存します。アプリ再起動でリセットされます。
```dart
// lib/adapter.dart
final authAdapter = const RuntimeAuthAdapter();
```
### LocalAuthAdapter
デバイスローカルに認証状態を保存します。アプリ再起動後も維持されますが、端末間の同期はできません。
```dart
// lib/adapter.dart
final authAdapter = const LocalAuthAdapter();
```
### FirebaseAuthAdapter
Firebase Authenticationを使用して、複数端末間で認証状態を同期できます。
```bash
flutter pub add katana_auth_firebase
```
```dart
// lib/adapter.dart
import 'package:katana_auth_firebase/katana_auth_firebase.dart';
import "package:any_apps/firebase_options.dart";
final authAdapter = const FirebaseAuthAdapter(
options: DefaultFirebaseOptions.currentPlatform,
);
```
## AuthQuery一覧
### EmailAndPasswordAuthQuery
メールアドレス・パスワード認証で使用するクエリ。
- `EmailAndPasswordAuthQuery.register()` - ユーザー登録
- `EmailAndPasswordAuthQuery.signIn()` - ログイン
- `EmailAndPasswordAuthQuery.reauth()` - 再認証
- `EmailAndPasswordAuthQuery.reset()` - パスワードリセット
- `EmailAndPasswordAuthQuery.verify()` - メール認証送信
- `EmailAndPasswordAuthQuery.changeEmail()` - メールアドレス変更
- `EmailAndPasswordAuthQuery.changePassword()` - パスワード変更
### EmailLinkAuthQuery
メールリンク認証で使用するクエリ。
- `EmailLinkAuthQuery.signIn()` - リンク送信
- `EmailLinkAuthQuery.confirmSignIn()` - リンク確認でログイン
- `EmailLinkAuthQuery.verify()` - 認証メール送信
- `EmailLinkAuthQuery.changeEmail()` - メールアドレス変更
### SmsAuthQuery
SMS認証で使用するクエリ。
- `SmsAuthQuery.signIn()` - SMS送信
- `SmsAuthQuery.confirmSignIn()` - コード確認でログイン
- `SmsAuthQuery.changePhoneNumber()` - 電話番号変更
- `SmsAuthQuery.confirmChangePhoneNumber()` - 電話番号変更確認
### AnonymouslyAuthQuery
匿名認証で使用するクエリ。
- `AnonymouslyAuthQuery.signIn()` - 匿名ログイン
### DirectAuthQuery
デバッグ認証で使用するクエリ。
- `DirectAuthQuery.signIn()` - ユーザーID指定ログイン
## 参考資料
- [katana_auth パッケージ](https://pub.dev/packages/katana_auth)
- [SNSログイン機能](plugins/sns_auth.md)
- [Firebase Authentication設定](https://firebase.google.com/docs/auth)
""";
}