Как создать безопасный процесс аутентификации пользователя в Flutter с использованием Firebase и управления состоянием Bloc

Аутентификация пользователя является важным аспектом разработки мобильных приложений. Она помогает убедиться, что только авторизованные пользователи имеют доступ к конфиденциальной информации и могут выполнять действия в приложении. В этом руководстве мы рассмотрим, как построить безопасную аутентификацию пользователей в Flutter с использованием Firebase для аутентификации и паттерна управления состоянием Bloc.

Проверка подлинности пользователя критична для разработки мобильных приложений. Она помогает убедиться, что только авторизованные пользователи могут получать доступ к конфиденциальной информации и выполнять действия внутри приложения.

В этом руководстве мы рассмотрим, как создать безопасную проверку подлинности пользователя в Flutter с использованием Firebase для аутентификации и шаблона управления состоянием Bloc для управления состоянием приложения. В конце у вас будет прочное понимание того, как интегрировать аутентификацию Firebase и реализовать безопасный процесс входа и регистрации с использованием Bloc.

Необходимые предварительные условия:

Для достижения максимальной эффективности в этом руководстве у вас должно быть следующее:

  • Хорошее понимание Flutter и Dart
  • Учетная запись Firebase: создайте учетную запись Firebase, если у вас еще нет такой. Вы можете настроить проект Firebase через Firebase Console.

Как работает аутентификация Firebase

Аутентификация Firebase – это мощный сервис, упрощающий процесс аутентификации пользователей в вашем приложении. Он поддерживает различные методы аутентификации, включая электронную почту/пароль, социальные сети и другие.

Одним из ключевых преимуществ аутентификации Firebase являются встроенные функции безопасности, такие как безопасное хранение учетных данных пользователей и шифрование конфиденциальных данных.

Описание графика потоков

Давайте визуализируем ход действий с помощью графика потоков, чтобы понять концепцию, которую вы собираетесь изучить. Взгляните на диаграмму ниже, чтобы лучше понять:

Flowcharts
Изображение 1: График потоков приложения

Изображение выше представляет собой график потоков, чтобы визуализировать поток приложения. Давайте обсудим, что представляет каждая часть. Округлые прямоугольники представляют начальные и конечные точки потока; фиолетовые прямоугольники представляют экраны; голубые прямоугольники представляют процессы, которые происходят; и, наконец, ромбы представляют принятие решения.

  • Приложение начинается с AuthenticationFlowScreen.
  • StreamBuilder следит за изменениями состояния аутентификации.
  • Если пользователь аутентифицирован, направляется на HomeScreen; в противном случае, переходит на SignupScreen.
  • AuthenticationBloc управляет событиями и состояниями аутентификации пользователя.
  • Когда пользователь регистрируется (SignUpUser событие срабатывает):
  • Он инициирует состояние загрузки аутентификации (AuthenticationLoadingState).
  • Вызывается signUpUser из AuthService для регистрации пользователя.
  • Если успешно, он выдает AuthenticationSuccessState с данными пользователя; в противном случае, выдает AuthenticationFailureState.
  • Когда пользователь инициирует процесс выхода из системы (SignOut событие срабатывает):
  • Он запускает состояние загрузки аутентификации (AuthenticationLoadingState).
  • Вызывается signOutUser из AuthService для выхода пользователя из системы.
  • Если произошла ошибка при выходе из системы, он записывает сообщение об ошибке.

Настройка проекта

Чтобы начать работу с аутентификацией Firebase, вам необходимо настроить Firebase в вашем проекте Flutter.

Следуйте этим шагам, чтобы добавить Firebase и bloc в ваш проект:

Добавьте зависимости в ваш проект

Откройте ваш проект в вашем предпочитаемом редакторе кода.

Добавьте следующие зависимости в файл pubspec.yaml вашего проекта:

dependencies:firebase_core: ^2.20.0firebase_auth: ^4.12.0flutter_bloc: ^8.1.3

Затем сохраните файл pubspec.yaml, чтобы получить зависимости.

