Firebase Auth (Dart/Flutter)

認証ライブラリFirebaseDartFlutterOAuthJWTモバイルセキュリティ

認証ライブラリ

Firebase Auth (Dart/Flutter)

概要

Firebase Auth for Flutterは、Googleが提供するFirebaseプラットフォームのFlutter向け認証SDKです。2025年現在、FlutterFireエコシステムの中核を成し、iOS、Android、Web、デスクトップアプリケーション向けに統一された認証体験を提供しています。メール・パスワード認証、OAuth(Google、Facebook、Apple等)、匿名認証、電話番号認証など、豊富な認証方式をサポートし、Firebase Consoleとの連携により企業レベルのユーザー管理機能を提供します。

詳細

Firebase Auth for Flutterは、クロスプラットフォーム認証ソリューションの決定版です。主な特徴:

  • マルチプラットフォーム対応: iOS、Android、Web、macOS、Windows、Linuxでの統一API
  • 豊富な認証方式: メール・パスワード、OAuth、匿名、電話番号、カスタムトークン認証
  • リアルタイム状態管理: authStateChanges、userChanges、idTokenChangesストリーム
  • セキュリティ機能: 多要素認証、メール認証、パスワードリセット機能
  • オフライン対応: 自動的な認証状態の永続化とオフライン機能
  • Firebase統合: Firestore、Cloud Functions、Analytics等との完全統合

メリット・デメリット

メリット

  • Googleが提供する企業グレードの認証基盤で信頼性が高い
  • クロスプラットフォーム対応でコードの再利用性が抜群
  • Firebase Consoleによる包括的なユーザー管理とアナリティクス
  • リアルタイム認証状態管理とリアクティブなUI更新
  • 豊富なOAuth プロバイダーとの統合が簡単
  • 自動的なトークン管理とリフレッシュ機能

デメリット

  • Firebaseエコシステムに依存するベンダーロックイン
  • カスタム認証ロジックの実装に制約がある
  • Firebaseの利用料金が発生(無料枠あり)
  • 複雑な企業認証要件には対応が困難な場合がある
  • Googleアカウントやインターネット接続が必要な機能がある

参考ページ

書き方の例

基本的なセットアップとインストール

# Firebase CLI のインストール
npm install -g firebase-tools

# Firebase にログイン
firebase login

# Flutter プロジェクトでFirebase を設定
flutter pub add firebase_core
flutter pub add firebase_auth

# FlutterFire CLI で設定
dart pub global activate flutterfire_cli
flutterfire configure

# プロジェクトの再ビルド
flutter run

Firebase初期化とメール・パスワード認証

// main.dart - Firebase初期化
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'firebase_options.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // Firebase 初期化
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  
  // エミュレーター使用時(開発環境)
  if (kDebugMode) {
    await FirebaseAuth.instance.useAuthEmulator('localhost', 9099);
  }
  
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Firebase Auth Demo',
      home: AuthWrapper(),
    );
  }
}

// auth_wrapper.dart - 認証状態に基づく画面切り替え
class AuthWrapper extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return StreamBuilder<User?>(
      stream: FirebaseAuth.instance.authStateChanges(),
      builder: (context, snapshot) {
        // 認証状態の確認
        if (snapshot.connectionState == ConnectionState.waiting) {
          return Scaffold(
            body: Center(child: CircularProgressIndicator()),
          );
        } else if (snapshot.hasData && snapshot.data != null) {
          // ログイン済み
          return HomePage();
        } else {
          // 未ログイン
          return LoginPage();
        }
      },
    );
  }
}

// login_page.dart - ログインページ
class LoginPage extends StatefulWidget {
  @override
  _LoginPageState createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();
  final _formKey = GlobalKey<FormState>();
  bool _isLoading = false;

  // メール・パスワードでのユーザー登録
  Future<void> _registerWithEmailPassword() async {
    if (!_formKey.currentState!.validate()) return;

    setState(() => _isLoading = true);

    try {
      final credential = await FirebaseAuth.instance
          .createUserWithEmailAndPassword(
        email: _emailController.text.trim(),
        password: _passwordController.text,
      );

      // 登録後にメール認証を送信
      await credential.user?.sendEmailVerification();
      
      _showMessage('登録成功!認証メールを確認してください。');
    } on FirebaseAuthException catch (e) {
      String message;
      switch (e.code) {
        case 'weak-password':
          message = 'パスワードが弱すぎます。';
          break;
        case 'email-already-in-use':
          message = 'このメールアドレスは既に使用されています。';
          break;
        case 'invalid-email':
          message = '無効なメールアドレスです。';
          break;
        default:
          message = '登録に失敗しました: ${e.message}';
      }
      _showMessage(message);
    } catch (e) {
      _showMessage('予期しないエラーが発生しました: $e');
    } finally {
      setState(() => _isLoading = false);
    }
  }

