PromleeBlog
sitemapaboutMe

posting thumbnail
Flutter에서 Firebase Auth를 사용하여 전화번호 인증 구현하기
Implement Phone Number Authentication with Firebase Auth in Flutter

📅

🚀

들어가기 전에🔗

이 포스팅은 firebase_auth ^5.5.0 버전을 기준으로 작성되었습니다.
이미 파이어베이스 콘솔에 프로젝트가 생성되어있고, 앱이 등록되어 있다고 가정합니다. 앱을 아직 등록하지 못하셨다면 앱 등록 포스팅을 참고해 주세요.
휴대폰 인증을 지원하는 여러 서비스가 있습니다. 네이버 클라우드 콘솔, 카카오 알림톡, Nice 인증 등 많은 서비스가 휴대폰 인증을 지원합니다. 하지만 이 포스팅에서는 플러터에서 가장 쉽게 구현할 수 있는
Firebase Auth
를 사용하여 휴대폰 인증을 구현하는 방법을 알아보도록 하겠습니다.

🚀

1. Firebase Auth 설정🔗

파이어베이스 콘솔에서 빌드-Authentication 페이지로 이동해 시작하기를 눌러줍니다.
Authentication 시작하기
Authentication 시작하기
이후 뜨는 로그인 방법 설정 창에서 '전화'를 활성화 해줍니다. 이때, 에뮬레이터를 사용하는 분들은 임의의 테스트용 전화번호와 인증번호를 추가해주세요.
필자는 +1 1234567812, 123654 를 추가했습니다.
전화 활성화
전화 활성화
전화 활성화 완료
전화 활성화 완료

🚀

2. SHA 인증 정보 추가 (Android)🔗

2.1. 인증서 지문 얻기🔗

Android 앱에서 파이어베이스 인증을 사용하기 위해서는 SHA 인증 정보를 추가해야 합니다. 다음과 같은 명령어를 통해 SHA 인증 정보를 확인할 수 있습니다.
비밀번호를 입력하라는 메시지가 뜰 텐데 기본 비밀번호인 android을 입력합니다.
MAC/Linux
keytool -list -v \
-alias androiddebugkey -keystore ~/.android/debug.keystore
Windows
keytool -list -v \
-alias androiddebugkey -keystore %USERPROFILE%\.android\debug.keystore
성공 시 다음과 같이 나타나게 됩니다.
SHA 인증 정보 찾기
SHA 인증 정보 찾기

2.2. SHA 인증 정보 추가🔗

파이어베이스 콘솔에서 등록되어있는 안드로이드 앱 설정에 들어갑니다
안드로이드 앱 설정
안드로이드 앱 설정
일반 탭 아래에 있는 내 앱 항목에서 디지털 지문 추가 버튼을 눌러줍니다.
디지털 지문 추가
디지털 지문 추가
인증서 지문 SHA1, SHA256을 각각 추가해줍니다.

🚀

3. 플러터 코드 작성🔗

필자의 폴더 구조는 다음과 같습니다. main.dart에서 page_onboarding.dart를 호출합니다.
📦lib
┣ 📜main.dart
┣ 📜page_onboarding.dart

3.1. Firebase Auth 플러그인 추가🔗

프로젝트 루트 폴더에서 다음 명령어를 실행해 플러그인을 추가합니다.
flutter pub add firebase_auth

3.2. page_onboarding.dart 파일 작성🔗

page_onboarding.dart 파일에 다음 코드를 추가합니다. 번호는 에서 추가한 테스트 번호를 사용합니다.
/lib/page_onboarding.dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
 
class PageOnboarding extends StatelessWidget {
  const PageOnboarding({super.key});
 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            TextButton(
                onPressed: () async {
                  await FirebaseAuth.instance.verifyPhoneNumber(
                    phoneNumber: '+1 1234567812', // 테스트 번호
                    verificationCompleted: (PhoneAuthCredential credential) {
                      print("credential: $credential");
                    },
                    verificationFailed: (FirebaseAuthException e) {
                      print("verificationFailed: $e");
                    },
                    codeSent: (String verificationId, int? resendToken) {
                      print("codeSent: $verificationId");
                      print("codeSent: $resendToken");
                    },
                    codeAutoRetrievalTimeout: (String verificationId) {
                      print("codeAutoRetrievalTimeout: $verificationId");
                    },
                  );
                },
                child: const Text("Phone Number Verification"))
          ],
        ),
      ),
    );
  }
}
텍스트를 클릭해 Captcha 인증 후 인증 완료 로그가 출력되는지 확인합니다.
만일 다음과 같은 오류가 발생한다면 인증 요청 시 오류 발생 항목을 참고해 주세요.
Fatal error: Please register custom URL scheme app-1-****** in the app's Info.plist file.

🚀

4. verifyPhoneNumber 함수 콜백 살펴보기🔗

4.1. verificationCompleted🔗

