본문 바로가기
Study/Flutter

[Flutter] FireAuth 로그인과 인증

Firebase에 Authentication을 이용해서 앱 안에서의 로그인과 사용자 인증을 다뤄보자.

두 가지 로그인을 구현해 볼까한다.

 

1. firebase_auth를 이용한 이메일 로그인

2. firebase 소셜 로그인에 대해서 살펴보고 구현해보자.

 

1. email 로그인

콘솔 사이트에서 auth페이지에 이메일 로그인 사용을 설정해준다.

 

1) Firebase Auth 에 이메일 로그인 사용 설정

https://console.firebase.google.com/

 

 

2) firebase_auth 사용

패키지를 프로젝트에 추가하여 코드를 작성하면 바로 사용이 가능하다.

https://pub.dev/packages/firebase_auth

 

firebase_auth | Flutter Package

Flutter plugin for Firebase Auth, enabling Android and iOS authentication using passwords, phone numbers and identity providers like Google, Facebook and Twitter.

pub.dev

 

3) 코드 구현

https://firebase.flutter.dev/docs/auth/email-link-auth

구현은 위  docs링크를 참조해서 다양한 사용법을 읽어보시기 바랍니다.

 

.저는 아래와 같이 따로 클래스를 만들어서

사용자 인증관련된 메소드를 한곳에 정의해두고 사용하였습니다. 

import 'package:firebase_auth/firebase_auth.dart';
import 'package:lottery_bang/main.dart';

class AuthManage{
  /// 회원가입
  Future<bool> createUser(String email, String pw) async{
    try {
      final credential = await FirebaseAuth.instance.createUserWithEmailAndPassword(
        email: email,
        password: pw,
      );
    } on FirebaseAuthException catch (e) {
      if (e.code == 'weak-password') {
        logger.w('The password provided is too weak.');
      } else if (e.code == 'email-already-in-use') {
        logger.w('The account already exists for that email.');
      }
    } catch (e) {
      logger.e(e);
      return false;
    }
    // authPersistence(); // 인증 영속
    return true;
  }

  /// 로그인
  Future<bool> signIn(String email, String pw) async{
    try {
      final credential = await FirebaseAuth.instance.signInWithEmailAndPassword(
          email: email,
          password: pw
      );
    } on FirebaseAuthException catch (e) {
      if (e.code == 'user-not-found') {
        logger.w('No user found for that email.');
      } else if (e.code == 'wrong-password') {
        logger.w('Wrong password provided for that user.');
      }
    } catch (e) {
      logger.e(e);
      return false;
    }
    // authPersistence(); // 인증 영속
    return true;
  }
  /// 로그아웃
  void signOut() async{
    await FirebaseAuth.instance.signOut();
  }

  /// 회원가입, 로그인시 사용자 영속
  void authPersistence() async{
    await FirebaseAuth.instance.setPersistence(Persistence.NONE);
  }
  /// 유저 삭제
  Future<void> deleteUser(String email) async{
    final user = FirebaseAuth.instance.currentUser;
    await user?.delete();
  }
  
  /// 현재 유저 정보 조회
  User? getUser(){
    final user = FirebaseAuth.instance.currentUser;
    if (user != null) {
      // Name, email address, and profile photo URL
      final name = user.displayName;
      final email = user.email;
      final photoUrl = user.photoURL;

      // Check if user's email is verified
      final emailVerified = user.emailVerified;

      // The user's ID, unique to the Firebase project. Do NOT use this value to
      // authenticate with your backend server, if you have one. Use
      // User.getIdToken() instead.
      final uid = user.uid;
    }
    return user;
  }
  /// 공급자로부터 유저 정보 조회
  User? getUserFromSocial(){
    final user = FirebaseAuth.instance.currentUser;
    if (user != null) {
      for (final providerProfile in user.providerData) {
        // ID of the provider (google.com, apple.cpm, etc.)
        final provider = providerProfile.providerId;

        // UID specific to the provider
        final uid = providerProfile.uid;

        // Name, email address, and profile photo URL
        final name = providerProfile.displayName;
        final emailAddress = providerProfile.email;
        final profilePhoto = providerProfile.photoURL;
      }
    }
    return user;
  }
  /// 유저 이름 업데이트
  Future<void> updateProfileName(String name) async{
    final user = FirebaseAuth.instance.currentUser;
    await user?.updateDisplayName(name);
  }
  /// 유저 url 업데이트
  Future<void> updateProfileUrl(String url) async{
    final user = FirebaseAuth.instance.currentUser;
    await user?.updatePhotoURL(url);
  }
  /// 비밀번호 초기화 메일보내기
  Future<void> sendPasswordResetEmail(String email) async{
    await FirebaseAuth.instance.setLanguageCode("kr");
    await FirebaseAuth.instance.sendPasswordResetEmail(email:email);
  }


}