  // メール・パスワードでのログイン
  Future<void> _signInWithEmailPassword() async {
    if (!_formKey.currentState!.validate()) return;

    setState(() => _isLoading = true);

    try {
      await FirebaseAuth.instance.signInWithEmailAndPassword(
        email: _emailController.text.trim(),
        password: _passwordController.text,
      );
    } on FirebaseAuthException catch (e) {
      String message;
      switch (e.code) {
        case 'user-not-found':
          message = 'ユーザーが見つかりません。';
          break;
        case 'wrong-password':
          message = 'パスワードが間違っています。';
          break;
        case 'invalid-email':
          message = '無効なメールアドレスです。';
          break;
        case 'user-disabled':
          message = 'このアカウントは無効化されています。';
          break;
        default:
          message = 'ログインに失敗しました: ${e.message}';
      }
      _showMessage(message);
    } finally {
      setState(() => _isLoading = false);
    }
  }

  void _showMessage(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(message)),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('ログイン')),
      body: Padding(
        padding: EdgeInsets.all(16.0),
        child: Form(
          key: _formKey,
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              TextFormField(
                controller: _emailController,
                decoration: InputDecoration(
                  labelText: 'メールアドレス',
                  border: OutlineInputBorder(),
                ),
                keyboardType: TextInputType.emailAddress,
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'メールアドレスを入力してください';
                  }
                  if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$')
                      .hasMatch(value)) {
                    return '有効なメールアドレスを入力してください';
                  }
                  return null;
                },
              ),
              SizedBox(height: 16),
              TextFormField(
                controller: _passwordController,
                decoration: InputDecoration(
                  labelText: 'パスワード',
                  border: OutlineInputBorder(),
                ),
                obscureText: true,
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'パスワードを入力してください';
                  }
                  if (value.length < 6) {
                    return 'パスワードは6文字以上で入力してください';
                  }
                  return null;
                },
              ),
              SizedBox(height: 24),
              if (_isLoading)
                CircularProgressIndicator()
              else ...[
                ElevatedButton(
                  onPressed: _signInWithEmailPassword,
                  style: ElevatedButton.styleFrom(
                    minimumSize: Size(double.infinity, 50),
                  ),
                  child: Text('ログイン'),
                ),
                SizedBox(height: 16),
                TextButton(
                  onPressed: _registerWithEmailPassword,
                  child: Text('新規登録'),
                ),
                TextButton(
                  onPressed: _resetPassword,
                  child: Text('パスワードを忘れた場合'),
                ),
              ],
            ],
          ),
        ),
      ),
    );
  }

  // パスワードリセット
  Future<void> _resetPassword() async {
    final email = _emailController.text.trim();
    if (email.isEmpty) {
      _showMessage('メールアドレスを入力してください');
      return;
    }

    try {
      await FirebaseAuth.instance.sendPasswordResetEmail(email: email);
      _showMessage('パスワードリセットメールを送信しました');
    } on FirebaseAuthException catch (e) {
      _showMessage('エラー: ${e.message}');
    }
  }

  @override
  void dispose() {
    _emailController.dispose();
    _passwordController.dispose();
    super.dispose();
  }
}

OAuth認証(Google、Apple等)の実装

// oauth_service.dart - OAuth認証サービス
import 'package:google_sign_in/google_sign_in.dart';
import 'package:flutter_facebook_auth/flutter_facebook_auth.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/foundation.dart';

class OAuthService {
  // Google サインイン
  static Future<UserCredential?> signInWithGoogle() async {
    try {
      if (kIsWeb) {
        // Web版Google認証
        GoogleAuthProvider googleProvider = GoogleAuthProvider();
        googleProvider.addScope('https://www.googleapis.com/auth/contacts.readonly');
        googleProvider.setCustomParameters({
          'login_hint': '[email protected]'
        });

        return await FirebaseAuth.instance.signInWithPopup(googleProvider);
      } else {
        // ネイティブ版Google認証
        final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn();
        if (googleUser == null) return null;

        final GoogleSignInAuthentication googleAuth = 
            await googleUser.authentication;

        final credential = GoogleAuthProvider.credential(
          accessToken: googleAuth.accessToken,
          idToken: googleAuth.idToken,
        );

        return await FirebaseAuth.instance.signInWithCredential(credential);
      }
    } catch (e) {
      print('Google サインインエラー: $e');
      return null;
    }
  }

