👋 들어가기 전
차기 버전에서 Phone Authentication을 도입한다는 소식이 들려
미리 Sample 프로젝트에 테스트를 진행해보려고 한다.
처음 해보는 인증 메서드이므로 시간이 조금 걸리더라도 차근차근 정리해 놓고
이후 실제 프로젝트에 들어갈 때 빠르게 진행할 수 있도록 하자.
✊ 등록
모든 서드파티의 근본 과정인 우리 서비스를 등록하는 과정이다.
기본적인 부분은 이미지로 살펴보고 중요하게 설정하는 부분만 조금 더 깊게 알아보자.
전화 옵션을 키면 아래와 같이 테스트용 전화번호를 선택할 수 있는 창이 뜬다.
테스트용으로 사용하지만 저 전화번호는 Firebase 접근에 막대한 권한을 갖기때문에
유출에 주의하자!
등록을 맞치면 전화번호와 해당하는 인증코드가 잘 등록이 되어있고, 위 주의 사항을 읽어보니
1000개의 일일 할당량이 제공된다고 한다. 1000개면 테스트에 충분할 것 같으니 일단은 넘어가자.
주의할 점
공식문서를 보면 전화번호 인증에 대해 다음과 같이 주의를 고하고 있다.
보안 문제
전화번호만 사용하는 인증은 편리하기는 하지만, 전화번호 소유권은 사용자 간에 쉽게 이전될 수 있으므로
다른 방식보다 보안성이 떨어집니다.
또한 기기에 여러 사용자 프로필이 있는 경우 SMS 메시지를 받을 수 있는 사람이라면 누구든지 기기의 전화번호로 계정에 로그인할 수 있습니다.
앱에서 전화번호 기반 로그인을 사용하는 경우 더 안전한 로그인 방법을 함께 제공해야 하고, 전화번호 로그인을 사용하면
보안이 약화된다는 점을 사용자에게 알려야 합니다.
정리하면 전화번호만 사용하면 보안이 취약해 서비스 제공자는 전화번호와 같이
다른 방법도 고민해야하고 전화번호 인증에 대한 위험을 유저에게 알려야한다.
☝️공식 문서 분석
1. 인증 방식
파이어 베이스 공식문서를 살펴보면 전화번호 인증방식은 총 2개의 인증 절차로 진행된다.
첫번 째는 자동 APN 알림, 두번 째는 reCAPTCHA 인증이다.
APN은 Apple Push Notification Service의 약자로 간단히 앱 노티 메시지라고 이해하면 되고
reCAPTCHA는 아래 사진과 같이 유저의 행동을 통해 봇을 방지하는 인증 방식이다.
2개의 인증방식을 다음과 같은 시나리오로 동작한다.
APN | reCAPTCHA | |
APN을 받을 수 없는 상황인가? | X | O |
시뮬레이터인가? | X | O |
APN 인증을 실패했는가 | X | O |
그 외 | O | X |
정리하면 APN 과정에 문제가 있거나 시뮬레이터를 이용한 테스트가 아닐 경우 reCAPTCHA는 보기 힘들다.
✌️프로젝트 설정
1. 설치
등록이 마무리되면 마찬가지로 firebase를 프로젝트에 설치하고 GoogleService-Info 등을 등록한다.
위 내용은 아래 포스팅을 참고하자.
위 포스팅과 다른 내용을 타겟에 FirebaseAuth Library를 추가해야한다.
2. 푸시 알람 설정
3. 백그라운드 알림 설정
백그라운드 패치 및 원격 알림 설정
4. reCAPTCHA를 위한 URL Type 추가
다음과 같이 URL Schemes에 인코딩된 Firebase app ID를 적는다.
인코딩된 앱 ID는 프로젝트 설정 -> 아래로 스크롤 하면 있다.
5. swizzling 방지 ( 선택 사항)
Firebase가 silent push notification으로 APNs 토큰을 얻거나, reCAPTCHA 지정한 custom scheme으로 리다이렉트를 하는 과정에서 Swizzling을 한다.
swizzling 사용하지 않는다면 명시적으로 APNs 토큰을 전달하고 redirect URL도 제공해야한다.
FirebaseAppDelegateProxyEnabled = No
👍 APN 발급 및 등록
1. APN 발급
개발자 계정 -> Keys -> APNs 체크 -> 만든 키 다운로드 (한번 밖에 다운할 수 없음)
2. 파이어 베이스에 등록
프로젝트 설정 -> 클라우드 메시징에 들어가서 APN 인증키 업로드를 들어간다.
3가지 내용이 필요한데
1) 다운받은 .p8 파일
2) 지금 생성한 KEY ID
3) 팀 ID (애플 개발자 페이지 Account에서 확인가능)
🧑💻 swift code
정말 많은 세팅 시간이 지나갔다.
이제 실질적인 authentication을 위한 코드를 적어보자.
1. AppDelegate
import Firebase
import FirebaseAuth
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
return true
}
// 앱이 APNs에 성공적으로 등록된 후 호출
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
Auth.auth().setAPNSToken(deviceToken, type: .sandbox) // 디바이스 토큰을 Firebase Authentication에 전달
}
// 앱이 원격 알림을 수신했을 때 호출
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
if Auth.auth().canHandleNotification(userInfo) {
completionHandler(.noData)
}
}
// 앱이 URL 스킴 또는 딥 링크를 통해 호출되었을 때
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
if Auth.auth().canHandle(url) {
return true
}
return false
}
}
2. authenticate
유효성 검증과 UI는 지금은 필요없고
중요한 것은 휴대전화 형식 아래에서 보는 것 같이 2가지 정보를 줘야한다.
- +국가번호
- 핸드폰번호 (-없이)
이후 가상전화만 테스트 한다면 아래 isAppVerifi...(이하 생략) 플래그를 켜주면 된다.
func authenticate() {
let code = "123456" // 여기를 랜덤한 6자리 숫자를 만드는 함수로 대체하면될 듯
Auth.auth().languageCode = "kr" // SMS 메시지 현지화
Auth.auth().settings?.isAppVerificationDisabledForTesting = true // 가상 번호를 테스트 플레그
let phoneNumber = "+825151212345"
PhoneAuthProvider.provider()
.verifyPhoneNumber(phoneNumber, uiDelegate: nil) { verificationID, error in
if let error = error {
print(error.localizedDescription)
return
}
guard let id = verificationID else { return }
let credential = PhoneAuthProvider.provider().credential(withVerificationID: id, verificationCode: code)
Auth.auth().signIn(with: credential) { (authData, error) in
if let error {
print(error.localizedDescription)
return
}
print("Info: \(authData?.user.uid) \(authData?.user.providerID) \(authData?.user.displayName)")
}
}
}
3. OTP send와 verify 구분하기
실제 전화로 테스트 할때는 코드를 보내는 것과 인증하는 것을 구분해야 하기 때문에
다음과 같이 가상번호 테스트 플레그를 제거하고 함수 2개로 나눠봤다.
func sendOTP() {
Auth.auth().languageCode = "kr"
PhoneAuthProvider.provider()
.verifyPhoneNumber(phoneNumber, uiDelegate: nil) { [weak self] verificationID, error in
STLogger.printDebug("SMS: \(verificationID)")
guard let self else { return }
if let error = error {
STLogger.printError("SMS ERROR: \(error)")
return
}
if let ID = verificationID {
self.verificationID = ID
}
}
}
func authenticate(otp: String) {
let credential = PhoneAuthProvider.provider().credential(withVerificationID: verificationID, verificationCode: otp)
Auth.auth().signIn(with: credential) { (authData, error) in
if let error {
STLogger.printDebug("SMS: \(error.localizedDescription)")
return
}
STLogger.printDebug("SMS: Info: \(authData?.user.uid) \(authData?.user.providerID) \(authData?.user.displayName)")
}
}
😀 소감 및 마무리
생각보다 코드는 별로 없지만 기본 설정을 많이 해야한다는게 힘들었다.
이번에 한번 정리했으니 세팅 시간은 훨씬 줄어들 것 같다.
전화번호 인증에 필요한 국가번호 api와 정규식을 이용한 형식 검증등이 필요해보인다.
위 내용은 실제 구현할 때 추가하도록 하자.
출처