Android 기기에서만 작동하는 SMS 코드 자동 처리 콜백입니다.
verificationCompleted: (PhoneAuthCredential credential) {
  print("credential: $credential");
	await auth.signInWithCredential(credential); // 자동 인증 과정 진행
},

4.2. verificationFailed🔗

잘못된 전화번호나 SMS 할당량 초과 여부 등의 실패 이벤트를 처리합니다.
verificationFailed: (FirebaseAuthException e) {
	if (e.code == 'invalid-phone-number') {
		print('The provided phone number is not valid.');
	} else {
		print("verificationFailed: $e");
	}
},

4.3. codeSent🔗

Firebase에서 기기로 코드가 전송된 경우를 처리하며 사용자에게 코드를 입력하라는 메시지를 표시하는 데 사용됩니다.
codeSent: (String verificationId, int? resendToken) {
  print("codeSent: $verificationId");
  print("codeSent: $resendToken");
},

4.4. codeAutoRetrievalTimeout🔗

자동 SMS 코드 처리에 실패한 경우 시간 초과를 처리합니다.
codeAutoRetrievalTimeout: (String verificationId) {
  print("codeAutoRetrievalTimeout: $verificationId");
},

🚀

5. 인증 완료 후 처리🔗

휴대폰 인증 코드를 입력하면 인증 완료 후 다음과 같은 코드를 통해 인증 완료 처리를 할 수 있습니다.
이때, verificationId는 4.3. codeSent에서 받은 값을 사용합니다. (전체 코드 참고)
/lib/page_onboarding.dart
TextButton(
	onPressed: () async {
		PhoneAuthCredential credential =
				PhoneAuthProvider.credential(
						verificationId: verificationId,
						smsCode: "123654",
						);
		await FirebaseAuth.instance
				.signInWithCredential(credential)
				.then((value) {
				print("signInWithCredential: $value");
		}).catchError((error) {
			print("signInWithCredential: $error");
		});
	},
	child: const Text("Sign In"),
),

🚀

문제 발생 및 해결🔗

빌드 시 오류 발생 - 코코아팟 버전 충돌🔗