  // Facebook サインイン
  static Future<UserCredential?> signInWithFacebook() async {
    try {
      if (kIsWeb) {
        // Web版Facebook認証
        FacebookAuthProvider facebookProvider = FacebookAuthProvider();
        facebookProvider.addScope('email');
        facebookProvider.setCustomParameters({'display': 'popup'});

        return await FirebaseAuth.instance.signInWithPopup(facebookProvider);
      } else {
        // ネイティブ版Facebook認証
        final LoginResult loginResult = await FacebookAuth.instance.login();
        
        if (loginResult.status != LoginStatus.success) return null;

        final OAuthCredential facebookAuthCredential = 
            FacebookAuthProvider.credential(loginResult.accessToken!.token);

        return await FirebaseAuth.instance
            .signInWithCredential(facebookAuthCredential);
      }
    } catch (e) {
      print('Facebook サインインエラー: $e');
      return null;
    }
  }

  // Apple サインイン
  static Future<UserCredential?> signInWithApple() async {
    try {
      final appleProvider = AppleAuthProvider();
      appleProvider.addScope('email');
      appleProvider.addScope('name');

      if (kIsWeb) {
        return await FirebaseAuth.instance.signInWithPopup(appleProvider);
      } else {
        return await FirebaseAuth.instance.signInWithProvider(appleProvider);
      }
    } catch (e) {
      print('Apple サインインエラー: $e');
      return null;
    }
  }

  // 匿名サインイン
  static Future<UserCredential?> signInAnonymously() async {
    try {
      return await FirebaseAuth.instance.signInAnonymously();
    } on FirebaseAuthException catch (e) {
      switch (e.code) {
        case "operation-not-allowed":
          print("匿名認証が有効化されていません");
          break;
        default:
          print("匿名認証エラー: ${e.message}");
      }
      return null;
    }
  }

  // サインアウト
  static Future<void> signOut() async {
    await GoogleSignIn().signOut();
    await FacebookAuth.instance.logOut();
    await FirebaseAuth.instance.signOut();
  }
}

// oauth_buttons.dart - OAuth認証ボタンウィジェット
class OAuthButtons extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // Google サインインボタン
        ElevatedButton.icon(
          onPressed: () async {
            final result = await OAuthService.signInWithGoogle();
            if (result != null) {
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(content: Text('Googleでログインしました')),
              );
            }
          },
          icon: Icon(Icons.login),
          label: Text('Googleでサインイン'),
          style: ElevatedButton.styleFrom(
            backgroundColor: Colors.red,
            foregroundColor: Colors.white,
            minimumSize: Size(double.infinity, 50),
          ),
        ),
        SizedBox(height: 12),
        
        // Facebook サインインボタン
        ElevatedButton.icon(
          onPressed: () async {
            final result = await OAuthService.signInWithFacebook();
            if (result != null) {
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(content: Text('Facebookでログインしました')),
              );
            }
          },
          icon: Icon(Icons.facebook),
          label: Text('Facebookでサインイン'),
          style: ElevatedButton.styleFrom(
            backgroundColor: Colors.blue[800],
            foregroundColor: Colors.white,
            minimumSize: Size(double.infinity, 50),
          ),
        ),
        SizedBox(height: 12),
        
        // Apple サインインボタン(iOS/Web)
        if (Theme.of(context).platform == TargetPlatform.iOS || kIsWeb)
          ElevatedButton.icon(
            onPressed: () async {
              final result = await OAuthService.signInWithApple();
              if (result != null) {
                ScaffoldMessenger.of(context).showSnackBar(
                  SnackBar(content: Text('Appleでログインしました')),
                );
              }
            },
            icon: Icon(Icons.apple),
            label: Text('Appleでサインイン'),
            style: ElevatedButton.styleFrom(
              backgroundColor: Colors.black,
              foregroundColor: Colors.white,
              minimumSize: Size(double.infinity, 50),
            ),
          ),
        
        SizedBox(height: 12),
        
        // 匿名サインインボタン
        TextButton.icon(
          onPressed: () async {
            final result = await OAuthService.signInAnonymously();
            if (result != null) {
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(content: Text('匿名でログインしました')),
              );
            }
          },
          icon: Icon(Icons.person_outline),
          label: Text('匿名でサインイン'),
        ),
      ],
    );
  }
}

