FIROAuthProvider.m 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import "FirebaseAuth/Sources/Public/FirebaseAuth/FIROAuthProvider.h"
  17. #include <CommonCrypto/CommonCrypto.h>
  18. #import "FirebaseAuth/Sources/Public/FirebaseAuth/FIRFacebookAuthProvider.h"
  19. #import "FirebaseAuth/Sources/Public/FirebaseAuth/FIROAuthCredential.h"
  20. #import "FirebaseCore/Extension/FirebaseCoreInternal.h"
  21. #import "FirebaseAppCheck/Interop/FIRAppCheckTokenResultInterop.h"
  22. #import "FirebaseAuth/Sources/Auth/FIRAuthGlobalWorkQueue.h"
  23. #import "FirebaseAuth/Sources/Auth/FIRAuth_Internal.h"
  24. #import "FirebaseAuth/Sources/AuthProvider/OAuth/FIROAuthCredential_Internal.h"
  25. #import "FirebaseAuth/Sources/Backend/FIRAuthBackend.h"
  26. #import "FirebaseAuth/Sources/Backend/FIRAuthRequestConfiguration.h"
  27. #import "FirebaseAuth/Sources/Utilities/FIRAuthErrorUtils.h"
  28. #import "FirebaseAuth/Sources/Utilities/FIRAuthWebUtils.h"
  29. #if TARGET_OS_IOS
  30. #import "FirebaseAuth/Sources/Utilities/FIRAuthURLPresenter.h"
  31. #endif
  32. NS_ASSUME_NONNULL_BEGIN
  33. /** @typedef FIRHeadfulLiteURLCallBack
  34. @brief The callback invoked at the end of the flow to fetch a headful-lite URL.
  35. @param headfulLiteURL The headful lite URL.
  36. @param error The error that occurred while fetching the headful-lite, if any.
  37. */
  38. typedef void (^FIRHeadfulLiteURLCallBack)(NSURL *_Nullable headfulLiteURL,
  39. NSError *_Nullable error);
  40. /** @var kHeadfulLiteURLStringFormat
  41. @brief The format of the URL used to open the headful lite page during sign-in.
  42. */
  43. NSString *const kHeadfulLiteURLStringFormat = @"https://%@/__/auth/handler?%@";
  44. /** @var kHeadfulLiteEmulatorURLStringFormat
  45. @brief The format of the URL used to open the emulated headful lite page during sign-in.
  46. */
  47. NSString *const kHeadfulLiteEmulatorURLStringFormat = @"http://%@/emulator/auth/handler?%@";
  48. /** @var kAppCheckURLFragmentKey
  49. @brief The format of passing appCheck token as a URL fragment.
  50. */
  51. NSString *const kAppCheckURLFragmentKey = @"fac=";
  52. /** @var kauthTypeSignInWithRedirect
  53. @brief The auth type to be specified in the sign-in request with redirect request and response.
  54. */
  55. static NSString *const kAuthTypeSignInWithRedirect = @"signInWithRedirect";
  56. /** @var kCustomUrlSchemePrefix
  57. @brief The prefix to append to the Firebase app ID custom callback scheme..
  58. */
  59. static NSString *const kCustomUrlSchemePrefix = @"app-";
  60. @implementation FIROAuthProvider {
  61. /** @var _auth
  62. @brief The auth instance used for launching the URL presenter.
  63. */
  64. FIRAuth *_auth;
  65. /** @var _callbackScheme
  66. @brief The callback URL scheme used for headful-lite sign-in.
  67. */
  68. NSString *_callbackScheme;
  69. /** @var _usingClientIDScheme
  70. @brief True if the reverse client ID is registered as a custom URL scheme, and false
  71. otherwise.
  72. */
  73. BOOL _usingClientIDScheme;
  74. }
  75. + (FIROAuthCredential *)credentialWithProviderID:(NSString *)providerID
  76. IDToken:(NSString *)IDToken
  77. accessToken:(nullable NSString *)accessToken {
  78. return [[FIROAuthCredential alloc] initWithProviderID:providerID
  79. IDToken:IDToken
  80. rawNonce:nil
  81. accessToken:accessToken
  82. secret:nil
  83. pendingToken:nil];
  84. }
  85. + (FIROAuthCredential *)credentialWithProviderID:(NSString *)providerID
  86. accessToken:(NSString *)accessToken {
  87. return [[FIROAuthCredential alloc] initWithProviderID:providerID
  88. IDToken:nil
  89. rawNonce:nil
  90. accessToken:accessToken
  91. secret:nil
  92. pendingToken:nil];
  93. }
  94. + (FIROAuthCredential *)credentialWithProviderID:(NSString *)providerID
  95. IDToken:(NSString *)IDToken
  96. rawNonce:(nullable NSString *)rawNonce
  97. accessToken:(nullable NSString *)accessToken {
  98. return [[FIROAuthCredential alloc] initWithProviderID:providerID
  99. IDToken:IDToken
  100. rawNonce:rawNonce
  101. accessToken:accessToken
  102. secret:nil
  103. pendingToken:nil];
  104. }
  105. + (FIROAuthCredential *)credentialWithProviderID:(NSString *)providerID
  106. IDToken:(NSString *)IDToken
  107. rawNonce:(nullable NSString *)rawNonce {
  108. return [[FIROAuthCredential alloc] initWithProviderID:providerID
  109. IDToken:IDToken
  110. rawNonce:rawNonce
  111. accessToken:nil
  112. secret:nil
  113. pendingToken:nil];
  114. }
  115. + (instancetype)providerWithProviderID:(NSString *)providerID {
  116. return [[self alloc] initWithProviderID:providerID auth:[FIRAuth auth]];
  117. }
  118. + (instancetype)providerWithProviderID:(NSString *)providerID auth:(FIRAuth *)auth {
  119. return [[self alloc] initWithProviderID:providerID auth:auth];
  120. }
  121. #if TARGET_OS_IOS
  122. - (void)getCredentialWithUIDelegate:(nullable id<FIRAuthUIDelegate>)UIDelegate
  123. completion:(nullable FIRAuthCredentialCallback)completion {
  124. if (![FIRAuthWebUtils isCallbackSchemeRegisteredForCustomURLScheme:self->_callbackScheme]) {
  125. [NSException raise:NSInternalInconsistencyException
  126. format:@"Please register custom URL scheme '%@' in the app's Info.plist file.",
  127. self->_callbackScheme];
  128. }
  129. __weak __typeof__(self) weakSelf = self;
  130. __weak FIRAuth *weakAuth = _auth;
  131. __weak NSString *weakProviderID = _providerID;
  132. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  133. FIRAuthCredentialCallback callbackOnMainThread =
  134. ^(FIRAuthCredential *_Nullable credential, NSError *_Nullable error) {
  135. if (completion) {
  136. dispatch_async(dispatch_get_main_queue(), ^{
  137. completion(credential, error);
  138. });
  139. }
  140. };
  141. NSString *eventID = [FIRAuthWebUtils randomStringWithLength:10];
  142. NSString *sessionID = [FIRAuthWebUtils randomStringWithLength:10];
  143. __strong __typeof__(self) strongSelf = weakSelf;
  144. [strongSelf
  145. getHeadFulLiteURLWithEventID:eventID
  146. sessionID:sessionID
  147. completion:^(NSURL *_Nullable headfulLiteURL, NSError *_Nullable error) {
  148. if (error) {
  149. callbackOnMainThread(nil, error);
  150. return;
  151. }
  152. FIRAuthURLCallbackMatcher callbackMatcher =
  153. ^BOOL(NSURL *_Nullable callbackURL) {
  154. return [FIRAuthWebUtils
  155. isExpectedCallbackURL:callbackURL
  156. eventID:eventID
  157. authType:kAuthTypeSignInWithRedirect
  158. callbackScheme:strongSelf->_callbackScheme];
  159. };
  160. __strong FIRAuth *strongAuth = weakAuth;
  161. [strongAuth.authURLPresenter
  162. presentURL:headfulLiteURL
  163. UIDelegate:UIDelegate
  164. callbackMatcher:callbackMatcher
  165. completion:^(NSURL *_Nullable callbackURL,
  166. NSError *_Nullable error) {
  167. if (error) {
  168. callbackOnMainThread(nil, error);
  169. return;
  170. }
  171. NSString *OAuthResponseURLString =
  172. [strongSelf OAuthResponseForURL:callbackURL
  173. error:&error];
  174. if (error) {
  175. callbackOnMainThread(nil, error);
  176. return;
  177. }
  178. __strong NSString *strongProviderID = weakProviderID;
  179. FIROAuthCredential *credential = [[FIROAuthCredential alloc]
  180. initWithProviderID:strongProviderID
  181. sessionID:sessionID
  182. OAuthResponseURLString:OAuthResponseURLString];
  183. callbackOnMainThread(credential, nil);
  184. }];
  185. }];
  186. });
  187. }
  188. #endif // TARGET_OS_IOS
  189. #pragma mark - Internal Methods
  190. /** @fn initWithProviderID:auth:
  191. @brief returns an instance of @c FIROAuthProvider associated with the provided auth instance.
  192. @param auth The Auth instance to be associated with the OAuthProvider instance.
  193. @return An Instance of @c FIROAuthProvider.
  194. */
  195. - (nullable instancetype)initWithProviderID:(NSString *)providerID auth:(FIRAuth *)auth {
  196. if (!auth.requestConfiguration.emulatorHostAndPort) {
  197. NSAssert(![providerID isEqual:FIRFacebookAuthProviderID],
  198. @"Sign in with Facebook is not supported via generic IDP; the Facebook TOS "
  199. "dictate that you must use the Facebook iOS SDK for Facebook login.");
  200. NSAssert(![providerID isEqual:@"apple.com"],
  201. @"Sign in with Apple is not supported via generic IDP; You must use the Apple iOS SDK"
  202. " for Sign in with Apple.");
  203. }
  204. self = [super init];
  205. if (self) {
  206. _auth = auth;
  207. _providerID = providerID;
  208. if (_auth.app.options.clientID) {
  209. NSString *reverseClientIDScheme =
  210. [[[_auth.app.options.clientID componentsSeparatedByString:@"."]
  211. reverseObjectEnumerator].allObjects componentsJoinedByString:@"."];
  212. if ([FIRAuthWebUtils isCallbackSchemeRegisteredForCustomURLScheme:reverseClientIDScheme]) {
  213. _callbackScheme = reverseClientIDScheme;
  214. _usingClientIDScheme = YES;
  215. }
  216. }
  217. if (!_usingClientIDScheme) {
  218. _callbackScheme = [kCustomUrlSchemePrefix
  219. stringByAppendingString:[_auth.app.options.googleAppID
  220. stringByReplacingOccurrencesOfString:@":"
  221. withString:@"-"]];
  222. }
  223. }
  224. return self;
  225. }
  226. /** @fn OAuthResponseForURL:error:
  227. @brief Parses the redirected URL and returns a string representation of the OAuth response URL.
  228. @param URL The url to be parsed for an OAuth response URL.
  229. @param error The error that occurred if any.
  230. @return The OAuth response if successful.
  231. */
  232. - (nullable NSString *)OAuthResponseForURL:(NSURL *)URL error:(NSError *_Nullable *_Nullable)error {
  233. NSDictionary<NSString *, NSString *> *URLQueryItems =
  234. [FIRAuthWebUtils dictionaryWithHttpArgumentsString:URL.query];
  235. NSURL *deepLinkURL = [NSURL URLWithString:URLQueryItems[@"deep_link_id"]];
  236. URLQueryItems = [FIRAuthWebUtils dictionaryWithHttpArgumentsString:deepLinkURL.query];
  237. NSString *queryItemLink = URLQueryItems[@"link"];
  238. if (queryItemLink) {
  239. return queryItemLink;
  240. }
  241. if (!error) {
  242. return nil;
  243. }
  244. NSData *errorData = [URLQueryItems[@"firebaseError"] dataUsingEncoding:NSUTF8StringEncoding];
  245. NSError *jsonError;
  246. NSDictionary *errorDict = [NSJSONSerialization JSONObjectWithData:errorData
  247. options:0
  248. error:&jsonError];
  249. if (jsonError) {
  250. *error = [FIRAuthErrorUtils JSONSerializationErrorWithUnderlyingError:jsonError];
  251. return nil;
  252. }
  253. *error = [FIRAuthErrorUtils URLResponseErrorWithCode:errorDict[@"code"]
  254. message:errorDict[@"message"]];
  255. if (!*error) {
  256. NSString *reason;
  257. if (errorDict[@"code"] && errorDict[@"message"]) {
  258. reason = [NSString stringWithFormat:@"[%@] - %@", errorDict[@"code"], errorDict[@"message"]];
  259. }
  260. *error = [FIRAuthErrorUtils webSignInUserInteractionFailureWithReason:reason];
  261. }
  262. return nil;
  263. }
  264. /** @fn getHeadFulLiteURLWithEventID:completion:
  265. @brief Constructs a URL used for opening a headful-lite flow using a given event
  266. ID and session ID.
  267. @param eventID The event ID used for this purpose.
  268. @param sessionID The session ID used when completing the headful lite flow.
  269. @param completion The callback invoked after the URL has been constructed or an error
  270. has been encountered.
  271. */
  272. - (void)getHeadFulLiteURLWithEventID:(NSString *)eventID
  273. sessionID:(NSString *)sessionID
  274. completion:(FIRHeadfulLiteURLCallBack)completion {
  275. __weak __typeof__(self) weakSelf = self;
  276. [FIRAuthWebUtils
  277. fetchAuthDomainWithRequestConfiguration:_auth.requestConfiguration
  278. completion:^(NSString *_Nullable authDomain,
  279. NSError *_Nullable error) {
  280. if (error) {
  281. if (completion) {
  282. completion(nil, error);
  283. }
  284. return;
  285. }
  286. __strong __typeof__(self) strongSelf = weakSelf;
  287. NSString *bundleID = [NSBundle mainBundle].bundleIdentifier;
  288. NSString *clientID = strongSelf->_auth.app.options.clientID;
  289. NSString *appID = strongSelf->_auth.app.options.googleAppID;
  290. NSString *apiKey =
  291. strongSelf->_auth.requestConfiguration.APIKey;
  292. NSString *tenantID = strongSelf->_auth.tenantID;
  293. id<FIRAppCheckInterop> appCheck =
  294. strongSelf->_auth.requestConfiguration.appCheck;
  295. NSMutableDictionary *urlArguments = [@{
  296. @"apiKey" : apiKey,
  297. @"authType" : kAuthTypeSignInWithRedirect,
  298. @"ibi" : bundleID ?: @"",
  299. @"sessionId" : [strongSelf hashforString:sessionID],
  300. @"v" : [FIRAuthBackend authUserAgent],
  301. @"eventId" : eventID,
  302. @"providerId" : strongSelf->_providerID,
  303. } mutableCopy];
  304. if (strongSelf->_usingClientIDScheme) {
  305. urlArguments[@"clientId"] = clientID;
  306. } else {
  307. urlArguments[@"appId"] = appID;
  308. }
  309. if (tenantID) {
  310. urlArguments[@"tid"] = tenantID;
  311. }
  312. if (strongSelf.scopes.count) {
  313. urlArguments[@"scopes"] =
  314. [strongSelf.scopes componentsJoinedByString:@","];
  315. }
  316. if (strongSelf.customParameters.count) {
  317. NSString *customParameters =
  318. [strongSelf customParametersStringWithError:&error];
  319. if (error) {
  320. completion(nil, error);
  321. return;
  322. }
  323. if (customParameters) {
  324. urlArguments[@"customParameters"] = customParameters;
  325. }
  326. }
  327. if (strongSelf->_auth.requestConfiguration.languageCode) {
  328. urlArguments[@"hl"] =
  329. strongSelf->_auth.requestConfiguration.languageCode;
  330. }
  331. NSString *argumentsString = [strongSelf
  332. httpArgumentsStringForArgsDictionary:urlArguments];
  333. NSString *URLString;
  334. if (strongSelf->_auth.requestConfiguration
  335. .emulatorHostAndPort) {
  336. URLString = [NSString
  337. stringWithFormat:kHeadfulLiteEmulatorURLStringFormat,
  338. authDomain, argumentsString];
  339. } else {
  340. URLString =
  341. [NSString stringWithFormat:kHeadfulLiteURLStringFormat,
  342. authDomain, argumentsString];
  343. }
  344. if (appCheck) {
  345. [appCheck
  346. getTokenForcingRefresh:false
  347. completion:^(
  348. id<FIRAppCheckTokenResultInterop> _Nonnull tokenResult) {
  349. if (tokenResult.error) {
  350. FIRLogWarning(
  351. kFIRLoggerAuth, @"I-AUT000018",
  352. @"Error getting App Check token; "
  353. @"using placeholder token "
  354. @"instead. Error: %@",
  355. tokenResult.error);
  356. }
  357. NSString *appCheckTokenFragments =
  358. [kAppCheckURLFragmentKey
  359. stringByAppendingString:
  360. tokenResult.token];
  361. NSString *URLStringWithAppCheckToken =
  362. [URLString stringByAppendingString:
  363. appCheckTokenFragments];
  364. if (completion) {
  365. NSCharacterSet *set = [NSCharacterSet
  366. URLFragmentAllowedCharacterSet];
  367. completion(
  368. [NSURL
  369. URLWithString:
  370. [URLStringWithAppCheckToken
  371. stringByAddingPercentEncodingWithAllowedCharacters:
  372. set]],
  373. nil);
  374. }
  375. NSLog(@"appCheck token: %@",
  376. URLStringWithAppCheckToken);
  377. }];
  378. } else {
  379. NSLog(@"URLString with appCheckTokenFragment: %@",
  380. URLString);
  381. if (completion) {
  382. NSCharacterSet *set =
  383. [NSCharacterSet URLFragmentAllowedCharacterSet];
  384. completion(
  385. [NSURL
  386. URLWithString:
  387. [URLString
  388. stringByAddingPercentEncodingWithAllowedCharacters:
  389. set]],
  390. nil);
  391. }
  392. }
  393. }];
  394. }
  395. /** @fn customParametersString
  396. @brief Returns a JSON string representation of the custom parameters dictionary corresponding
  397. to the OAuthProvider.
  398. @return The JSON string representation of the custom parameters dictionary corresponding
  399. to the OAuthProvider.
  400. */
  401. - (nullable NSString *)customParametersStringWithError:(NSError *_Nullable *_Nullable)error {
  402. if (!_customParameters.count) {
  403. return nil;
  404. }
  405. if (!error) {
  406. return nil;
  407. }
  408. NSError *jsonError;
  409. NSData *customParametersJSONData = [NSJSONSerialization dataWithJSONObject:_customParameters
  410. options:0
  411. error:&jsonError];
  412. if (jsonError) {
  413. *error = [FIRAuthErrorUtils JSONSerializationErrorWithUnderlyingError:jsonError];
  414. return nil;
  415. }
  416. NSString *customParamsRawJSON = [[NSString alloc] initWithData:customParametersJSONData
  417. encoding:NSUTF8StringEncoding];
  418. return customParamsRawJSON;
  419. }
  420. /** @fn hashforString:
  421. @brief Returns the SHA256 hash representation of a given string object.
  422. @param string The string for which a SHA256 hash is desired.
  423. @return An hexadecimal string representation of the SHA256 hash.
  424. */
  425. - (NSString *)hashforString:(NSString *)string {
  426. NSData *sessionIDData = [string dataUsingEncoding:NSUTF8StringEncoding];
  427. NSMutableData *hashOutputData = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH];
  428. if (CC_SHA256(sessionIDData.bytes, (CC_LONG)[sessionIDData length],
  429. hashOutputData.mutableBytes)) {
  430. }
  431. return [self hexStringFromData:hashOutputData];
  432. ;
  433. }
  434. /** @fn hexStringFromData:
  435. @brief Returns the hexadecimal string representation of an NSData object.
  436. @param data The NSData object for which a hexadecical string is desired.
  437. @return The hexadecimal string representation of the supplied NSData object.
  438. */
  439. - (NSString *)hexStringFromData:(NSData *)data {
  440. const unsigned char *dataBuffer = (const unsigned char *)[data bytes];
  441. NSMutableString *string = [[NSMutableString alloc] init];
  442. for (unsigned int i = 0; i < data.length; i++) {
  443. [string appendFormat:@"%02lx", (unsigned long)dataBuffer[i]];
  444. }
  445. return [string copy];
  446. }
  447. - (NSString *)httpArgumentsStringForArgsDictionary:(NSDictionary *)argsDictionary {
  448. NSMutableArray *arguments = [NSMutableArray arrayWithCapacity:argsDictionary.count];
  449. NSString *key;
  450. for (key in argsDictionary) {
  451. NSString *description = [argsDictionary[key] description];
  452. [arguments
  453. addObject:[NSString
  454. stringWithFormat:@"%@=%@",
  455. [FIRAuthWebUtils stringByUnescapingFromURLArgument:key],
  456. [FIRAuthWebUtils
  457. stringByUnescapingFromURLArgument:description]]];
  458. }
  459. return [arguments componentsJoinedByString:@"&"];
  460. }
  461. @end
  462. NS_ASSUME_NONNULL_END