完整的验证流程示例:
@interface YourViewController () <FIRAuthUIDelegate>
@property (nonatomic, strong) NSString *verificationID;
@end
@implementation YourViewController
- (void)startPhoneVerification:(NSString *)phoneNumber {
// 1. 获取 provider
FIRPhoneAuthProvider *provider = [FIRPhoneAuthProvider provider];
// 2. 开始验证流程
[provider verifyPhoneNumber:phoneNumber
UIDelegate:self
completion:^(NSString * _Nullable verificationID, NSError * _Nullable error) {
if (error) {
// 处理错误
NSLog(@"Error: %@", error.localizedDescription);
return;
}
// 3. 保存 verificationID
self.verificationID = verificationID;
// 4. 提示用户输入验证码
[self promptForVerificationCode];
}];
}
- (void)promptForVerificationCode {
// 显示输入验证码的 UI
UIAlertController *alertController = [UIAlertController
alertControllerWithTitle:@"输入验证码"
message:@"请输入您收到的 6 位验证码"
preferredStyle:UIAlertControllerStyleAlert];
[alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
textField.placeholder = @"验证码";
textField.keyboardType = UIKeyboardTypeNumberPad;
}];
UIAlertAction *verifyAction = [UIAlertAction
actionWithTitle:@"验证"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * _Nonnull action) {
NSString *verificationCode = alertController.textFields.firstObject.text;
[self verifyCode:verificationCode];
}];
[alertController addAction:verifyAction];
[self presentViewController:alertController animated:YES completion:nil];
}
- (void)verifyCode:(NSString *)verificationCode {
// 创建认证凭据
FIRAuthCredential *credential = [[FIRPhoneAuthProvider provider]
credentialWithVerificationID:self.verificationID
verificationCode:verificationCode];
// 使用凭据登录
[[FIRAuth auth] signInWithCredential:credential
completion:^(FIRAuthDataResult * _Nullable authResult,
NSError * _Nullable error) {
if (error) {
// 处理错误
NSLog(@"Authentication error: %@", error.localizedDescription);
return;
}
// 验证成功
NSLog(@"Successfully authenticated user: %@", authResult.user.uid);
}];
}
@end
注意事项:
reCAPTCHA 验证是自动触发的,你不需要手动获取 token
Firebase SDK 会自动处理 reCAPTCHA 验证流程
确保在 Firebase Console 中已经启用了电话号码验证方式
在 Info.plist 中添加必要的权限配置
如果你遇到 reCAPTCHA 相关的问题,请检查:
Firebase 项目配置是否正确
网络连接是否正常
是否正确实现了 FIRAuthUIDelegate 协议
Info.plist 中是否包含必要的权限配置
使用 Firebase Phone Auth 时的最佳实践:
总是处理错误情况
实现超时重试机制
提供清晰的用户反馈
考虑添加测试号码支持(在开发阶段)
- 封装一个完整的手机验证码流程管理类,包括获取验证码、验证码验证、获取 Token 并提交给服务器的完整流程。
// PhoneAuthManager.h
#import <Foundation/Foundation.h>
#import <FirebaseAuth/FirebaseAuth.h>
NS_ASSUME_NONNULL_BEGIN
typedef void(^PhoneAuthCompletion)(BOOL success, NSString * _Nullable token, NSError * _Nullable error);
typedef void(^VerificationCodeCompletion)(BOOL success, NSString * _Nullable verificationID, NSError * _Nullable error);
@interface PhoneAuthManager : NSObject <FIRAuthUIDelegate>
+ (instancetype)sharedManager;
/// 获取验证码
/// @param phoneNumber 手机号
/// @param completion 回调
- (void)requestVerificationCode:(NSString *)phoneNumber
completion:(VerificationCodeCompletion)completion;
/// 验证验证码并获取 Token
/// @param verificationCode 验证码
/// @param verificationID 验证 ID
/// @param completion 回调
- (void)verifyCode:(NSString *)verificationCode
verificationID:(NSString *)verificationID
completion:(PhoneAuthCompletion)completion;
/// 提交 Token 到服务器
/// @param token Firebase Token
/// @param completion 回调
- (void)submitTokenToServer:(NSString *)token
completion:(PhoneAuthCompletion)completion;
@end
NS_ASSUME_NONNULL_END
// PhoneAuthManager.m
#import "PhoneAuthManager.h"
@interface PhoneAuthManager ()
@property (nonatomic, strong) UIViewController *presentingViewController;
@end
@implementation PhoneAuthManager
+ (instancetype)sharedManager {
static PhoneAuthManager *manager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[PhoneAuthManager alloc] init];
});
return manager;
}
- (instancetype)init {
self = [super init];
if (self) {
// 获取当前最顶层的 ViewController
UIWindow *window = [[UIApplication sharedApplication] keyWindow];
self.presentingViewController = window.rootViewController;
while (self.presentingViewController.presentedViewController) {
self.presentingViewController = self.presentingViewController.presentedViewController;
}
}
return self;
}
#pragma mark - Public Methods
- (void)requestVerificationCode:(NSString *)phoneNumber
completion:(VerificationCodeCompletion)completion {
// 参数验证
if (![self isValidPhoneNumber:phoneNumber]) {
if (completion) {
NSError *error = [NSError errorWithDomain:@"PhoneAuthError"
code:-1
userInfo:@{NSLocalizedDescriptionKey: @"无效的手机号"}];
completion(NO, nil, error);
}
return;
}
// 获取 PhoneAuthProvider 实例
FIRPhoneAuthProvider *provider = [FIRPhoneAuthProvider provider];
// 开始验证流程
[provider verifyPhoneNumber:phoneNumber
UIDelegate:self
completion:^(NSString * _Nullable verificationID, NSError * _Nullable error) {
if (error) {
if (completion) {
completion(NO, nil, error);
}
return;
}
if (completion) {
completion(YES, verificationID, nil);
}
}];
}
- (void)verifyCode:(NSString *)verificationCode
verificationID:(NSString *)verificationID
completion:(PhoneAuthCompletion)completion {
// 参数验证
if (![self isValidVerificationCode:verificationCode] || !verificationID.length) {
if (completion) {
NSError *error = [NSError errorWithDomain:@"PhoneAuthError"
code:-2
userInfo:@{NSLocalizedDescriptionKey: @"无效的验证码或验证ID"}];
completion(NO, nil, error);
}
return;
}
// 创建认证凭据
FIRAuthCredential *credential = [[FIRPhoneAuthProvider provider]
credentialWithVerificationID:verificationID
verificationCode:verificationCode];
// 使用凭据登录
[[FIRAuth auth] signInWithCredential:credential
completion:^(FIRAuthDataResult * _Nullable authResult,
NSError * _Nullable error) {
if (error) {
if (completion) {
completion(NO, nil, error);
}
return;
}
// 获取 Firebase Token
[authResult.user getIDTokenWithCompletion:^(NSString * _Nullable token,
NSError * _Nullable error) {
if (error) {
if (completion) {
completion(NO, nil, error);
}
return;
}
if (completion) {
completion(YES, token, nil);
}
}];
}];
}
- (void)submitTokenToServer:(NSString *)token
completion:(PhoneAuthCompletion)completion {
// 创建请求 URL
NSURL *url = [NSURL URLWithString:@"YOUR_SERVER_URL"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
// 设置请求头
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
// 设置请求体
NSDictionary *params = @{@"token": token};
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:params
options:0
error:&error];
if (error) {
if (completion) {
completion(NO, nil, error);
}
return;
}
request.HTTPBody = jsonData;
// 发送请求
NSURLSessionDataTask *task = [[NSURLSession sharedSession]
dataTaskWithRequest:request
completionHandler:^(NSData * _Nullable data,
NSURLResponse * _Nullable response,
NSError * _Nullable error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
if (completion) {
completion(NO, nil, error);
}
return;
}
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode != 200) {
NSError *serverError = [NSError errorWithDomain:@"ServerError"
code:httpResponse.statusCode
userInfo:nil];
if (completion) {
completion(NO, nil, serverError);
}
return;
}
if (completion) {
completion(YES, nil, nil);
}
});
}];
[task resume];
}
#pragma mark - FIRAuthUIDelegate
- (void)presentViewController:(UIViewController *)viewControllerToPresent
animated:(BOOL)flag
completion:(void (^)(void))completion {
[self.presentingViewController presentViewController:viewControllerToPresent
animated:flag
completion:completion];
}
- (void)dismissViewControllerAnimated:(BOOL)flag
completion:(void (^)(void))completion {
[self.presentingViewController dismissViewControllerAnimated:flag
completion:completion];
}
#pragma mark - Helper Methods
- (BOOL)isValidPhoneNumber:(NSString *)phoneNumber {
// 这里添加手机号验证逻辑
NSString *pattern = @"^1[3-9]\\d{9}$";
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", pattern];
return [predicate evaluateWithObject:phoneNumber];
}
- (BOOL)isValidVerificationCode:(NSString *)code {
// 验证码格式验证
NSString *pattern = @"^\\d{6}$";
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", pattern];
return [predicate evaluateWithObject:code];
}
@end
使用示例:
// ViewController.m
- (void)startPhoneVerification {
NSString *phoneNumber = @"+8613800138000"; // 示例手机号
// 1. 请求验证码
[[PhoneAuthManager sharedManager] requestVerificationCode:phoneNumber
completion:^(BOOL success,
NSString * _Nullable verificationID,
NSError * _Nullable error) {
if (!success) {
NSLog(@"获取验证码失败: %@", error.localizedDescription);
return;
}
// 保存 verificationID
self.verificationID = verificationID;
// 显示输入验证码的 UI
[self showVerificationCodeInput];
}];
}
- (void)verifyCode:(NSString *)code {
// 2. 验证验证码
[[PhoneAuthManager sharedManager] verifyCode:code
verificationID:self.verificationID
completion:^(BOOL success,
NSString * _Nullable token,
NSError * _Nullable error) {
if (!success) {
NSLog(@"验证失败: %@", error.localizedDescription);
return;
}
// 3. 提交 Token 到服务器
[[PhoneAuthManager sharedManager] submitTokenToServer:token
completion:^(BOOL success,
NSString * _Nullable token,
NSError * _Nullable error) {
if (!success) {
NSLog(@"提交 Token 失败: %@", error.localizedDescription);
return;
}
// 验证成功,进行后续操作
NSLog(@"验证成功!");
}];
}];
}
- (void)showVerificationCodeInput {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"输入验证码"
message:@"请输入收到的验证码"
preferredStyle:UIAlertControllerStyleAlert];
[alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
textField.placeholder = @"验证码";
textField.keyboardType = UIKeyboardTypeNumberPad;
}];
UIAlertAction *verifyAction = [UIAlertAction actionWithTitle:@"验证"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * _Nonnull action) {
NSString *code = alert.textFields.firstObject.text;
[self verifyCode:code];
}];
[alert addAction:verifyAction];
[self presentViewController:alert animated:YES completion:nil];
}
这个封装包含了以下特点:
单例模式管理整个验证流程
完整的错误处理
参数验证
异步操作的完整回调
模块化的设计
符合 FIRAuthUIDelegate 协议
网络请求的处理
使用时需要注意:
需要在 AppDelegate 中配置 Firebase
需要替换 submitTokenToServer 中的服务器 URL
根据实际需求修改手机号和验证码的验证规则
可以根据需要添加超时处理和重试机制
建议添加网络状态检查
可以添加日志记录功能
可以添加更多的错误处理场景
这个封装可以让你更方便地管理手机验证码的整个流程,同时保持代码的清晰和可维护性。