ユーザー情報管理とプロファイル更新

// user_profile_page.dart - ユーザープロファイル管理
class UserProfilePage extends StatefulWidget {
  @override
  _UserProfilePageState createState() => _UserProfilePageState();
}

class _UserProfilePageState extends State<UserProfilePage> {
  final _displayNameController = TextEditingController();
  final _currentPasswordController = TextEditingController();
  final _newPasswordController = TextEditingController();
  bool _isLoading = false;

  @override
  void initState() {
    super.initState();
    _loadUserData();
  }

  void _loadUserData() {
    final user = FirebaseAuth.instance.currentUser;
    if (user != null) {
      _displayNameController.text = user.displayName ?? '';
    }
  }

  // プロフィール更新
  Future<void> _updateProfile() async {
    final user = FirebaseAuth.instance.currentUser;
    if (user == null) return;

    setState(() => _isLoading = true);

    try {
      await user.updateDisplayName(_displayNameController.text);
      await user.reload();
      
      _showMessage('プロフィールを更新しました');
    } catch (e) {
      _showMessage('更新に失敗しました: $e');
    } finally {
      setState(() => _isLoading = false);
    }
  }

  // パスワード変更
  Future<void> _changePassword() async {
    final user = FirebaseAuth.instance.currentUser;
    if (user == null) return;

    if (_currentPasswordController.text.isEmpty || 
        _newPasswordController.text.isEmpty) {
      _showMessage('現在のパスワードと新しいパスワードを入力してください');
      return;
    }

    setState(() => _isLoading = true);

    try {
      // 再認証
      final credential = EmailAuthProvider.credential(
        email: user.email!,
        password: _currentPasswordController.text,
      );
      await user.reauthenticateWithCredential(credential);

      // パスワード更新
      await user.updatePassword(_newPasswordController.text);
      
      _currentPasswordController.clear();
      _newPasswordController.clear();
      _showMessage('パスワードを変更しました');
    } on FirebaseAuthException catch (e) {
      String message;
      switch (e.code) {
        case 'wrong-password':
          message = '現在のパスワードが間違っています';
          break;
        case 'weak-password':
          message = '新しいパスワードが弱すぎます';
          break;
        default:
          message = 'パスワード変更に失敗しました: ${e.message}';
      }
      _showMessage(message);
    } finally {
      setState(() => _isLoading = false);
    }
  }

  // メール認証送信
  Future<void> _sendEmailVerification() async {
    final user = FirebaseAuth.instance.currentUser;
    if (user == null) return;

    try {
      await user.sendEmailVerification();
      _showMessage('認証メールを送信しました');
    } catch (e) {
      _showMessage('送信に失敗しました: $e');
    }
  }

  // アカウント削除
  Future<void> _deleteAccount() async {
    final confirmed = await _showConfirmDialog(
      'アカウント削除',
      'アカウントを削除すると、すべてのデータが失われます。\n本当に削除しますか?',
    );
    
    if (!confirmed) return;

    final user = FirebaseAuth.instance.currentUser;
    if (user == null) return;

    try {
      await user.delete();
      _showMessage('アカウントを削除しました');
    } on FirebaseAuthException catch (e) {
      if (e.code == 'requires-recent-login') {
        _showMessage('セキュリティのため、再ログインしてからもう一度お試しください');
      } else {
        _showMessage('削除に失敗しました: ${e.message}');
      }
    }
  }

  Future<bool> _showConfirmDialog(String title, String message) async {
    return await showDialog<bool>(
      context: context,
      builder: (context) => AlertDialog(
        title: Text(title),
        content: Text(message),
        actions: [
          TextButton(
            onPressed: () => Navigator.of(context).pop(false),
            child: Text('キャンセル'),
          ),
          TextButton(
            onPressed: () => Navigator.of(context).pop(true),
            child: Text('削除'),
            style: TextButton.styleFrom(foregroundColor: Colors.red),
          ),
        ],
      ),
    ) ?? false;
  }