Firebase/CoreOnly (= 11.8.0)
2
 
    You have either:
     * out-of-date source repos which you can update with pod repo update or with pod install --repo-update.
     * changed the constraints of dependency Firebase/CoreOnly inside your development pod firebase_core.
       You should run pod update Firebase/CoreOnly to apply changes you've made.
 
    /Library/Ruby/Gems/2.6.0/gems/molinillo-0.8.0/lib/molinillo/resolution.rb:317:in raise_error_unless_state'
    /Library/Ruby/Gems/2.6.0/gems/molinillo-0.8.0/lib/molinillo/resolution.rb:299:in block in unwind_for_conflict'
    /Library/Ruby/Gems/2.6.0/gems/molinillo-0.8.0/lib/molinillo/resolution.rb:297:in tap'
    /Library/Ruby/Gems/2.6.0/gems/molinillo-0.8.0/lib/molinillo/resolution.rb:297:in unwind_for_conflict'
    /Library/Ruby/Gems/2.6.0/gems/molinillo-0.8.0/lib/molinillo/resolution.rb:257:in process_topmost_state'
    /Library/Ruby/Gems/2.6.0/gems/molinillo-0.8.0/lib/molinillo/resolution.rb:182:in resolve'
    /Library/Ruby/Gems/2.6.0/gems/molinillo-0.8.0/lib/molinillo/resolver.rb:43:in resolve'
    /Library/Ruby/Gems/2.6.0/gems/cocoapods-1.16.2/lib/cocoapods/resolver.rb:94:in resolve'
    /Library/Ruby/Gems/2.6.0/gems/cocoapods-1.16.2/lib/cocoapods/installer/analyzer.rb:1082:in block in resolve_dependencies'
    /Library/Ruby/Gems/2.6.0/gems/cocoapods-1.16.2/lib/cocoapods/user_interface.rb:64:in section'
    /Library/Ruby/Gems/2.6.0/gems/cocoapods-1.16.2/lib/cocoapods/installer/analyzer.rb:1080:in resolve_dependencies'
    /Library/Ruby/Gems/2.6.0/gems/cocoapods-1.16.2/lib/cocoapods/installer/analyzer.rb:125:in analyze'
    /Library/Ruby/Gems/2.6.0/gems/cocoapods-1.16.2/lib/cocoapods/installer.rb:422:in analyze'
    /Library/Ruby/Gems/2.6.0/gems/cocoapods-1.16.2/lib/cocoapods/installer.rb:244:in block in resolve_dependencies'
    /Library/Ruby/Gems/2.6.0/gems/cocoapods-1.16.2/lib/cocoapods/user_interface.rb:64:in section'
    /Library/Ruby/Gems/2.6.0/gems/cocoapods-1.16.2/lib/cocoapods/installer.rb:243:in resolve_dependencies'
    /Library/Ruby/Gems/2.6.0/gems/cocoapods-1.16.2/lib/cocoapods/installer.rb:162:in install!'
    /Library/Ruby/Gems/2.6.0/gems/cocoapods-1.16.2/lib/cocoapods/command/install.rb:52:in run'
    /Library/Ruby/Gems/2.6.0/gems/claide-1.1.0/lib/claide/command.rb:334:in run'
    /Library/Ruby/Gems/2.6.0/gems/cocoapods-1.16.2/lib/cocoapods/command.rb:52:in run'
    /Library/Ruby/Gems/2.6.0/gems/cocoapods-1.16.2/bin/pod:55:in <top (required)>'
    /usr/local/bin/pod:23:in load'
    /usr/local/bin/pod:23:in `<main>'
 
Error output from CocoaPods:

 
    [!] Automatically assigning platform iOS with version 13.0 on target Runner because no platform was specified. Please specify a platform for this target in your Podfile. See https://guides.cocoapods.org/syntax/podfile.html#platform.
 
Error: CocoaPods's specs repository is too out-of-date to satisfy dependencies.
To update the CocoaPods specs, run:
  pod repo update
 
Error running pod install
Error launching application on iPhone 16 Pro Max.
➡️

해결 방법🔗

CocoaPods의 저장소가 오래된 상태일 수 있으므로 업데이트 후 의존성을 다시 설치해 줍니다.
pod repo update
pod install --repo-update

빌드 시 오류 발생 - 파이어베이스 버전 충돌🔗

Podfile.lock에는 Firebase/CoreOnly 버전이 11.6.0으로 잠겨 있지만, firebase_core 플러그인은 11.8.0을 요구하고 있어서 버전 충돌이 발생했습니다.
!] CocoaPods could not find compatible versions for pod "Firebase/CoreOnly":
  In snapshot (Podfile.lock):
    Firebase/CoreOnly (= 11.6.0)
 
  In Podfile:
    firebase_core (from .symlinks/plugins/firebase_core/ios) was resolved to 3.12.0, which depends on
      Firebase/CoreOnly (= 11.8.0)
 
You have either:
 * changed the constraints of dependency Firebase/CoreOnly inside your development pod firebase_core.
   You should run pod update Firebase/CoreOnly to apply changes you've made.
➡️

해결 방법🔗

Podfile.lock을 삭제하고 다시 설치합니다.
rm ios/Podfile.lock
pod install --repo-update

인증 요청 시 오류 발생🔗

ios에서 Recaptcha 인증을 위해 브라우저에 갔다 올 때 URL Scheme가 등록되지 않은 경우 다음과 같은 오류가 발생할 수 있습니다.
Fatal error: Please register custom URL scheme app-1-****** in the app's Info.plist file.
➡️

해결 방법🔗

info.plist에 다음과 같이 URL Scheme을 추가합니다.
/ios/Runner/Info.plist
<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>app-1-******</string>
        </array>
    </dict>
</array>

🚀

결론🔗

전체 코드🔗

펼쳐보기
/lib/page_onboarding.dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
 
class PageOnboarding extends StatefulWidget {
  const PageOnboarding({super.key});
 
  @override
  State<PageOnboarding> createState() => _PageOnboardingState();
}
 
class _PageOnboardingState extends State<PageOnboarding> {
  String verificationId = '';
 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            TextButton(
              onPressed: () async {
                await FirebaseAuth.instance.verifyPhoneNumber(
                  phoneNumber: '+1 1234567812', // 테스트 번호
                  timeout: const Duration(seconds: 60),
                  verificationCompleted: (PhoneAuthCredential credential) {
										print("verificationCompleted: $credential");
                  },
                  verificationFailed: (FirebaseAuthException e) {
										print("verificationFailed: $e");
                  },
                  codeSent: (String verificationId, int? resendToken) {
                    this.verificationId = verificationId;
										print("codeSent: $verificationId");
										print("codeSent: $resendToken");
                  },
                  codeAutoRetrievalTimeout: (String verificationId) {
										print("codeAutoRetrievalTimeout: $verificationId");
                  },
                );
              },
              child: const Text("Phone Number Verification"),
            ),
            const SizedBox(height: 20),
            SizedBox(
              width: 200,
              child: TextField( 	// 인증 코드 입력 (123654)
                onChanged: (value) {
                  setState(() {
                    smsCode = value;
                  });
                },
              ),
            ),
            TextButton(
              onPressed: () async {
                PhoneAuthCredential credential = PhoneAuthProvider.credential(
                    verificationId: verificationId, smsCode: smsCode);
                await FirebaseAuth.instance
                    .signInWithCredential(credential)
											.then((value) {
										print("signInWithCredential: $value");
                }).catchError((error) {
										print("signInWithCredential: $error");
                });
              },
              child: const Text("Sign In"),
            ),
          ],
        ),
      ),
    );
  }
}

더 생각해 보기🔗

참고🔗