다양한 메소드 중에서  맨위에 3가지 회원가입, 로그인, 로그아웃을 연결시켜 테스트 해보았다.

해당메서드에 email, pw를 넣어주고, bool값을 반환받도록 설정하여 테스트해보니 정상동작된다.

(UI 코드는 생략)

 

여기에 await중 처리 진행중이라는 사실을 알리기위한 progressBar 

인증 에러 발생 시, 사유를 담아 SnackBar알림이나 AlertDialog 을 추가하면 더 좋을것 같다.

 

 

 

2. 소셜 로그인

파이어베이스에서는 다양한 소셜로그인을 사용할 수 있게 제공한다.

구글 로그인을 하나 붙여보겠다.

 

1) Firebase Auth 에 구글 로그인 사용 설정

https://console.firebase.google.com/

 

 

2) Firebase에 앱 서명을 추가 

소셜로그인은 아래 설명과 같이 반드시 앱을 배포한 후에  sha인증서 지문이 있어야 사용이 가능하다.

https://firebase.google.com/docs/android/setup

SHA인증서 발급은 여기서 진행하면 된다.

발급후 firebase에 등록해주었다면 이제 코드를 작성해보자.

 

 

3) google_sign_in 사용

프로젝트에 google_sign_in 을 install 해주자

 

google_sign_in | Flutter Package

Flutter plugin for Google Sign-In, a secure authentication system for signing in with a Google account on Android and iOS.

pub.dev

 

4) 코드 구현

api 페이지에서 제공해주는 예시코드 본문 그대로이며,

이를 이해하여 쉽게 구현할 수 있다. 

// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// ignore_for_file: public_member_api_docs

import 'dart:async';
import 'dart:convert' show json;

import 'package:flutter/material.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:http/http.dart' as http;

GoogleSignIn _googleSignIn = GoogleSignIn(
  // Optional clientId
  // clientId: '479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u.apps.googleusercontent.com',
  scopes: <String>[
    'email',
    'https://www.googleapis.com/auth/contacts.readonly',
  ],
);

void main() {
  runApp(
    const MaterialApp(
      title: 'Google Sign In',
      home: SignInDemo(),
    ),
  );
}

class SignInDemo extends StatefulWidget {
  const SignInDemo({Key? key}) : super(key: key);

  @override
  State createState() => SignInDemoState();
}

class SignInDemoState extends State<SignInDemo> {
  GoogleSignInAccount? _currentUser;
  String _contactText = '';

  @override
  void initState() {
    super.initState();
    _googleSignIn.onCurrentUserChanged.listen((GoogleSignInAccount? account) {
      setState(() {
        _currentUser = account;
      });
      if (_currentUser != null) {
        _handleGetContact(_currentUser!);
      }
    });
    _googleSignIn.signInSilently();
  }

  Future<void> _handleGetContact(GoogleSignInAccount user) async {
    setState(() {
      _contactText = 'Loading contact info...';
    });
    final http.Response response = await http.get(
      Uri.parse('https://people.googleapis.com/v1/people/me/connections'
          '?requestMask.includeField=person.names'),
      headers: await user.authHeaders,
    );
    if (response.statusCode != 200) {
      setState(() {
        _contactText = 'People API gave a ${response.statusCode} '
            'response. Check logs for details.';
      });
      print('People API ${response.statusCode} response: ${response.body}');
      return;
    }
    final Map<String, dynamic> data =
        json.decode(response.body) as Map<String, dynamic>;
    final String? namedContact = _pickFirstNamedContact(data);
    setState(() {
      if (namedContact != null) {
        _contactText = 'I see you know $namedContact!';
      } else {
        _contactText = 'No contacts to display.';
      }
    });
  }