  void _showMessage(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(message)),
    );
  }

  @override
  Widget build(BuildContext context) {
    final user = FirebaseAuth.instance.currentUser;
    
    return Scaffold(
      appBar: AppBar(
        title: Text('プロフィール'),
        actions: [
          IconButton(
            onPressed: () async {
              await OAuthService.signOut();
            },
            icon: Icon(Icons.logout),
          ),
        ],
      ),
      body: user == null
          ? Center(child: Text('ユーザーが見つかりません'))
          : SingleChildScrollView(
              padding: EdgeInsets.all(16.0),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  // ユーザー情報表示
                  Card(
                    child: Padding(
                      padding: EdgeInsets.all(16.0),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Text('基本情報', style: Theme.of(context).textTheme.headlineSmall),
                          SizedBox(height: 16),
                          
                          if (user.photoURL != null)
                            CircleAvatar(
                              radius: 40,
                              backgroundImage: NetworkImage(user.photoURL!),
                            ),
                          
                          SizedBox(height: 16),
                          Text('メールアドレス: ${user.email ?? "未設定"}'),
                          SizedBox(height: 8),
                          Text('UID: ${user.uid}'),
                          SizedBox(height: 8),
                          Row(
                            children: [
                              Text('メール認証: '),
                              Icon(
                                user.emailVerified ? Icons.check_circle : Icons.cancel,
                                color: user.emailVerified ? Colors.green : Colors.red,
                              ),
                              if (!user.emailVerified) ...[
                                SizedBox(width: 8),
                                TextButton(
                                  onPressed: _sendEmailVerification,
                                  child: Text('認証メール送信'),
                                ),
                              ],
                            ],
                          ),
                          
                          SizedBox(height: 16),
                          TextField(
                            controller: _displayNameController,
                            decoration: InputDecoration(
                              labelText: '表示名',
                              border: OutlineInputBorder(),
                            ),
                          ),
                          
                          SizedBox(height: 16),
                          ElevatedButton(
                            onPressed: _isLoading ? null : _updateProfile,
                            child: _isLoading
                                ? CircularProgressIndicator()
                                : Text('プロフィール更新'),
                          ),
                        ],
                      ),
                    ),
                  ),
                  
                  SizedBox(height: 16),
                  
                  // パスワード変更セクション
                  if (user.providerData.any((info) => info.providerId == 'password'))
                    Card(
                      child: Padding(
                        padding: EdgeInsets.all(16.0),
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Text('パスワード変更', 
                                style: Theme.of(context).textTheme.headlineSmall),
                            SizedBox(height: 16),
                            
                            TextField(
                              controller: _currentPasswordController,
                              decoration: InputDecoration(
                                labelText: '現在のパスワード',
                                border: OutlineInputBorder(),
                              ),
                              obscureText: true,
                            ),
                            
                            SizedBox(height: 16),
                            
                            TextField(
                              controller: _newPasswordController,
                              decoration: InputDecoration(
                                labelText: '新しいパスワード',
                                border: OutlineInputBorder(),
                              ),
                              obscureText: true,
                            ),
                            
                            SizedBox(height: 16),
                            
                            ElevatedButton(
                              onPressed: _isLoading ? null : _changePassword,
                              child: Text('パスワード変更'),
                            ),
                          ],
                        ),
                      ),
                    ),
                  
                  SizedBox(height: 16),
                  
                  // アカウント削除セクション
                  Card(
                    child: Padding(
                      padding: EdgeInsets.all(16.0),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Text('危険な操作', 
                              style: Theme.of(context).textTheme.headlineSmall),
                          SizedBox(height: 16),
                          
                          ElevatedButton(
                            onPressed: _deleteAccount,
                            style: ElevatedButton.styleFrom(
                              backgroundColor: Colors.red,
                              foregroundColor: Colors.white,
                            ),
                            child: Text('アカウント削除'),
                          ),
                        ],
                      ),
                    ),
                  ),
                ],
              ),
            ),
    );
  }

  @override
  void dispose() {
    _displayNameController.dispose();
    _currentPasswordController.dispose();
    _newPasswordController.dispose();
    super.dispose();
  }
}

認証状態管理と高度な機能

// auth_state_service.dart - 認証状態管理サービス
class AuthStateService {
  static final FirebaseAuth _auth = FirebaseAuth.instance;

  // 現在のユーザーを取得
  static User? get currentUser => _auth.currentUser;

  // 認証状態変更ストリーム
  static Stream<User?> get authStateChanges => _auth.authStateChanges();

  // ユーザー変更ストリーム(プロフィール変更も含む)
  static Stream<User?> get userChanges => _auth.userChanges();

