Skip to content

Commit dc754ba

Browse files
Xiaoshouzi-ghpragatimodikevinthecheung
authored
Firebase Auth TOTP support (#11261)
Added Time-based One Time Password MFA support. Co-authored-by: pragatimodi <[email protected]> Co-authored-by: Kevin Cheung <[email protected]>
1 parent 7061b00 commit dc754ba

File tree

54 files changed

+1933
-263
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+1933
-263
lines changed

FirebaseAuth/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# 10.12.0
2+
- [added] Added support to Firebase Auth to enroll and sign in a user with TOTP second factor.(#11261)
3+
14
# 10.8.0
25
- [added] Added Firebase App Check support to Firebase Auth. (#11056)
36
- [added] Added Sign in with Apple token revocation support. (#9906)

FirebaseAuth/Sources/AuthProvider/Phone/FIRPhoneAuthProvider.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -528,7 +528,7 @@ - (void)verifyClientAndSendVerificationCodeToPhoneNumber:(NSString *)phoneNumber
528528
} else {
529529
if (callback) {
530530
callback(
531-
response.enrollmentResponse.sessionInfo,
531+
response.phoneSessionInfo.sessionInfo,
532532
nil);
533533
}
534534
}

FirebaseAuth/Sources/Backend/FIRAuthBackend.m

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@
7474

7575
#import "FirebaseAuth/Sources/AuthProvider/Phone/FIRPhoneAuthCredential_Internal.h"
7676
#import "FirebaseAuth/Sources/MultiFactor/Phone/FIRPhoneMultiFactorInfo+Internal.h"
77+
#import "FirebaseAuth/Sources/MultiFactor/TOTP/FIRTOTPMultiFactorInfo.h"
78+
7779
#endif
7880

7981
NS_ASSUME_NONNULL_BEGIN
@@ -798,15 +800,25 @@ - (void)verifyAssertion:(FIRVerifyAssertionRequest *)request
798800
} else {
799801
if (!response.IDToken && response.MFAInfo) {
800802
#if TARGET_OS_IOS
801-
NSMutableArray<FIRMultiFactorInfo *> *multiFactorInfo = [NSMutableArray array];
803+
NSMutableArray<FIRMultiFactorInfo *> *multiFactorInfoArray =
804+
[[NSMutableArray alloc] init];
802805
for (FIRAuthProtoMFAEnrollment *MFAEnrollment in response.MFAInfo) {
803-
FIRPhoneMultiFactorInfo *info =
804-
[[FIRPhoneMultiFactorInfo alloc] initWithProto:MFAEnrollment];
805-
[multiFactorInfo addObject:info];
806+
if (MFAEnrollment.phoneInfo) {
807+
FIRMultiFactorInfo *multiFactorInfo =
808+
[[FIRPhoneMultiFactorInfo alloc] initWithProto:MFAEnrollment];
809+
[multiFactorInfoArray addObject:multiFactorInfo];
810+
} else if (MFAEnrollment.TOTPInfo) {
811+
FIRMultiFactorInfo *multiFactorInfo =
812+
[[FIRTOTPMultiFactorInfo alloc] initWithProto:MFAEnrollment];
813+
[multiFactorInfoArray addObject:multiFactorInfo];
814+
} else {
815+
FIRLogError(kFIRLoggerAuth, @"I-AUT000020",
816+
@"Multifactor type is not supported");
817+
}
806818
}
807819
NSError *multiFactorRequiredError = [FIRAuthErrorUtils
808820
secondFactorRequiredErrorWithPendingCredential:response.MFAPendingCredential
809-
hints:multiFactorInfo
821+
hints:multiFactorInfoArray
810822
auth:request.requestConfiguration
811823
.auth];
812824
callback(nil, multiFactorRequiredError);
@@ -846,9 +858,19 @@ - (void)verifyPassword:(FIRVerifyPasswordRequest *)request
846858
#if TARGET_OS_IOS
847859
NSMutableArray<FIRMultiFactorInfo *> *multiFactorInfo = [NSMutableArray array];
848860
for (FIRAuthProtoMFAEnrollment *MFAEnrollment in response.MFAInfo) {
849-
FIRPhoneMultiFactorInfo *info =
850-
[[FIRPhoneMultiFactorInfo alloc] initWithProto:MFAEnrollment];
851-
[multiFactorInfo addObject:info];
861+
// check which MFA factors are enabled.
862+
if (MFAEnrollment.phoneInfo != nil) {
863+
FIRPhoneMultiFactorInfo *info =
864+
[[FIRPhoneMultiFactorInfo alloc] initWithProto:MFAEnrollment];
865+
[multiFactorInfo addObject:info];
866+
} else if (MFAEnrollment.TOTPInfo != nil) {
867+
FIRTOTPMultiFactorInfo *info =
868+
[[FIRTOTPMultiFactorInfo alloc] initWithProto:MFAEnrollment];
869+
[multiFactorInfo addObject:info];
870+
} else {
871+
FIRLogError(kFIRLoggerAuth, @"I-AUT000021",
872+
@"Multifactor type is not supported");
873+
}
852874
}
853875
NSError *multiFactorRequiredError = [FIRAuthErrorUtils
854876
secondFactorRequiredErrorWithPendingCredential:response.MFAPendingCredential
@@ -876,15 +898,25 @@ - (void)emailLinkSignin:(FIREmailLinkSignInRequest *)request
876898
} else {
877899
if (!response.IDToken && response.MFAInfo) {
878900
#if TARGET_OS_IOS
879-
NSMutableArray<FIRMultiFactorInfo *> *multiFactorInfo = [NSMutableArray array];
901+
NSMutableArray<FIRMultiFactorInfo *> *multiFactorInfoArray =
902+
[[NSMutableArray alloc] init];
880903
for (FIRAuthProtoMFAEnrollment *MFAEnrollment in response.MFAInfo) {
881-
FIRPhoneMultiFactorInfo *info =
882-
[[FIRPhoneMultiFactorInfo alloc] initWithProto:MFAEnrollment];
883-
[multiFactorInfo addObject:info];
904+
if (MFAEnrollment.phoneInfo) {
905+
FIRMultiFactorInfo *multiFactorInfo =
906+
[[FIRPhoneMultiFactorInfo alloc] initWithProto:MFAEnrollment];
907+
[multiFactorInfoArray addObject:multiFactorInfo];
908+
} else if (MFAEnrollment.TOTPInfo) {
909+
FIRMultiFactorInfo *multiFactorInfo =
910+
[[FIRTOTPMultiFactorInfo alloc] initWithProto:MFAEnrollment];
911+
[multiFactorInfoArray addObject:multiFactorInfo];
912+
} else {
913+
FIRLogError(kFIRLoggerAuth, @"I-AUT000022",
914+
@"Multifactor type is not supported");
915+
}
884916
}
885917
NSError *multiFactorRequiredError = [FIRAuthErrorUtils
886918
secondFactorRequiredErrorWithPendingCredential:response.MFAPendingCredential
887-
hints:multiFactorInfo
919+
hints:multiFactorInfoArray
888920
auth:request.requestConfiguration
889921
.auth];
890922
callback(nil, multiFactorRequiredError);

FirebaseAuth/Sources/Backend/RPC/MultiFactor/Enroll/FIRFinalizeMFAEnrollmentRequest.h

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#import "FirebaseAuth/Sources/Backend/FIRAuthRPCRequest.h"
1818
#import "FirebaseAuth/Sources/Backend/FIRIdentityToolkitRequest.h"
1919
#import "FirebaseAuth/Sources/Backend/RPC/Proto/Phone/FIRAuthProtoFinalizeMFAPhoneRequestInfo.h"
20+
#import "FirebaseAuth/Sources/Backend/RPC/Proto/TOTP/FIRAuthProtoFinalizeMFATOTPEnrollmentRequestInfo.h"
2021

2122
NS_ASSUME_NONNULL_BEGIN
2223

@@ -25,13 +26,21 @@ NS_ASSUME_NONNULL_BEGIN
2526
@property(nonatomic, copy, readonly, nullable) NSString *IDToken;
2627

2728
@property(nonatomic, copy, readonly, nullable) NSString *displayName;
28-
2929
@property(nonatomic, copy, readonly, nullable)
30-
FIRAuthProtoFinalizeMFAPhoneRequestInfo *verificationInfo;
30+
FIRAuthProtoFinalizeMFAPhoneRequestInfo *phoneVerificationInfo;
31+
@property(nonatomic, copy, readonly, nullable)
32+
FIRAuthProtoFinalizeMFATOTPEnrollmentRequestInfo *TOTPVerificationInfo;
33+
34+
- (nullable instancetype)initWithIDToken:(NSString *)IDToken
35+
displayName:(NSString *)displayName
36+
phoneVerificationInfo:
37+
(FIRAuthProtoFinalizeMFAPhoneRequestInfo *)phoneVerificationInfo
38+
requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration;
3139

3240
- (nullable instancetype)initWithIDToken:(NSString *)IDToken
3341
displayName:(NSString *)displayName
34-
verificationInfo:(FIRAuthProtoFinalizeMFAPhoneRequestInfo *)verificationInfo
42+
TOTPVerificationInfo:
43+
(FIRAuthProtoFinalizeMFATOTPEnrollmentRequestInfo *)TOTPVerificationInfo
3544
requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration;
3645

3746
@end

FirebaseAuth/Sources/Backend/RPC/MultiFactor/Enroll/FIRFinalizeMFAEnrollmentRequest.m

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
#import "FirebaseAuth/Sources/Backend/RPC/MultiFactor/Enroll/FIRFinalizeMFAEnrollmentRequest.h"
1818

19+
NS_ASSUME_NONNULL_BEGIN
20+
1921
static NSString *const kFinalizeMFAEnrollmentEndPoint = @"accounts/mfaEnrollment:finalize";
2022

2123
/** @var kTenantIDKey
@@ -27,7 +29,25 @@ @implementation FIRFinalizeMFAEnrollmentRequest
2729

2830
- (nullable instancetype)initWithIDToken:(NSString *)IDToken
2931
displayName:(NSString *)displayName
30-
verificationInfo:(FIRAuthProtoFinalizeMFAPhoneRequestInfo *)verificationInfo
32+
phoneVerificationInfo:
33+
(FIRAuthProtoFinalizeMFAPhoneRequestInfo *)phoneVerificationInfo
34+
requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration {
35+
self = [super initWithEndpoint:kFinalizeMFAEnrollmentEndPoint
36+
requestConfiguration:requestConfiguration
37+
useIdentityPlatform:YES
38+
useStaging:NO];
39+
if (self) {
40+
_IDToken = IDToken;
41+
_displayName = displayName;
42+
_phoneVerificationInfo = phoneVerificationInfo;
43+
}
44+
return self;
45+
}
46+
47+
- (nullable instancetype)initWithIDToken:(NSString *)IDToken
48+
displayName:(NSString *)displayName
49+
TOTPVerificationInfo:
50+
(FIRAuthProtoFinalizeMFATOTPEnrollmentRequestInfo *)TOTPVerificationInfo
3151
requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration {
3252
self = [super initWithEndpoint:kFinalizeMFAEnrollmentEndPoint
3353
requestConfiguration:requestConfiguration
@@ -36,7 +56,7 @@ - (nullable instancetype)initWithIDToken:(NSString *)IDToken
3656
if (self) {
3757
_IDToken = IDToken;
3858
_displayName = displayName;
39-
_verificationInfo = verificationInfo;
59+
_TOTPVerificationInfo = TOTPVerificationInfo;
4060
}
4161
return self;
4262
}
@@ -49,10 +69,10 @@ - (nullable id)unencodedHTTPRequestBodyWithError:(NSError *__autoreleasing _Null
4969
if (_displayName) {
5070
postBody[@"displayName"] = _displayName;
5171
}
52-
if (_verificationInfo) {
53-
if ([_verificationInfo isKindOfClass:[FIRAuthProtoFinalizeMFAPhoneRequestInfo class]]) {
54-
postBody[@"phoneVerificationInfo"] = [_verificationInfo dictionary];
55-
}
72+
if (_phoneVerificationInfo) {
73+
postBody[@"phoneVerificationInfo"] = [_phoneVerificationInfo dictionary];
74+
} else if (_TOTPVerificationInfo) {
75+
postBody[@"totpVerificationInfo"] = [_TOTPVerificationInfo dictionary];
5676
}
5777
if (self.tenantID) {
5878
postBody[kTenantIDKey] = self.tenantID;
@@ -61,3 +81,5 @@ - (nullable id)unencodedHTTPRequestBodyWithError:(NSError *__autoreleasing _Null
6181
}
6282

6383
@end
84+
85+
NS_ASSUME_NONNULL_END

FirebaseAuth/Sources/Backend/RPC/MultiFactor/Enroll/FIRFinalizeMFAEnrollmentResponse.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#import "FirebaseAuth/Sources/Backend/FIRAuthRPCResponse.h"
1818
#import "FirebaseAuth/Sources/Backend/RPC/Proto/Phone/FIRAuthProtoFinalizeMFAPhoneResponseInfo.h"
19+
#import "FirebaseAuth/Sources/Backend/RPC/Proto/TOTP/FIRAuthProtoFinalizeMFATOTPEnrollmentResponseInfo.h"
1920

2021
NS_ASSUME_NONNULL_BEGIN
2122

@@ -24,6 +25,10 @@ NS_ASSUME_NONNULL_BEGIN
2425
@property(nonatomic, copy, readonly, nullable) NSString *IDToken;
2526

2627
@property(nonatomic, copy, readonly, nullable) NSString *refreshToken;
28+
@property(nonatomic, copy, readonly, nullable)
29+
FIRAuthProtoFinalizeMFAPhoneResponseInfo *phoneAuthInfo;
30+
@property(nonatomic, copy, readonly, nullable)
31+
FIRAuthProtoFinalizeMFATOTPEnrollmentResponseInfo *TOTPAuthInfo;
2732

2833
@end
2934

FirebaseAuth/Sources/Backend/RPC/MultiFactor/Enroll/FIRFinalizeMFAEnrollmentResponse.m

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,23 @@
1515
*/
1616

1717
#import "FirebaseAuth/Sources/Backend/RPC/MultiFactor/Enroll/FIRFinalizeMFAEnrollmentResponse.h"
18-
1918
#import "FirebaseAuth/Sources/Backend/RPC/Proto/Phone/FIRAuthProtoFinalizeMFAPhoneResponseInfo.h"
19+
#import "FirebaseAuth/Sources/Backend/RPC/Proto/TOTP/FIRAuthProtoFinalizeMFATOTPEnrollmentResponseInfo.h"
2020

2121
@implementation FIRFinalizeMFAEnrollmentResponse
2222

2323
- (BOOL)setWithDictionary:(nonnull NSDictionary *)dictionary
2424
error:(NSError *__autoreleasing _Nullable *_Nullable)error {
2525
_IDToken = [dictionary[@"idToken"] copy];
2626
_refreshToken = [dictionary[@"refreshToken"] copy];
27+
if (dictionary[@"phoneAuthInfo"] != nil) {
28+
NSDictionary *data = dictionary[@"phoneAuthInfo"];
29+
_phoneAuthInfo = [[FIRAuthProtoFinalizeMFAPhoneResponseInfo alloc] initWithDictionary:data];
30+
} else if (dictionary[@"totpAuthInfo"] != nil) {
31+
NSDictionary *data = dictionary[@"totpAuthInfo"];
32+
_TOTPAuthInfo =
33+
[[FIRAuthProtoFinalizeMFATOTPEnrollmentResponseInfo alloc] initWithDictionary:data];
34+
}
2735
return YES;
2836
}
2937

FirebaseAuth/Sources/Backend/RPC/MultiFactor/Enroll/FIRStartMFAEnrollmentRequest.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,25 @@
1717
#import "FirebaseAuth/Sources/Backend/FIRAuthRPCRequest.h"
1818
#import "FirebaseAuth/Sources/Backend/FIRIdentityToolkitRequest.h"
1919
#import "FirebaseAuth/Sources/Backend/RPC/Proto/Phone/FIRAuthProtoStartMFAPhoneRequestInfo.h"
20+
#import "FirebaseAuth/Sources/Backend/RPC/Proto/TOTP/FIRAuthProtoStartMFATOTPEnrollmentRequestInfo.h"
2021

2122
NS_ASSUME_NONNULL_BEGIN
2223

2324
@interface FIRStartMFAEnrollmentRequest : FIRIdentityToolkitRequest <FIRAuthRPCRequest>
2425

2526
@property(nonatomic, copy, readonly, nullable) NSString *IDToken;
27+
@property(nonatomic, copy, readonly, nullable)
28+
FIRAuthProtoStartMFAPhoneRequestInfo *phoneEnrollmentInfo;
29+
@property(nonatomic, copy, readonly, nullable)
30+
FIRAuthProtoStartMFATOTPEnrollmentRequestInfo *TOTPEnrollmentInfo;
2631

27-
@property(nonatomic, copy, readonly, nullable) FIRAuthProtoStartMFAPhoneRequestInfo *enrollmentInfo;
32+
- (nullable instancetype)initWithIDToken:(NSString *)IDToken
33+
enrollmentInfo:(FIRAuthProtoStartMFAPhoneRequestInfo *)phoneEnrollmentInfo
34+
requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration;
2835

2936
- (nullable instancetype)initWithIDToken:(NSString *)IDToken
30-
enrollmentInfo:(FIRAuthProtoStartMFAPhoneRequestInfo *)enrollmentInfo
37+
TOTPEnrollmentInfo:
38+
(FIRAuthProtoStartMFATOTPEnrollmentRequestInfo *)TOTPEnrollmentInfo
3139
requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration;
3240

3341
@end

FirebaseAuth/Sources/Backend/RPC/MultiFactor/Enroll/FIRStartMFAEnrollmentRequest.m

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
*/
1616

1717
#import "FirebaseAuth/Sources/Backend/RPC/MultiFactor/Enroll/FIRStartMFAEnrollmentRequest.h"
18-
1918
#import "FirebaseAuth/Sources/Backend/RPC/Proto/Phone/FIRAuthProtoStartMFAPhoneRequestInfo.h"
19+
#import "FirebaseAuth/Sources/Backend/RPC/Proto/TOTP/FIRAuthProtoStartMFATOTPEnrollmentRequestInfo.h"
2020

2121
static NSString *const kStartMFAEnrollmentEndPoint = @"accounts/mfaEnrollment:start";
2222

@@ -28,15 +28,30 @@
2828
@implementation FIRStartMFAEnrollmentRequest
2929

3030
- (nullable instancetype)initWithIDToken:(NSString *)IDToken
31-
enrollmentInfo:(FIRAuthProtoStartMFAPhoneRequestInfo *)enrollmentInfo
31+
enrollmentInfo:(FIRAuthProtoStartMFAPhoneRequestInfo *)phoneEnrollmentInfo
32+
requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration {
33+
self = [super initWithEndpoint:kStartMFAEnrollmentEndPoint
34+
requestConfiguration:requestConfiguration
35+
useIdentityPlatform:YES
36+
useStaging:NO];
37+
if (self) {
38+
_IDToken = IDToken;
39+
_phoneEnrollmentInfo = phoneEnrollmentInfo;
40+
}
41+
return self;
42+
}
43+
44+
- (nullable instancetype)initWithIDToken:(NSString *)IDToken
45+
TOTPEnrollmentInfo:
46+
(FIRAuthProtoStartMFATOTPEnrollmentRequestInfo *)TOTPEnrollmentInfo
3247
requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration {
3348
self = [super initWithEndpoint:kStartMFAEnrollmentEndPoint
3449
requestConfiguration:requestConfiguration
3550
useIdentityPlatform:YES
3651
useStaging:NO];
3752
if (self) {
3853
_IDToken = IDToken;
39-
_enrollmentInfo = enrollmentInfo;
54+
_TOTPEnrollmentInfo = TOTPEnrollmentInfo;
4055
}
4156
return self;
4257
}
@@ -46,10 +61,10 @@ - (nullable id)unencodedHTTPRequestBodyWithError:(NSError *__autoreleasing _Null
4661
if (_IDToken) {
4762
postBody[@"idToken"] = _IDToken;
4863
}
49-
if (_enrollmentInfo) {
50-
if ([_enrollmentInfo isKindOfClass:[FIRAuthProtoStartMFAPhoneRequestInfo class]]) {
51-
postBody[@"phoneEnrollmentInfo"] = [_enrollmentInfo dictionary];
52-
}
64+
if (_phoneEnrollmentInfo) {
65+
postBody[@"phoneEnrollmentInfo"] = [_phoneEnrollmentInfo dictionary];
66+
} else if (_TOTPEnrollmentInfo) {
67+
postBody[@"totpEnrollmentInfo"] = [_TOTPEnrollmentInfo dictionary];
5368
}
5469
if (self.tenantID) {
5570
postBody[kTenantIDKey] = self.tenantID;

FirebaseAuth/Sources/Backend/RPC/MultiFactor/Enroll/FIRStartMFAEnrollmentResponse.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@
1616

1717
#import "FirebaseAuth/Sources/Backend/FIRAuthRPCResponse.h"
1818
#import "FirebaseAuth/Sources/Backend/RPC/Proto/Phone/FIRAuthProtoStartMFAPhoneResponseInfo.h"
19+
#import "FirebaseAuth/Sources/Backend/RPC/Proto/TOTP/FIRAuthProtoStartMFATOTPEnrollmentResponseInfo.h"
1920

2021
NS_ASSUME_NONNULL_BEGIN
2122

2223
@interface FIRStartMFAEnrollmentResponse : NSObject <FIRAuthRPCResponse>
23-
2424
@property(nonatomic, copy, readonly, nullable)
25-
FIRAuthProtoStartMFAPhoneResponseInfo *enrollmentResponse;
25+
FIRAuthProtoStartMFAPhoneResponseInfo *phoneSessionInfo;
26+
@property(nonatomic, copy, readonly, nullable)
27+
FIRAuthProtoStartMFATOTPEnrollmentResponseInfo *TOTPSessionInfo;
2628

2729
@end
2830

0 commit comments

Comments
 (0)