  String? _pickFirstNamedContact(Map<String, dynamic> data) {
    final List<dynamic>? connections = data['connections'] as List<dynamic>?;
    final Map<String, dynamic>? contact = connections?.firstWhere(
      (dynamic contact) => contact['names'] != null,
      orElse: () => null,
    ) as Map<String, dynamic>?;
    if (contact != null) {
      final Map<String, dynamic>? name = contact['names'].firstWhere(
        (dynamic name) => name['displayName'] != null,
        orElse: () => null,
      ) as Map<String, dynamic>?;
      if (name != null) {
        return name['displayName'] as String?;
      }
    }
    return null;
  }

  Future<void> _handleSignIn() async {
    try {
      await _googleSignIn.signIn();
    } catch (error) {
      print(error);
    }
  }

  Future<void> _handleSignOut() => _googleSignIn.disconnect();

  Widget _buildBody() {
    final GoogleSignInAccount? user = _currentUser;
    if (user != null) {
      return Column(
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        children: <Widget>[
          ListTile(
            leading: GoogleUserCircleAvatar(
              identity: user,
            ),
            title: Text(user.displayName ?? ''),
            subtitle: Text(user.email),
          ),
          const Text('Signed in successfully.'),
          Text(_contactText),
          ElevatedButton(
            onPressed: _handleSignOut,
            child: const Text('SIGN OUT'),
          ),
          ElevatedButton(
            child: const Text('REFRESH'),
            onPressed: () => _handleGetContact(user),
          ),
        ],
      );
    } else {
      return Column(
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        children: <Widget>[
          const Text('You are not currently signed in.'),
          ElevatedButton(
            onPressed: _handleSignIn,
            child: const Text('SIGN IN'),
          ),
        ],
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: const Text('Google Sign In'),
        ),
        body: ConstrainedBox(
          constraints: const BoxConstraints.expand(),
          child: _buildBody(),
        ));
  }
}

이걸 활용해서 아래와 같이

버튼을 추가해 로그인을 연결해주면 정상적으로 로그인됨을 확인할 수 있다.

 

만약 아래와 같은 에러가 발생했다면

I/flutter ( 9648): PlatformException(sign_in_failed, com.google.android.gms.common.api.ApiException: 10: , null, null)
I/ViewRootImpl@f84be9a[MainActivity]( 9648): Relayout returned: old=(0,0,1080,2340) new=(0,0,1080,2340) req=(1080,2340)0 dur=10 res=0x1 s={true -5476376668236069456} ch=false fn=3
I/ViewRootImpl@f84be9a[MainActivity]( 9648): updateBoundsLayer: t = android.view.SurfaceControl$Transaction@f636e41 sc = Surface(name=Bounds for - com.lottery_bang.lottery_bang/com.lottery_bang.lottery_bang.MainActivity@0)/@0x33efe6 frame = 3
I/ViewRootImpl@f84be9a[MainActivity]( 9648): mWNT: t = android.view.SurfaceControl$Transaction@f636e41 fN = 3 android.view.ViewRootImpl.prepareSurfaces:2778 android.view.ViewRootImpl.performTraversals:4024 android.view.ViewRootImpl.doTraversal:2919

서명을 등록하지 않았기 때문이다.

서두에 말한것과 같이 앱을 배포한 후에 해당 인증서 서명을 받아서 firebase에 등록해주자.

 

 

Firebase Auth를 이용하여 

이메일 로그인 및 구글 로그인을 공부하고 작성해보았다.

다른 로그인도 필요에 따라 이와 같은 순서로 쉽게 붙여 사용하고 커스텀할 수 있을것이다.