플러터:인증(OAuth2)
둘러보기로 이동
검색으로 이동
커스텀 OAuth2 인증 (네이버, 카카오 등)[편집 | 원본 편집]
Firebase는 Google/Apple/GitHub 등 주요 소셜 로그인만 공식 지원합니다. 네이버, 카카오, 디스코드 등의 OAuth2 인증은 직접 구현해야 합니다.
방식 비교[편집 | 원본 편집]
| 방식 | 장점 | 단점 | 사용 케이스 |
|---|---|---|---|
| Firebase Auth 공식 | 설정만으로 완성, 자동 토큰 관리 | 지원 제공업체만 가능 | Google, Apple, GitHub, Microsoft |
| Firebase + Custom Token | Firebase 생태계 유지 | 백엔드 서버 필요 | 네이버/카카오 + Firestore 함께 사용 |
| 순수 OAuth2 직접 구현 | 완전한 제어, 백엔드 불필요 | 토큰/세션 직접 관리 | 간단한 앱, 학습용 |
네이버 OAuth2 예시 (순수 구현)[편집 | 원본 편집]
사전 준비[편집 | 원본 편집]
| 단계 | 설명 |
|---|---|
| 네이버 개발자센터 설정 |
|
| 패키지 설치 | dependencies:
http: ^1.1.0 # HTTP 요청용
flutter_secure_storage: ^9.0.0 # 토큰 안전 저장
url_launcher: ^6.2.1 # OAuth 웹뷰 열기
|
OAuth2 흐름[편집 | 원본 편집]
- 사용자가 "네이버 로그인" 버튼 클릭
- 네이버 로그인 페이지로 이동 (브라우저/웹뷰)
- 사용자 로그인 → 네이버가 Authorization Code 발급
- 앱이 Code를 받아서 Access Token 요청
- Access Token으로 사용자 정보 API 호출
구현 코드[편집 | 원본 편집]
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:url_launcher/url_launcher.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class NaverAuthService {
// 네이버 개발자센터에서 발급받은 정보
static const String clientId = 'YOUR_CLIENT_ID';
static const String clientSecret = 'YOUR_CLIENT_SECRET';
static const String redirectUri = 'http://localhost/callback';
final storage = const FlutterSecureStorage();
// 1단계: 네이버 로그인 페이지 열기
Future<void> signIn() async {
final authUrl = Uri.parse(
'https://nid.naver.com/oauth2.0/authorize'
'?response_type=code'
'&client_id=$clientId'
'&redirect_uri=$redirectUri'
'&state=RANDOM_STATE', // CSRF 방지용 랜덤 문자열
);
if (await canLaunchUrl(authUrl)) {
await launchUrl(authUrl, mode: LaunchMode.externalApplication);
// 실제로는 Callback을 받아야 함 (아래 참고)
// 웹: window.location 감지
// 모바일: Deep Link 설정 필요
}
}
// 2단계: Authorization Code로 Access Token 받기
Future<String?> getAccessToken(String code) async {
final response = await http.post(
Uri.parse('https://nid.naver.com/oauth2.0/token'),
body: {
'grant_type': 'authorization_code',
'client_id': clientId,
'client_secret': clientSecret,
'code': code,
'state': 'RANDOM_STATE',
},
);
if (response.statusCode == 200) {
final data = json.decode(response.body);
final accessToken = data['access_token'];
// 안전하게 토큰 저장
await storage.write(key: 'naver_token', value: accessToken);
return accessToken;
}
return null;
}
// 3단계: Access Token으로 사용자 정보 가져오기
Future<Map<String, dynamic>?> getUserInfo() async {
final token = await storage.read(key: 'naver_token');
if (token == null) return null;
final response = await http.get(
Uri.parse('https://openapi.naver.com/v1/nid/me'),
headers: {'Authorization': 'Bearer $token'},
);
if (response.statusCode == 200) {
final data = json.decode(response.body);
return data['response']; // {id, name, email, profile_image, ...}
}
return null;
}
// 로그아웃
Future<void> signOut() async {
await storage.delete(key: 'naver_token');
}
}
// UI 예시
class NaverLoginPage extends StatefulWidget {
const NaverLoginPage({super.key});
@override
State<NaverLoginPage> createState() => _NaverLoginPageState();
}
class _NaverLoginPageState extends State<NaverLoginPage> {
final NaverAuthService _authService = NaverAuthService();
Map<String, dynamic>? _userInfo;
@override
void initState() {
super.initState();
_checkLoginStatus();
}
Future<void> _checkLoginStatus() async {
final userInfo = await _authService.getUserInfo();
if (userInfo != null) {
setState(() => _userInfo = userInfo);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('네이버 로그인')),
body: Center(
child: _userInfo == null
? ElevatedButton(
onPressed: () => _authService.signIn(),
child: const Text('네이버 로그인'),
)
: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('이름: ${_userInfo!['name']}'),
Text('이메일: ${_userInfo!['email']}'),
ElevatedButton(
onPressed: () async {
await _authService.signOut();
setState(() => _userInfo = null);
},
child: const Text('로그아웃'),
),
],
),
),
);
}
}
Callback 처리 (중요!)[편집 | 원본 편집]
웹[편집 | 원본 편집]
import 'dart:html' as html;
void main() {
// URL에서 code 파라미터 추출
final uri = Uri.parse(html.window.location.href);
final code = uri.queryParameters['code'];
if (code != null) {
// Access Token 받기
NaverAuthService().getAccessToken(code);
}
runApp(const MyApp());
}
Android (Deep Link)[편집 | 원본 편집]
android/app/src/main/AndroidManifest.xml:
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="yourapp"
android:host="callback" />
</intent-filter>
Flutter에서 받기:
import 'package:uni_links/uni_links.dart';
void initState() {
super.initState();
// Deep Link 리스너
uriLinkStream.listen((Uri? uri) {
if (uri != null) {
final code = uri.queryParameters['code'];
if (code != null) {
_authService.getAccessToken(code);
}
}
});
}
Firebase Custom Token 방식[편집 | 원본 편집]
백엔드 서버가 있다면 더 안전한 방식:
흐름[편집 | 원본 편집]
- Flutter → 네이버 OAuth로 Access Token 받기
- Flutter → 백엔드 서버로 Access Token 전송
- 백엔드 → 네이버 API로 사용자 정보 확인
- 백엔드 → Firebase Admin SDK로 Custom Token 생성
- 백엔드 → Flutter로 Custom Token 전송
- Flutter → Firebase.signInWithCustomToken()으로 로그인
장점[편집 | 원본 편집]
- Client Secret이 앱에 노출 안 됨 (보안 ↑)
- Firebase Authentication + Firestore 연동 가능
- Firebase Security Rules 사용 가능
백엔드 예시 (Node.js)[편집 | 원본 편집]
const admin = require('firebase-admin');
app.post('/auth/naver', async (req, res) => {
const { accessToken } = req.body;
// 1. 네이버 사용자 정보 확인
const naverUser = await fetch('https://openapi.naver.com/v1/nid/me', {
headers: { 'Authorization': `Bearer ${accessToken}` }
}).then(r => r.json());
// 2. Firebase Custom Token 생성
const customToken = await admin.auth().createCustomToken(naverUser.response.id);
res.json({ customToken });
});
Flutter에서 사용[편집 | 원본 편집]
// 1. 네이버 로그인 → Access Token 받기
final accessToken = await getNaverAccessToken();
// 2. 백엔드로 전송 → Custom Token 받기
final response = await http.post(
Uri.parse('https://yourserver.com/auth/naver'),
body: {'accessToken': accessToken},
);
final customToken = json.decode(response.body)['customToken'];
// 3. Firebase 로그인
await FirebaseAuth.instance.signInWithCustomToken(customToken);
// 4. 이제 Firebase 기능 모두 사용 가능!
final user = FirebaseAuth.instance.currentUser;
추천 방식 정리[편집 | 원본 편집]
| 상황 | 추천 방식 |
|---|---|
| 학습용, 간단한 앱 | 순수 OAuth2 직접 구현 |
| Firestore/Storage 사용 | Firebase + Custom Token (백엔드 필요) |
| 이미 백엔드 서버 있음 | 백엔드에서 JWT 발급 (Firebase 불필요) |
| Google/Apple만 사용 | Firebase Auth 공식 (제일 간단) |
주의사항[편집 | 원본 편집]
보안[편집 | 원본 편집]
- Client Secret은 절대 앱 코드에 하드코딩 금지!
- 백엔드 서버나 환경변수로 관리
- 또는 Firebase Functions 사용
토큰 관리[편집 | 원본 편집]
- Access Token은
flutter_secure_storage로 암호화 저장 - Refresh Token으로 자동 갱신 구현 권장
Deep Link[편집 | 원본 편집]
- Android:
AndroidManifest.xml설정 - iOS:
Info.plist+ URL Scheme 설정 - 웹: Callback URL을 앱 도메인으로 설정
패키지 활용[편집 | 원본 편집]
직접 구현이 복잡하다면 커뮤니티 패키지 사용:
- 카카오:
kakao_flutter_sdk(공식) - 네이버:
flutter_naver_login - 디스코드:
discord_oauth2 - 범용 OAuth2:
oauth2,flutter_appauth
하지만 학습 목적이라면 직접 구현 추천!