MainViewController+OAuth.m 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. /*
  2. * Copyright 2019 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 CommonCrypto;
  17. #import "MainViewController+OAuth.h"
  18. #import <AuthenticationServices/AuthenticationServices.h>
  19. #import "AppManager.h"
  20. #import <FirebaseAuth/FIROAuthProvider.h>
  21. #import "MainViewController+Internal.h"
  22. NS_ASSUME_NONNULL_BEGIN
  23. @interface MainViewController () <ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding>
  24. @end
  25. @implementation MainViewController (OAuth)
  26. - (StaticContentTableViewSection *)oAuthSection {
  27. __weak typeof(self) weakSelf = self;
  28. return [StaticContentTableViewSection sectionWithTitle:@"OAuth" cells:@[
  29. [StaticContentTableViewCell cellWithTitle:@"Sign in with Google"
  30. action:^{ [weakSelf signInGoogleHeadfulLite]; }],
  31. [StaticContentTableViewCell cellWithTitle:@"Link with Google"
  32. action:^{ [weakSelf linkWithGoogleHeadfulLite]; }],
  33. [StaticContentTableViewCell cellWithTitle:@"Reauthenticate with Google"
  34. action:^{ [weakSelf reauthenticateWithGoogleHeadfulLite]; }],
  35. [StaticContentTableViewCell cellWithTitle:@"Sign in with Apple"
  36. action:^{ [weakSelf signInWithApple]; }],
  37. [StaticContentTableViewCell cellWithTitle:@"Link with Apple"
  38. action:^{ [weakSelf linkWithApple]; }],
  39. [StaticContentTableViewCell cellWithTitle:@"Unlink with Apple"
  40. action:^{ [weakSelf unlinkFromProvider:@"apple.com" completion:nil]; }],
  41. [StaticContentTableViewCell cellWithTitle:@"Reauthenticate with Apple"
  42. action:^{ [weakSelf reauthenticateWithApple]; }],
  43. [StaticContentTableViewCell cellWithTitle:@"Sign in with Twitter"
  44. action:^{ [weakSelf signInTwitterHeadfulLite]; }],
  45. [StaticContentTableViewCell cellWithTitle:@"Sign in with GitHub"
  46. action:^{ [weakSelf signInGitHubHeadfulLite]; }],
  47. [StaticContentTableViewCell cellWithTitle:@"Sign in with GitHub (Access token)"
  48. action:^{ [weakSelf signInWithGitHub]; }],
  49. [StaticContentTableViewCell cellWithTitle:@"Sign in with Microsoft"
  50. action:^{ [weakSelf signInMicrosoftHeadfulLite]; }],
  51. [StaticContentTableViewCell cellWithTitle:@"Sign in with Yahoo"
  52. action:^{ [weakSelf signInYahooHeadfulLite]; }],
  53. [StaticContentTableViewCell cellWithTitle:@"Sign in with Linkedin"
  54. action:^{ [weakSelf signInLinkedinHeadfulLite]; }],
  55. ]];
  56. }
  57. - (void)signInGoogleHeadfulLite {
  58. FIROAuthProvider *provider = self.googleOAuthProvider;
  59. provider.customParameters = @{
  60. @"prompt" : @"consent",
  61. };
  62. provider.scopes = @[ @"profile", @"email", @"https://www.googleapis.com/auth/plus.me" ];
  63. [self showSpinner:^{
  64. [[AppManager auth] signInWithProvider:provider
  65. UIDelegate:nil
  66. completion:^(FIRAuthDataResult *_Nullable authResult,
  67. NSError *_Nullable error) {
  68. [self hideSpinner:^{
  69. if (error) {
  70. [self logFailure:@"sign-in with provider (Google) failed" error:error];
  71. } else if (authResult.additionalUserInfo) {
  72. [self logSuccess:[self stringWithAdditionalUserInfo:authResult.additionalUserInfo]];
  73. if (self.isNewUserToggleOn) {
  74. NSString *newUserString = authResult.additionalUserInfo.newUser ?
  75. @"New user" : @"Existing user";
  76. [self showMessagePromptWithTitle:@"New or Existing"
  77. message:newUserString
  78. showCancelButton:NO
  79. completion:nil];
  80. }
  81. }
  82. [self showTypicalUIForUserUpdateResultsWithTitle:@"Sign-In Error" error:error];
  83. }];
  84. }];
  85. }];
  86. }
  87. - (void)linkWithGoogleHeadfulLite {
  88. FIROAuthProvider *provider = self.googleOAuthProvider;
  89. provider.customParameters = @{
  90. @"prompt" : @"consent",
  91. };
  92. provider.scopes = @[ @"profile", @"email", @"https://www.googleapis.com/auth/plus.me" ];
  93. [self showSpinner:^{
  94. [[AppManager auth].currentUser linkWithProvider:provider
  95. UIDelegate:nil
  96. completion:^(FIRAuthDataResult *_Nullable authResult,
  97. NSError *_Nullable error) {
  98. [self hideSpinner:^{
  99. if (error) {
  100. [self logFailure:@"Reauthenticate with provider (Google) failed" error:error];
  101. } else if (authResult.additionalUserInfo) {
  102. [self logSuccess:[self stringWithAdditionalUserInfo:authResult.additionalUserInfo]];
  103. if (self.isNewUserToggleOn) {
  104. NSString *newUserString = authResult.additionalUserInfo.newUser ?
  105. @"New user" : @"Existing user";
  106. [self showMessagePromptWithTitle:@"New or Existing"
  107. message:newUserString
  108. showCancelButton:NO
  109. completion:nil];
  110. }
  111. }
  112. [self showTypicalUIForUserUpdateResultsWithTitle:@"Link Error" error:error];
  113. }];
  114. }];
  115. }];
  116. }
  117. - (void)reauthenticateWithGoogleHeadfulLite {
  118. FIROAuthProvider *provider = self.googleOAuthProvider;
  119. provider.customParameters = @{
  120. @"prompt" : @"consent",
  121. };
  122. provider.scopes = @[ @"profile", @"email", @"https://www.googleapis.com/auth/plus.me" ];
  123. [self showSpinner:^{
  124. [[AppManager auth].currentUser reauthenticateWithProvider:provider
  125. UIDelegate:nil
  126. completion:^(FIRAuthDataResult *_Nullable authResult,
  127. NSError *_Nullable error) {
  128. [self hideSpinner:^{
  129. if (error) {
  130. [self logFailure:@"Link with provider (Google) failed" error:error];
  131. } else if (authResult.additionalUserInfo) {
  132. [self logSuccess:[self stringWithAdditionalUserInfo:authResult.additionalUserInfo]];
  133. if (self.isNewUserToggleOn) {
  134. NSString *newUserString = authResult.additionalUserInfo.newUser ?
  135. @"New user" : @"Existing user";
  136. [self showMessagePromptWithTitle:@"New or Existing"
  137. message:newUserString
  138. showCancelButton:NO
  139. completion:nil];
  140. }
  141. }
  142. [self showTypicalUIForUserUpdateResultsWithTitle:@"Reauthenticate Error" error:error];
  143. }];
  144. }];
  145. }];
  146. }
  147. - (void)signInTwitterHeadfulLite {
  148. FIROAuthProvider *provider = self.twitterOAuthProvider;
  149. [self showSpinner:^{
  150. [provider getCredentialWithUIDelegate:nil completion:^(FIRAuthCredential *_Nullable credential,
  151. NSError *_Nullable error) {
  152. if (error) {
  153. [self logFailure:@"sign-in with Twitter failed" error:error];
  154. return;
  155. }
  156. [[AppManager auth] signInWithCredential:credential
  157. completion:^(FIRAuthDataResult *_Nullable
  158. authResult,
  159. NSError *_Nullable error) {
  160. [self hideSpinner:^{
  161. if (error) {
  162. [self logFailure:@"sign-in with Twitter (headful-lite) failed" error:error];
  163. return;
  164. } else {
  165. [self logSuccess:@"sign-in with Twitter (headful-lite) succeeded."];
  166. }
  167. [self showTypicalUIForUserUpdateResultsWithTitle:@"Sign-In Error" error:error];
  168. }];
  169. }];
  170. }];
  171. }];
  172. }
  173. - (void)signInWithGitHub {
  174. [self showTextInputPromptWithMessage:@"GitHub Access Token:"
  175. completionBlock:^(BOOL userPressedOK, NSString *_Nullable accessToken) {
  176. if (!userPressedOK || !accessToken.length) {
  177. return;
  178. }
  179. FIROAuthCredential *credential =
  180. [FIROAuthProvider credentialWithProviderID:FIRGitHubAuthProviderID accessToken:accessToken];
  181. if (credential) {
  182. [[AppManager auth] signInWithCredential:credential
  183. completion:^(FIRAuthDataResult *_Nullable result,
  184. NSError *_Nullable error) {
  185. if (error) {
  186. [self logFailure:@"sign-in with provider failed" error:error];
  187. } else {
  188. [self logSuccess:@"sign-in with provider succeeded."];
  189. }
  190. [self showTypicalUIForUserUpdateResultsWithTitle:@"Sign-In" error:error];
  191. }];
  192. }
  193. }];
  194. }
  195. - (void)signInGitHubHeadfulLite {
  196. FIROAuthProvider *provider = self.gitHubOAuthProvider;
  197. [self showSpinner:^{
  198. [provider getCredentialWithUIDelegate:nil completion:^(FIRAuthCredential *_Nullable credential,
  199. NSError *_Nullable error) {
  200. if (error) {
  201. [self logFailure:@"sign-in with GitHub failed" error:error];
  202. return;
  203. }
  204. [[AppManager auth] signInWithCredential:credential
  205. completion:^(FIRAuthDataResult *_Nullable
  206. authResult,
  207. NSError *_Nullable error) {
  208. [self hideSpinner:^{
  209. if (error) {
  210. [self logFailure:@"sign-in with GitHub (headful-lite) failed" error:error];
  211. return;
  212. } else {
  213. [self logSuccess:@"sign-in with GitHub (headful-lite) succeeded."];
  214. }
  215. [self showTypicalUIForUserUpdateResultsWithTitle:@"Sign-In Error" error:error];
  216. }];
  217. }];
  218. }];
  219. }];
  220. }
  221. - (void)signInLinkedinHeadfulLite {
  222. FIROAuthProvider *provider = self.linkedinOAuthProvider;
  223. [self showSpinner:^{
  224. [provider getCredentialWithUIDelegate:nil completion:^(FIRAuthCredential *_Nullable credential,
  225. NSError *_Nullable error) {
  226. if (error) {
  227. [self logFailure:@"sign-in with Linkedin failed" error:error];
  228. return;
  229. }
  230. [[AppManager auth] signInWithCredential:credential
  231. completion:^(FIRAuthDataResult *_Nullable
  232. authResult,
  233. NSError *_Nullable error) {
  234. [self hideSpinner:^{
  235. if (error) {
  236. [self logFailure:@"sign-in with Linkedin (headful-lite) failed" error:error];
  237. return;
  238. } else {
  239. [self logSuccess:@"sign-in with Linkedin (headful-lite) succeeded."];
  240. }
  241. [self showTypicalUIForUserUpdateResultsWithTitle:@"Sign-In Error" error:error];
  242. }];
  243. }];
  244. }];
  245. }];
  246. }
  247. - (void)signInMicrosoftHeadfulLite {
  248. FIROAuthProvider *provider = self.microsoftOAuthProvider;
  249. provider.customParameters = @{
  250. @"prompt" : @"consent",
  251. @"login_hint" : @"tu8731@gmail.com",
  252. };
  253. provider.scopes = @[ @"user.readwrite,calendars.read" ];
  254. [self showSpinner:^{
  255. [provider getCredentialWithUIDelegate:nil completion:^(FIRAuthCredential *_Nullable credential,
  256. NSError *_Nullable error) {
  257. if (error) {
  258. [self logFailure:@"sign-in with Microsoft failed" error:error];
  259. return;
  260. }
  261. [[AppManager auth] signInWithCredential:credential
  262. completion:^(FIRAuthDataResult *_Nullable
  263. authResult,
  264. NSError *_Nullable error) {
  265. [self hideSpinner:^{
  266. if (error) {
  267. [self logFailure:@"sign-in with Microsoft failed" error:error];
  268. return;
  269. } else {
  270. [self logSuccess:@"sign-in with Microsoft (headful-lite) succeeded."];
  271. }
  272. [self showTypicalUIForUserUpdateResultsWithTitle:@"Sign-In Error" error:error];
  273. }];
  274. }];
  275. }];
  276. }];
  277. }
  278. - (void)signInYahooHeadfulLite {
  279. FIROAuthProvider *provider = self.yahooOAuthProvider;
  280. [self showSpinner:^{
  281. [provider getCredentialWithUIDelegate:nil completion:^(FIRAuthCredential *_Nullable credential,
  282. NSError *_Nullable error) {
  283. if (error) {
  284. [self logFailure:@"sign-in with Yahoo failed" error:error];
  285. return;
  286. }
  287. [[AppManager auth] signInWithCredential:credential
  288. completion:^(FIRAuthDataResult *_Nullable
  289. authResult,
  290. NSError *_Nullable error) {
  291. [self hideSpinner:^{
  292. if (error) {
  293. [self logFailure:@"sign-in with Yahoo (headful-lite) failed" error:error];
  294. return;
  295. } else {
  296. [self logSuccess:@"sign-in with Yahoo (headful-lite) succeeded."];
  297. }
  298. [self showTypicalUIForUserUpdateResultsWithTitle:@"Sign-In Error" error:error];
  299. }];
  300. }];
  301. }];
  302. }];
  303. }
  304. - (ASAuthorizationAppleIDRequest *)appleIDRequestWithState:(NSString *)state API_AVAILABLE(ios(13.0)) {
  305. ASAuthorizationAppleIDRequest *request = [[[ASAuthorizationAppleIDProvider alloc] init] createRequest];
  306. request.requestedScopes = @[ASAuthorizationScopeEmail, ASAuthorizationScopeFullName];
  307. NSString *rawNonce = [self randomNonce:32];
  308. self.appleRawNonce = rawNonce;
  309. request.nonce = [self stringBySha256HashingString:rawNonce];
  310. request.state = state;
  311. return request;
  312. }
  313. - (NSString *)randomNonce:(NSInteger)length {
  314. NSAssert(length > 0, @"Expected nonce to have positive length");
  315. NSString *characterSet = @"0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._";
  316. NSMutableString *result = [NSMutableString string];
  317. NSInteger remainingLength = length;
  318. while (remainingLength > 0) {
  319. NSMutableArray *randoms = [NSMutableArray arrayWithCapacity:16];
  320. for (NSInteger i = 0; i < 16; i++) {
  321. uint8_t random = 0;
  322. int errorCode = SecRandomCopyBytes(kSecRandomDefault, 1, &random);
  323. NSAssert(errorCode == errSecSuccess, @"Unable to generate nonce: OSStatus %i", errorCode);
  324. [randoms addObject:@(random)];
  325. }
  326. for (NSNumber *random in randoms) {
  327. if (remainingLength == 0) {
  328. break;
  329. }
  330. if (random.unsignedIntValue < characterSet.length) {
  331. unichar character = [characterSet characterAtIndex:random.unsignedIntValue];
  332. [result appendFormat:@"%C", character];
  333. remainingLength--;
  334. }
  335. }
  336. }
  337. return result;
  338. }
  339. - (NSString *)stringBySha256HashingString:(NSString *)input {
  340. const char *string = [input UTF8String];
  341. unsigned char result[CC_SHA256_DIGEST_LENGTH];
  342. CC_SHA256(string, (CC_LONG)strlen(string), result);
  343. NSMutableString *hashed = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];
  344. for (NSInteger i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) {
  345. [hashed appendFormat:@"%02x", result[i]];
  346. }
  347. return hashed;
  348. }
  349. - (void)signInWithApple {
  350. if (@available(iOS 13, *)) {
  351. ASAuthorizationAppleIDRequest* request = [self appleIDRequestWithState:@"signIn"];
  352. ASAuthorizationController* controller = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[request]];
  353. controller.delegate = self;
  354. controller.presentationContextProvider = self;
  355. [controller performRequests];
  356. }
  357. }
  358. - (void)linkWithApple {
  359. if (@available(iOS 13, *)) {
  360. ASAuthorizationAppleIDRequest* request = [self appleIDRequestWithState:@"link"];
  361. ASAuthorizationController* controller = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[request]];
  362. controller.delegate = self;
  363. controller.presentationContextProvider = self;
  364. [controller performRequests];
  365. }
  366. }
  367. - (void)reauthenticateWithApple {
  368. if (@available(iOS 13, *)) {
  369. ASAuthorizationAppleIDRequest* request = [self appleIDRequestWithState:@"reauth"];
  370. ASAuthorizationController* controller = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[request]];
  371. controller.delegate = self;
  372. controller.presentationContextProvider = self;
  373. [controller performRequests];
  374. }
  375. }
  376. - (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0)) {
  377. ASAuthorizationAppleIDCredential* appleIDCredential = authorization.credential;
  378. NSString *IDToken = [NSString stringWithUTF8String:[appleIDCredential.identityToken bytes]];
  379. FIROAuthCredential *credential = [FIROAuthProvider credentialWithProviderID:@"apple.com"
  380. IDToken:IDToken
  381. rawNonce:self.appleRawNonce
  382. accessToken:nil];
  383. if ([appleIDCredential.state isEqualToString:@"signIn"]) {
  384. [FIRAuth.auth signInWithCredential:credential completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) {
  385. if (!error) {
  386. NSLog(@"%@", authResult.description);
  387. } else {
  388. NSLog(@"%@", error.description);
  389. }
  390. }];
  391. } else if ([appleIDCredential.state isEqualToString:@"link"]) {
  392. [FIRAuth.auth.currentUser linkWithCredential:credential completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) {
  393. if (!error) {
  394. NSLog(@"%@", authResult.description);
  395. } else {
  396. NSLog(@"%@", error.description);
  397. }
  398. }];
  399. } else if ([appleIDCredential.state isEqualToString:@"reauth"]) {
  400. [FIRAuth.auth.currentUser reauthenticateWithCredential:credential completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) {
  401. if (!error) {
  402. NSLog(@"%@", authResult.description);
  403. } else {
  404. NSLog(@"%@", error.description);
  405. }
  406. }];
  407. }
  408. }
  409. - (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error API_AVAILABLE(ios(13.0)) {
  410. NSLog(@"%@", error.description);
  411. }
  412. @end
  413. NS_ASSUME_NONNULL_END