Настройка Firebase

Создайте новый проект Firebase через консоль Firebase Console. Щелкните по разделу “аутентификация” в проекте и следуйте предоставленным инструкциям.

Для получения дополнительной информации вы можете ознакомиться с веб-сайтом Firebase website.

Инициализация Firebase

Сначала откройте файл main.dart в папке lib.

Добавьте следующий код в файл для инициализации Firebase:

void main() async {WidgetsFlutterBinding.ensureInitialized();await Firebase.initializeApp(  options: DefaultFirebaseOptions.currentPlatform);

Код выше показывает код для запуска приложения. В этом коде нет ничего необычного, кроме того, что мы добавили некоторый код в void main для инициализации Firebase.

Модель пользователя

Перед созданием класса Firebase для взаимодействия с сервисом Firebase давайте определим модель UserModel для представления данных пользователя.

Начните с создания файла user.dart в папке lib вашего проекта.

Затем добавьте следующий код в файл:

class UserModel {final String? id;final String? email;final String? displayName;UserModel({ this.id, this.email, this.displayName, });}

Теперь, когда вы настроили Firebase и создали модель пользователя, вам нужно создать класс сервиса, который будет взаимодействовать с Firebase напрямую.

Сервис аутентификации

Создайте папку с названием services, создайте в этой папке файл с названием authentication.dart. Теперь вы можете добавить в этот файл следующий код.

import 'package:firebase_auth/firebase_auth.dart';import '../models/user.dart';class AuthService {  final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;  /// создание пользователя  Future<UserModel?> signUpUser(    String email,    String password,  ) async {    try {      final UserCredential userCredential =          await _firebaseAuth.createUserWithEmailAndPassword(        email: email.trim(),        password: password.trim(),      );      final User? firebaseUser = userCredential.user;      if (firebaseUser != null) {        return UserModel(          id: firebaseUser.uid,          email: firebaseUser.email ?? '',          displayName: firebaseUser.displayName ?? '',        );      }    } on FirebaseAuthException catch (e) {      print(e.toString());    }    return null;  }    ///signOutUser    Future<void> signOutUser() async {      final User? firebaseUser = FirebaseAuth.instance.currentUser;    if (firebaseUser != null) {      await FirebaseAuth.instance.signOut();    }  }  // ... (другие методы)}}

Вышеуказанный сниппет кода является методом создания пользователя в приложении с использованием Firebase. С помощью этого метода метод signUpUser принимает два строки параметров: email и password соответственно. Затем вы вызываете метод Firebase для создания пользователя, используя добавленные нами параметры.

Теперь, когда вы знаете, как создавать метод регистрации, вы также можете создать метод входа в систему. Класс в конечном итоге описывает взаимодействие между Firebase и приложением.

Следующий этап – связать сервис с управлением состоянием, о чем мы сейчас узнаем.

Как работает управление состоянием Bloc

Bloc – это популярный шаблон управления состоянием для Flutter, который помогает предсказуемо и тестируемо управлять сложными состояниями приложений. Bloc означает “Business Logic Component” и разделяет бизнес-логику и пользовательский интерфейс. Bloc будет мостом между вашим приложением и Firebase.

Существует расширение для VScode, которое создает код заготовки для Bloc. Вы можете использовать расширение, чтобы ускорить процесс разработки.

Настройка блока аутентификации Firebase

Bloc состоит из событий и состояний. Давайте сначала создадим состояния и события для Bloc. Затем мы создадим AuthenticationBloc, который будет обрабатывать логику, используя созданные события, состояния и сервис.

Класс AuthenticationState

Класс AuthenticationState отвечает за различные состояния процесса аутентификации. Как мы увидим в коде, существуют состояния начала, загрузки, успеха и ошибки, чтобы мы знали, что происходит в процессе аутентификации.

Сначала создайте файл authentication_state.dart в папке bloc вашего проекта.

часть 'authentication_bloc.dart';
абстрактный класс AuthenticationState {
  const AuthenticationState();
  Список<Object> получитьПропс => [];
}

класс AuthenticationInitialState extends AuthenticationState {}

класс AuthenticationLoadingState extends AuthenticationState {
  final bool загрузка;
  
  AuthenticationLoadingState({required this.загрузка});
}

класс AuthenticationSuccessState extends AuthenticationState {
  final UserModel пользователь;
  
  const AuthenticationSuccessState(this.пользователь);
  
  @override
  Список<Object> получитьПропс => [пользователь];
}

класс AuthenticationFailureState extends AuthenticationState {
  final String сообщениеОбОшибке;
  
  const AuthenticationFailureState(this.сообщениеОбОшибке);
  
  @override
  Список<Object> получитьПропс => [сообщениеОбОшибке];
}

Давайте разберем этот код:

Абстрактный класс AuthenticationState:

  • AuthenticationState – это базовый класс для различных состояний, в которых может находиться процесс аутентификации.
  • Он содержит метод получитьПропс, который возвращает список объектов. Этот метод используется для проверки равенства при сравнении экземпляров этого класса.

Класс AuthenticationInitialState:

  • AuthenticationInitialState представляет начальное состояние процесса аутентификации.

Класс AuthenticationLoadingState:

  • AuthenticationLoadingState представляет состояние, когда процесс аутентификации находится в процессе, и на пользовательском интерфейсе может отображаться индикатор загрузки.
  • Он принимает булевый параметр загрузка, чтобы указать, загружается ли в данный момент процесс аутентификации или нет.

Класс AuthenticationSuccessState:

  • AuthenticationSuccessState представляет состояние, когда процесс аутентификации завершен.
  • Он включает свойство пользователя типа UserModel, представляющее авторизованного пользователя.

Класс AuthenticationFailureState:

  • AuthenticationFailureState представляет состояние, когда процесс аутентификации завершился неудачно.
  • Он содержит свойство сообщениеОбОшибке, содержащее информацию об ошибке.

Класс AuthenticationEvent

Класс AuthenticationEvent отвечает за события, которые будут выполняться AuthenticationBloc. В данном случае это событие входа в систему. Вы можете написать здесь и другие события, например, регистрацию и выход из системы.

Создайте файл authentication_Event.dart в каталоге вашего проекта bloc.

часть 'authentication_bloc.dart';

абстрактный класс AuthenticationEvent {
  const AuthenticationEvent();
  Список<Object> получитьПропс => [];
}

класс SignUpUser extends AuthenticationEvent {
  final String электроннаяПочта;
  final String пароль;

  const SignUpUser(this.электроннаяПочта, this.пароль);

  @override
  Список<Object> получитьПропс => [электроннаяПочта, пароль];
}

класс SignOut extends AuthenticationEvent {}

Класс AuthenticationEvent похож на AuthenticationState. Давайте рассмотрим код, чтобы понять, что он делает:

Абстрактный класс AuthenticationEvent:

  • Это базовый класс для различных событий, вызывающих изменение состояния аутентификации.

Класс SignUpUser:

  • Этот класс представляет событие, когда пользователь пытается зарегистрироваться.
  • Он принимает два параметра: электроннаяПочта и пароль, представляющие учетные данные, которые пользователь использует для регистрации.
  • Экземпляры этого класса будут сигнализировать о том, что пользователь пытается зарегистрироваться, и Bloc может ответить, инициировав процесс регистрации и соответствующим образом перейдя к состоянию аутентификации.

Класс SignOut:

  • Экземпляры этого класса будут сигнализировать Bloc, что пользователь пытается выйти из системы. Bloc может ответить, инициировав процесс выхода из системы и обновив состояние аутентификации соответствующим образом.

Класс AuthenticationBloc

AuthenticationBloc будет обрабатывать общее состояние аутентификации, от того, что происходит, когда пользователь нажимает кнопку, до того, что отображается на экране. Он также взаимодействует непосредственно с созданным нами сервисом Firebase.

Сначала создайте файл с именем authentication_bloc.dart в каталоге вашего проекта bloc.

Добавьте следующий код, чтобы определить класс AuthenticationBloc:

import 'package:bloc/bloc.dart';import 'package:meta/meta.dart';import '../models/user.dart';import '../services/authentication.dart';part 'authentication_event.dart';part 'authentication_state.dart';class AuthenticationBloc extends Bloc<AuthenticationEvent, AuthenticationState> {  final AuthService authService = AuthService();    AuthenticationBloc() : super(AuthenticationInitialState()) {    on<AuthenticationEvent>((event, emit) {});    on<SignUpUser>((event, emit) async {      emit(AuthenticationLoadingState(isLoading: true));      try {          final UserModel? user =          await authService.signUpUser(event.email, event.password);      if (user != null) {        emit(AuthenticationSuccessState(user));              } else {        emit(const AuthenticationFailureState('create user failed'));      }      } catch (e) {        print(e.toString());      }     emit(AuthenticationLoadingState(isLoading: false));    });     on<SignOut>((event, emit) async {      emit(AuthenticationLoadingState(isLoading: true));      try {        authService.signOutUser();      } catch (e) {        print('error');        print(e.toString());      }        emit(AuthenticationLoadingState(isLoading: false));     });}}

В этом фрагменте кода мы создали экземпляр класса AuthService, который обрабатывает операции аутентификации пользователя, такие как регистрация и выход.

on<SignUpUser>((event, emit) async { ... } определяет обработчик для события SignUpUser. Когда это событие срабатывает, bloc выполняет следующие шаги:

  • Он отправляет  AuthenticationLoadingState для обозначения того, что процесс аутентификации находится в процессе.
  • Он вызывает метод signUpUser  authService, чтобы попытаться создать учетную запись пользователя с указанным электронной почтой и паролем.
  • Если создание учетной записи пользователя успешно (то есть пользователь не равен null), он отправляет  AuthenticationSuccessState с данными пользователя.
  • Если создание учетной записи пользователя не удалось, он отправляет  AuthenticationFailureState с сообщением об ошибке и регистрирует ошибку.
  • Безусловно, отправляет еще один  AuthenticationLoadingState для сигнализации окончания процесса аутентификации.

on<SignOut>((event, emit) async { ... } определяет обработчик для события SignOut. Когда это событие срабатывает, bloc выполняет следующие шаги:

  • Он отправляет  AuthenticationLoadingState для обозначения того, что процесс выхода из системы находится в процессе.
  • Он вызывает метод signOutUser  authService для выхода пользователя из системы.
  • Если в процессе выхода из системы происходят какие-либо ошибки, он регистрирует ошибку.
  • Он отправляет еще один  AuthenticationLoadingState для сигнализации окончания процесса выхода из системы.

AuthenticationBloc управляет состоянием процесса аутентификации, включая состояния загрузки, успеха и ошибки, на основе событий, вызванных действиями пользователя. authService отвечает за осуществление фактических операций аутентификации. С настройкой блока мы можем реализовать процесс аутентификации с использованием Bloc.

Как реализовать процесс аутентификации с использованием Bloc

Для реализации процесса аутентификации вы создадите отдельный Stateiless виджет для проверки, вошел ли пользователь в систему, чтобы мы знали, какой экран показать пользователю. Страница будет отображать разные экраны в зависимости от состояния аутентификации пользователя.

AuthenticationFlowScreen:

Создайте новый файл с названием authentication_page.dart в директории screens вашего проекта.

import 'package:bloc_authentication_flow/screens/home.dart';import 'package:bloc_authentication_flow/screens/sign_up.dart';import 'package:firebase_auth/firebase_auth.dart';import 'package:flutter/material.dart';class AuthenticationFlowScreen extends StatelessWidget {  const AuthenticationFlowScreen({super.key});  static String id = 'main screen';  @override  Widget build(BuildContext context) {    return Scaffold(      body: StreamBuilder<User?>(        stream: FirebaseAuth.instance.authStateChanges(),        builder: (context, snapshot) {          if (snapshot.hasData) {            return const HomeScreen();          } else {            return const SignupScreen();          }        },      ),    );  }}

В приведенном выше коде у вас есть StatelessWidget с StreamBuilder в качестве дочернего элемента. StreamBuilder выступает в роли судьи, используя Firebase для проверки изменений состояния и того, вошел ли пользователь в систему или нет. Если пользователь вошел в систему, он перенаправляет их на домашний экран, иначе он переходит на экран регистрации.

Измените маршрут домашней страницы на AuthenticationFlowScreen, чтобы приложение проверяло перед переходом на любую страницу.

   home: const AuthenticationFlowScreen() 

Экран регистрации

Сначала создайте новый файл с именем sign_up.dart в каталоге screens.

import 'package:bloc_authentication_flow/screens/home.dart';import 'package:flutter/material.dart';import 'package:flutter_bloc/flutter_bloc.dart';import '../bloc/authentication_bloc.dart';class SignupScreen extends StatefulWidget {  static String id = 'login_screen';  const SignupScreen({    Key? key,  }) : super(key: key);  @override  State<SignupScreen> createState() => _SignupScreenState();}class _SignupScreenState extends State<SignupScreen> {  // Text Controllers  final emailController = TextEditingController();  final passwordController = TextEditingController();  @override  void dispose() {    emailController.dispose();    passwordController.dispose();    super.dispose();  }  @override  Widget build(BuildContext context) {    return Scaffold(      appBar: AppBar(        title: const Text(          'Войти в свою учетную запись',          style: TextStyle(            color: Colors.deepPurple,          ),        ),        centerTitle: true,      ),      body: Padding(        padding: const EdgeInsets.all(16.0),        child: Column(          crossAxisAlignment: CrossAxisAlignment.start,          children: [            const SizedBox(height: 20),            const Text('Адрес электронной почты'),            const SizedBox(height: 10),            TextFormField(              controller: emailController,              decoration: const InputDecoration(                border: OutlineInputBorder(),                hintText: 'Введите адрес электронной почты',              ),            ),            const SizedBox(height: 10),            const Text('Пароль'),            TextFormField(              controller: passwordController,              decoration: const InputDecoration(                border: OutlineInputBorder(),                hintText: 'Введите свой пароль',              ),              obscureText: false,            ),            const SizedBox(height: 10),            GestureDetector(              onTap: () {},              child: const Text(                'Забыли пароль?',                style: TextStyle(                  color: Colors.deepPurple,                ),              ),            ),            const SizedBox(height: 20),            BlocConsumer<AuthenticationBloc, AuthenticationState>(              listener: (context, state) {                if (state is AuthenticationSuccessState) {                  Navigator.pushNamedAndRemoveUntil(                    context,                    HomeScreen.id,                    (route) => false,                  );                } else if (state is AuthenticationFailureState) {                  showDialog(                      context: context,                      builder: (context) {                        return const AlertDialog(                          content: Text('Ошибка'),                        );                      });                }              },              builder: (context, state) {                return SizedBox(                  height: 50,                  width: double.infinity,                  child: ElevatedButton(                    onPressed: () {                      BlocProvider.of<AuthenticationBloc>(context).add(                        SignUpUser(                          emailController.text.trim(),                          passwordController.text.trim(),                        ),                      );                    },                    child:  Text(                      state is AuthenticationLoadingState                            ? '.......',                            : 'Зарегистрироваться',                      style: TextStyle(                        fontSize: 20,                      ),                    ),                  ),                );              },            ),            const SizedBox(height: 20),            Row(              mainAxisAlignment: MainAxisAlignment.center,              children: [                const Text("У вас уже есть учетная запись? "),                GestureDetector(                  onTap: () {},                  child: const Text(                    'Войти',                    style: TextStyle(                      color: Colors.deepPurple,                    ),                  ),                )              ],            ),          ],        ),      ),    );  }}

Этот код – это простой интерфейс входа в систему с двумя textfields и кнопкой с поднятыми краями. Виджет BlocConsumer оборачивает кнопку Регистрация и прослушивает изменения состояния AuthenticationBloc. Когда пользователь нажимает на кнопку, он отправляет событие в AuthenticationBloc, чтобы начать процесс регистрации пользователя.

В зависимости от состояния аутентификации, эта кнопка может отображать разные отзывы или переходить на другой экран. Она проверяет состояния AuthenticationSuccessState, AuthenticationLoadingState и AuthenticationFailureState, чтобы реагировать соответствующим образом.

ezgif.com-video-to-gif--1-
Изображение 2: Экран входа, показывающий процесс входа с 2 из 3 состояний.

Экран домашней страницы

Создайте еще один файл с названием home_screen.dart в директории screens и добавьте следующий код в файл.

import 'package:flutter/material.dart';import 'package:flutter_bloc/flutter_bloc.dart';import '../bloc/authentication_bloc.dart';class HomeScreen extends StatelessWidget {  static String id = 'home_screen';  const HomeScreen({super.key});  @override  Widget build(BuildContext context) {    return Scaffold(      body: Center(        child: Column(          mainAxisAlignment: MainAxisAlignment.center,          children: [            const Text(              'Привет, Пользователь',              style: TextStyle(                fontSize: 20,              ),            ),            const SizedBox(              height: 20,            ),            BlocConsumer<AuthenticationBloc, AuthenticationState>(              listener: (context, state) {                if (state is AuthenticationLoadingState) {                   const CircularProgressIndicator();                } else if (state is AuthenticationFailureState){                    showDialog(context: context, builder: (context){                          return const AlertDialog(                            content: Text('Ошибка'),                          );                        });                }              },              builder: (context, state) {                return ElevatedButton(                    onPressed: () {                      BlocProvider.of<AuthenticationBloc>(context)                      .add(SignOut());                    }, child: const Text(                      'Выйти'                      ));              },            ),          ],        ),      ),    );  }}

Приведенный выше код представляет HomeScreen и это также простая страница, состоящая из конструкции “Scaffold”, столбца и виджета с текстом, но интересную часть составляет BlocConsumer, который находится на кнопке с надписью “Выйти”.
Давайте рассмотрим это поближе.

Помние, что BlocConsumer отслеживает изменения состояний из AuthenticationBloc. Он имеет два параметра – listener и builder.

  • listener: Отслеживает изменения состояния и реагирует на него в зависимости от текущего состояния, полученного из AuthenticationBloc.
  • Если состояние – это AuthenticationLoadingState, то выводится компонент CircularProgressIndicator.
  • Если состояние – это AuthenticationFailureState, то выводится диалоговое окно AlertDialog с сообщением “Ошибка”.
  • builder: Строит интерфейс на основе текущего состояния, полученного из AuthenticationBloc.
  • Отображается компонент ElevatedButton с надписью “Выйти”.
  • При нажатии кнопки, событие SignOut вызывается в AuthenticationBloc через BlocProvider.

С реализацией процесса аутентификации с использованием Bloc вы можете запустить свое приложение Flutter и протестировать функциональность регистрации. Убедитесь также в обработке других связанных с аутентификацией сценариев, таких как вход пользователя и восстановление пароля, в соответствии с требованиями вашего приложения. Также важно грамотно обрабатывать ошибки, чтобы пользователь получил наилучший опыт использования приложения.

Если вы хотите склонировать репозиторий, вы можете посмотреть его на GitHub и оставить отметку “Мне нравится”.

Заключение

В этой статье мы рассмотрели построение процесса аутентификации пользователя в Flutter с использованием Firebase для аутентификации и паттерна управления состоянием Bloc для управления состоянием приложения.

Мы узнали, как настроить Firebase в проекте Flutter, создать Blocs для аутентификации и реализовать процесс аутентификации с использованием Bloc.

Используя мощь Firebase и предсказуемость Bloc, вы можете обеспечить безопасный и плавный процесс аутентификации пользователей в ваших приложениях Flutter.</p


Leave a Reply

Your email address will not be published. Required fields are marked *