Firebase Authentication 的 iOS SDK 进行手机号认证

完整的验证流程示例:

@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
根据实际需求修改手机号和验证码的验证规则
可以根据需要添加超时处理和重试机制
建议添加网络状态检查
可以添加日志记录功能
可以添加更多的错误处理场景
这个封装可以让你更方便地管理手机验证码的整个流程,同时保持代码的清晰和可维护性。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容