플러터:인증(Firebase 인증)

학교의 모든 지식. SMwiki
둘러보기로 이동 검색으로 이동

틀:플러터 Dart:개요 플러터에 대한 지식 분류

  1. 플러터:개요
    1. 플러터:VSCode
  2. 플러터:실행
  3. 개념 잡기
    1. 플러터:화면 하나 만들기
    2. 플러터:변하는 화면(StatefulWidget)
    3. 플러터:화면 전환(화면 쌓기, 하단 네비게이션 바)
    4. 플러터:화면 전환(Drawer)
    5. 플러터:키보드 입력
    6. 플러터:슬라이더
    7. 플러터:그래프 그리기(fl chart)
    8. 플러터:데이터 저장(간단한 데이터)
    9. 플러터:인증(Firebase 인증)
    10. 플러터:인증(OAuth2)
  4. 권한 사용
    1. 플러터:마이크 입력
  5. 위젯
    1. 플러터:아이콘
    2. 플러터:레이아웃 계열 위젯
    3. 플러터:네비게이션 계열 위젯
    4. 플러터:버튼
    5. 플러터:상태관리
  6. 플러터:DB연결
    1. 플러터:Firebase
    2. 플러터:MySQL
  7. 디자인
    1. 플러터:테마
    2. 플러터:앱바
    3. 플러터:버튼
  8. 플러터:배포
  9. 플러터:참고자료
  10. 플러터:위젯
    1. 플러터:공간배치용 위젯
  11. 플러터:라이브러리
    1. 플러터:logger

개요[편집 | 원본 편집]

Flutter에서 Firebase Authentication을 이용해 앱에 로그인 기능을 구현하는 방법을 정리한 문서.

특징[편집 | 원본 편집]

  • 쉽다: Firebase는 이메일/비밀번호, 구글, 애플, 전화번호 등 다양한 인증 방식을 제공하며, 별도의 백엔드 없이도 안정적인 인증 시스템을 구축할 수 있다.
  • Firebase가 제공하는 인증 서비스(공식지원)
  • 서버 없이 사용자 계정 관리 가능(백엔드 필요 없음)
  • 소셜 로그인(Google/Apple/GitHub/Microsoft) 지원
  • 인증 상태(로그인 여부) 자동 유지
  • 토큰/세션 관리 자동 처리

한계[편집 | 원본 편집]

  • 기본적으로 제공하는 소셜로그인 외의 OAuth2 등 인증은 따로 구현하는 편이 더 간편하다.

Firebase 인증 개념[편집 | 원본 편집]

인증 흐름[편집 | 원본 편집]

  1. 앱에서 FirebaseAuth API 호출
  2. Firebase가 사용자 인증 처리
  3. 성공 시 User 객체 반환
  4. authStateChanges() 스트림을 통한 로그인 상태 전달

기본 인증 (Email/Password)[편집 | 원본 편집]

이메일과 패스워드만 사용한 인증을 구현한다.(그런데, 사실 소셜로그인을 이용하는 경우가 많이 때문에.. 이 방식은 굳이...?)

사전 준비[편집 | 원본 편집]

단계 설명 비고
Firebase CLI 설치

+ 로그인

  • npm install -g firebase-tools
  • firebase login
nodeJS 설치되어 있어야 함. 없다면 https://nodejs.org/참고.

firebase --version 에서 숫자가 나오면 정상.

FlutterFire CLI 설치 dart pub global activate flutterfire_cli
Firebase 프로젝트 연동 flutterfire configure

→ Android/iOS 설정 파일 자동 생성

파이어베이스에서 작성해둔 프로젝트 중 하나를 고르게 된다. 여기에 맞게 설정 파일을 자동으로 생성.
pubspec.yaml 설정
dependencies:
  firebase_core: ^3.8.1
  firebase_auth: ^5.3.3
패키지 설치 flutter pub get

main.dart[편집 | 원본 편집]

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(const MyApp());
}

회원가입[편집 | 원본 편집]

await FirebaseAuth.instance.createUserWithEmailAndPassword(
  email: email,
  password: password,
);

로그인[편집 | 원본 편집]

await FirebaseAuth.instance.signInWithEmailAndPassword(
  email: email,
  password: password,
);

로그아웃[편집 | 원본 편집]

await FirebaseAuth.instance.signOut();

로그인 상태 감지[편집 | 원본 편집]

FirebaseAuth는 로그인 상태를 Stream으로 감시할 수 있다.

StreamBuilder(
  stream: FirebaseAuth.instance.authStateChanges(),
  builder: (context, snapshot) {
    if (snapshot.hasData) {
      return HomeScreen();   // 로그인됨
    }
    return LoginScreen();    // 로그인 안됨
  },
)

구글 로그인[편집 | 원본 편집]

안타깝게도 Window 앱에선 지원하지 않는다. 기본적으로 플러터는 모바일과 웹을 위한 것.

