플러터:인증(Firebase 인증)
개요[편집 | 원본 편집]
Flutter에서 Firebase Authentication을 이용해 앱에 로그인 기능을 구현하는 방법을 정리한 문서.
특징[편집 | 원본 편집]
- 쉽다: Firebase는 이메일/비밀번호, 구글, 애플, 전화번호 등 다양한 인증 방식을 제공하며, 별도의 백엔드 없이도 안정적인 인증 시스템을 구축할 수 있다.
- Firebase가 제공하는 인증 서비스(공식지원)
- 서버 없이 사용자 계정 관리 가능(백엔드 필요 없음)
- 소셜 로그인(Google/Apple/GitHub/Microsoft) 지원
- 인증 상태(로그인 여부) 자동 유지
- 토큰/세션 관리 자동 처리
한계[편집 | 원본 편집]
- 기본적으로 제공하는 소셜로그인 외의 OAuth2 등 인증은 따로 구현하는 편이 더 간편하다.
Firebase 인증 개념[편집 | 원본 편집]
인증 흐름[편집 | 원본 편집]
- 앱에서 FirebaseAuth API 호출
- Firebase가 사용자 인증 처리
- 성공 시 User 객체 반환
- authStateChanges() 스트림을 통한 로그인 상태 전달
기본 인증 (Email/Password)[편집 | 원본 편집]
이메일과 패스워드만 사용한 인증을 구현한다.(그런데, 사실 소셜로그인을 이용하는 경우가 많이 때문에.. 이 방식은 굳이...?)
사전 준비[편집 | 원본 편집]
| 단계 | 설명 | 비고 |
|---|---|---|
| Firebase CLI 설치
+ 로그인 |
|
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 |
| (웹의 경우)
구글 클라우드 설정 |
클라우드 콘솔 설정.(반드시 파이어베이스와 같은 프로젝트로 진행해야 한다.)
index.html에 추가 <meta name="google-signin-client_id" content="여기에_클라이언트_ID_붙여넣기.apps.googleusercontent.com"> 형식으로.
아래 링크에서 People API를 활성화해준다.(프론트에서 로그인을 처리하기에 과정이 좀 있네;;)
|
|
| Firebase CLI 설치
+ 로그인 |
|
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();
});