  // IDトークン変更ストリーム
  static Stream<User?> get idTokenChanges => _auth.idTokenChanges();

  // 認証状態の永続化設定(Web専用)
  static Future<void> setPersistence(Persistence persistence) async {
    if (kIsWeb) {
      await _auth.setPersistence(persistence);
    }
  }

  // 現在のユーザーの詳細情報を取得
  static Future<User?> reloadCurrentUser() async {
    final user = _auth.currentUser;
    if (user != null) {
      await user.reload();
      return _auth.currentUser;
    }
    return null;
  }

  // IDトークンを取得
  static Future<String?> getIdToken({bool forceRefresh = false}) async {
    final user = _auth.currentUser;
    if (user != null) {
      return await user.getIdToken(forceRefresh);
    }
    return null;
  }

  // カスタムクレームを取得
  static Future<Map<String, dynamic>?> getCustomClaims() async {
    final user = _auth.currentUser;
    if (user != null) {
      final idTokenResult = await user.getIdTokenResult();
      return idTokenResult.claims;
    }
    return null;
  }
}

// auth_guard.dart - 認証ガードウィジェット
class AuthGuard extends StatelessWidget {
  final Widget child;
  final Widget? loginPage;
  final bool requireEmailVerification;

  const AuthGuard({
    Key? key,
    required this.child,
    this.loginPage,
    this.requireEmailVerification = false,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<User?>(
      stream: AuthStateService.authStateChanges,
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return Scaffold(
            body: Center(child: CircularProgressIndicator()),
          );
        }

        final user = snapshot.data;
        
        // ユーザーが存在しない場合
        if (user == null) {
          return loginPage ?? LoginPage();
        }

        // メール認証が必要な場合
        if (requireEmailVerification && !user.emailVerified) {
          return EmailVerificationPage();
        }

        // 認証済みユーザー
        return child;
      },
    );
  }
}

// email_verification_page.dart - メール認証確認ページ
class EmailVerificationPage extends StatefulWidget {
  @override
  _EmailVerificationPageState createState() => _EmailVerificationPageState();
}

class _EmailVerificationPageState extends State<EmailVerificationPage> {
  bool _isLoading = false;
  Timer? _timer;

  @override
  void initState() {
    super.initState();
    _checkEmailVerification();
  }

  void _checkEmailVerification() {
    _timer = Timer.periodic(Duration(seconds: 3), (timer) async {
      await FirebaseAuth.instance.currentUser?.reload();
      final user = FirebaseAuth.instance.currentUser;
      
      if (user?.emailVerified == true) {
        timer.cancel();
        // 認証確認後にページを更新するため、setState を呼び出す
        if (mounted) {
          setState(() {});
        }
      }
    });
  }

  Future<void> _resendVerificationEmail() async {
    setState(() => _isLoading = true);

    try {
      await FirebaseAuth.instance.currentUser?.sendEmailVerification();
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('認証メールを再送信しました')),
      );
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('送信に失敗しました: $e')),
      );
    } finally {
      setState(() => _isLoading = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    final user = FirebaseAuth.instance.currentUser;

    return Scaffold(
      appBar: AppBar(
        title: Text('メール認証'),
        actions: [
          IconButton(
            onPressed: () => FirebaseAuth.instance.signOut(),
            icon: Icon(Icons.logout),
          ),
        ],
      ),
      body: Padding(
        padding: EdgeInsets.all(16.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Icon(
              Icons.mark_email_unread,
              size: 100,
              color: Colors.orange,
            ),
            SizedBox(height: 24),
            
            Text(
              'メール認証が必要です',
              style: Theme.of(context).textTheme.headlineSmall,
              textAlign: TextAlign.center,
            ),
            
            SizedBox(height: 16),
            
            Text(
              '${user?.email} に送信された認証メールのリンクをクリックしてください。',
              style: Theme.of(context).textTheme.bodyLarge,
              textAlign: TextAlign.center,
            ),
            
            SizedBox(height: 32),
            
            ElevatedButton(
              onPressed: _isLoading ? null : _resendVerificationEmail,
              child: _isLoading
                  ? CircularProgressIndicator()
                  : Text('認証メールを再送信'),
            ),
            
            SizedBox(height: 16),
            
            TextButton(
              onPressed: () async {
                await FirebaseAuth.instance.currentUser?.reload();
                setState(() {});
              },
              child: Text('認証状態を確認'),
            ),
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    _timer?.cancel();
    super.dispose();
  }
}