사전 준비[편집 | 원본 편집]

단계 설명 비고
Firebase 콘솔 설정 파이어베이스 콘솔에서 제공업체를 선택한다.

Authentication → Sign-in Method → Google 활성화 로그인 방법 > 새 제공업체 등에서 선택한다.

https://console.firebase.google.com/project/ktalk-d20ab/authentication/providers
(웹의 경우)

구글 클라우드 설정

클라우드 콘솔 설정.(반드시 파이어베이스와 같은 프로젝트로 진행해야 한다.)
  • 왼쪽 메뉴: API 및 서비스 → OAuth 동의 화면, 외부 선택 → 만들기
  • 앱 이름, 이메일 입력 → 저장
  • 왼쪽 메뉴: API 및 서비스 → 사용자 인증 정보
  • 상단: + 사용자 인증 정보 만들기 → OAuth 2.0 클라이언트 ID
  • 애플리케이션 유형: 웹 애플리케이션
  • 이름: Web Client (아무거나)
  • 만들기 클릭
  • 생성되면 팝업에서 클라이언트 ID 복사 (xxx.apps.googleusercontent.com 형식)

index.html에 추가

<meta name="google-signin-client_id" content="여기에_클라이언트_ID_붙여넣기.apps.googleusercontent.com"> 형식으로.


아래 링크에서 People API를 활성화해준다.(프론트에서 로그인을 처리하기에 과정이 좀 있네;;)

  • 또는 Google Cloud Console → API 및 서비스라이브러리
  • 안드로이드 Ios의 경우 거의 작업 없이 진행됨.
Firebase CLI 설치

+ 로그인

  • npm install -g firebase-tools
  • firebase login
nodeJS 설치되어 있어야 함. 없다면 https://nodejs.org/참고.

firebase --version 에서 숫자가 나오면 정상.

FlutterFire CLI 설치 dart pub global activate flutterfire_cli
Firebase 프로젝트 연동 flutterfire configure

→ Android/iOS 설정 파일 자동 생성

  • 파이어베이스에서 작성해둔 프로젝트 중 하나를 고르게 된다. 여기에 맞게 설정 파일을 자동으로 생성.
  • 콘솔에서 제공업체에 변경이 발생할 때마다 다시 진행해야 한다.
pubspec.yaml 설정
dependencies:
  firebase_core: ^3.8.1
  firebase_auth: ^5.3.3
  google_sign_in: ^6.2.2  # 공급업체에 따라 추가해준다.
패키지 설치 flutter pub get

구현 코드[편집 | 원본 편집]

import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'firebase_options.dart';

// 앱 시작 전에 Firebase를 초기화해야 함
void main() async {
  // Flutter 엔진이 완전히 초기화되도록 보장 (Firebase 사용 전 필수)
  WidgetsFlutterBinding.ensureInitialized();
  
  // Firebase 초기화 (firebase_options.dart 파일 사용) await로 초기화 완료될 때까지 기다려야 함
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Google 로그인 학습',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const GoogleSignInPage(),
    );
  }
}

class GoogleSignInPage extends StatefulWidget {
  const GoogleSignInPage({super.key});

  @override
  State<GoogleSignInPage> createState() => _GoogleSignInPageState();
}

class _GoogleSignInPageState extends State<GoogleSignInPage> {
  // Firebase Authentication 인스턴스 (사용자 인증 관리)
  final FirebaseAuth _auth = FirebaseAuth.instance;
  
  // Google Sign-In 인스턴스 (구글 로그인 처리)
  final GoogleSignIn _googleSignIn = GoogleSignIn();
  
  // 현재 로그인된 사용자 정보를 저장할 변수
  User? _user;

  @override
  void initState() {
    super.initState();
    // 앱 시작시 현재 로그인 상태 확인
    _user = _auth.currentUser;
  }

  // 구글 로그인 함수
  Future<void> _signInWithGoogle() async {
    try {
      // 1. Google Sign-In 팝업 띄우기 (사용자가 구글 계정 선택)
      final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();
      
      // 사용자가 로그인 취소한 경우
      if (googleUser == null) {
        print('로그인 취소됨');
        return;
      }

      // 2. 선택한 구글 계정의 인증 정보 가져오기
      final GoogleSignInAuthentication googleAuth = 
          await googleUser.authentication;

      // 3. Firebase용 인증 자격증명 생성
      //    accessToken: 구글 API 접근용 토큰
      //    idToken: 사용자 신원 확인용 토큰
      final credential = GoogleAuthProvider.credential(
        accessToken: googleAuth.accessToken,
        idToken: googleAuth.idToken,
      );

      // 4. Firebase에 자격증명으로 로그인
      final UserCredential userCredential = 
          await _auth.signInWithCredential(credential);

      // 5. 로그인 성공 - UI 업데이트
      setState(() {
        _user = userCredential.user;
      });

      print('로그인 성공: ${_user?.displayName}');
      
    } catch (e) {
      // 로그인 실패시 에러 출력
      print('로그인 실패: $e');
      
      // 사용자에게 에러 메시지 표시
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('로그인 실패: $e')),
        );
      }
    }
  }

  // 로그아웃 함수
  Future<void> _signOut() async {
    try {
      // Firebase에서 로그아웃
      await _auth.signOut();
      
      // Google Sign-In에서도 로그아웃 (다음 로그인시 계정 선택 가능)
      await _googleSignIn.signOut();

      // UI 업데이트
      setState(() {
        _user = null;
      });

      print('로그아웃 성공');
      
    } catch (e) {
      print('로그아웃 실패: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('구글 로그인 학습'),
        centerTitle: true,
      ),
      body: Center(
        child: Padding(
          padding: const EdgeInsets.all(20.0),
          child: _user == null
              ? _buildSignInUI()  // 로그인 안된 상태 UI
              : _buildUserInfoUI(), // 로그인된 상태 UI
        ),
      ),
    );
  }

  // 로그인 버튼 UI (로그인 전)
  Widget _buildSignInUI() {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        const Icon(
          Icons.account_circle,
          size: 100,
          color: Colors.grey,
        ),
        const SizedBox(height: 30),
        const Text(
          '구글 계정으로 로그인하세요',
          style: TextStyle(fontSize: 18),
        ),
        const SizedBox(height: 30),
        
        // 구글 로그인 버튼
        ElevatedButton.icon(
          onPressed: _signInWithGoogle,
          icon: const Icon(Icons.login),
          label: const Text('Google로 로그인'),
          style: ElevatedButton.styleFrom(
            padding: const EdgeInsets.symmetric(
              horizontal: 30,
              vertical: 15,
            ),
            textStyle: const TextStyle(fontSize: 16),
          ),
        ),
      ],
    );
  }

  // 사용자 정보 UI (로그인 후)
  Widget _buildUserInfoUI() {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        // 프로필 사진 (없으면 기본 아이콘)
        _user?.photoURL != null
            ? CircleAvatar(
                radius: 50,
                backgroundImage: NetworkImage(_user!.photoURL!),
              )
            : const CircleAvatar(
                radius: 50,
                child: Icon(Icons.person, size: 50),
              ),
        const SizedBox(height: 20),
        
        // 사용자 이름
        Text(
          _user?.displayName ?? '이름 없음',
          style: const TextStyle(
            fontSize: 24,
            fontWeight: FontWeight.bold,
          ),
        ),
        const SizedBox(height: 10),
        
        // 이메일
        Text(
          _user?.email ?? '이메일 없음',
          style: const TextStyle(
            fontSize: 16,
            color: Colors.grey,
          ),
        ),
        const SizedBox(height: 10),
        
        // UID (Firebase에서 부여한 고유 ID)
        Text(
          'UID: ${_user?.uid}',
          style: const TextStyle(
            fontSize: 12,
            color: Colors.grey,
          ),
          textAlign: TextAlign.center,
        ),
        const SizedBox(height: 30),
        
        // 로그아웃 버튼
        ElevatedButton.icon(
          onPressed: _signOut,
          icon: const Icon(Icons.logout),
          label: const Text('로그아웃'),
          style: ElevatedButton.styleFrom(
            backgroundColor: Colors.red,
            foregroundColor: Colors.white,
            padding: const EdgeInsets.symmetric(
              horizontal: 30,
              vertical: 15,
            ),
            textStyle: const TextStyle(fontSize: 16),
          ),
        ),
      ],
    );
  }
}

[편집 | 원본 편집]

User 객체[편집 | 원본 편집]

Firebase가 로그인 성공 시 반환하는 사용자 정보

  • uid: Firebase 고유 ID (변하지 않음, DB 키로 사용)
  • email: 이메일 주소
  • displayName: 표시 이름
  • photoURL: 프로필 사진 URL

실무 코드 구조 (권장) 파일 분리[편집 | 원본 편집]

학습용은 한 파일에 다 넣어도 되지만, 실무에서는 분리:

lib/
├── main.dart
├── services/
│   └── auth_service.dart      # 로그인/로그아웃 로직만
├── screens/
│   └── login_screen.dart      # UI만
└── models/
    └── user_model.dart         # 데이터 구조

보안[편집 | 원본 편집]

기본적으로 자동 처리로, 어차피 승인된 도메인에서만 작동하기에 key 값들도 그냥 공개해버려도 되는데, Firebase Security Rules 정도만 만져주면 될 듯하다. 여기서 권한을 주니까. 이건 나중의 이야기...

상태 관리[편집 | 원본 편집]

로그인 상태를 다른 화면에서도 사용하기 위해 Provider/Riverpod 사용 권장.

// Provider 예시
final authProvider = StreamProvider<User?>((ref) {
  return FirebaseAuth.instance.authStateChanges();
});