Firebase Twitter oAuth callback not working for Swift ios13 - swift

I have followed the instructions on https://firebase.google.com/docs/auth/ios/twitter-login to a T for Swift and I get the web popup to authorise the App I created on Twitter Dev, the callback is called and then the webview sits on an empty page of about:blank. Nothing can be done but click the Done button which then results in a Error Domain=FIRAuthErrorDomain Code=17058 "The interaction was cancelled by the user."
Callback address is correct. I've used the Twitter Consumer API Keys as the keys to enter in the Firebase console.
What am I missing?

For everyone who still has an issue with getting callback working, I managed to fix it. Sadly, you have to edit the method in the library (not the best way, but still. Bug was reported to firebase team). The method should look like the one below (you can find it in the file named FIROAuthProvider.m, line 125. I intentionally left commented lines, so you see the problem there... Hope it helps somebody :)
- (void)getCredentialWithUIDelegate:(nullable id<FIRAuthUIDelegate>)UIDelegate
completion:(nullable FIRAuthCredentialCallback)completion {
if (![FIRAuthWebUtils isCallbackSchemeRegisteredForCustomURLScheme:self->_callbackScheme]) {
[NSException raise:NSInternalInconsistencyException
format:#"Please register custom URL scheme '%#' in the app's Info.plist file.",
self->_callbackScheme];
}
// __weak __typeof__(self) weakSelf = self;
// __weak FIRAuth *weakAuth = _auth;
// __weak NSString *weakProviderID = _providerID;
dispatch_async(FIRAuthGlobalWorkQueue(), ^{
FIRAuthCredentialCallback callbackOnMainThread =
^(FIRAuthCredential *_Nullable credential, NSError *_Nullable error) {
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(credential, error);
});
}
};
NSString *eventID = [FIRAuthWebUtils randomStringWithLength:10];
NSString *sessionID = [FIRAuthWebUtils randomStringWithLength:10];
// __strong __typeof__(self) strongSelf = weakSelf;
[self
getHeadFulLiteURLWithEventID:eventID
sessionID:sessionID
completion:^(NSURL *_Nullable headfulLiteURL, NSError *_Nullable error) {
if (error) {
callbackOnMainThread(nil, error);
return;
}
FIRAuthURLCallbackMatcher callbackMatcher =
^BOOL(NSURL *_Nullable callbackURL) {
return [FIRAuthWebUtils
isExpectedCallbackURL:callbackURL
eventID:eventID
authType:kAuthTypeSignInWithRedirect
callbackScheme:self->_callbackScheme];
};
// __strong FIRAuth *strongAuth = weakAuth;
[_auth.authURLPresenter
presentURL:headfulLiteURL
UIDelegate:UIDelegate
callbackMatcher:callbackMatcher
completion:^(NSURL *_Nullable callbackURL,
NSError *_Nullable error) {
if (error) {
callbackOnMainThread(nil, error);
return;
}
NSString *OAuthResponseURLString =
[self OAuthResponseForURL:callbackURL
error:&error];
if (error) {
callbackOnMainThread(nil, error);
return;
}
__strong NSString *strongProviderID = _providerID;
FIROAuthCredential *credential = [[FIROAuthCredential alloc]
initWithProviderID:strongProviderID
sessionID:sessionID
OAuthResponseURLString:OAuthResponseURLString];
callbackOnMainThread(credential, nil);
}];
}];
});
}

For me there was no need in framework modifications.
As stated in a issue on GitHub: provider property should be
declared at a class or global level
So I just moved its initialisation out of a function.
Before:
class AuthViewController: UIViewController {
private func signIn() {
let provider = OAuthProvider(providerID: "twitter.com")
provider.getCredentialWith(nil) {
...
}
}
}
After:
class AuthViewController: UIViewController {
private let provider = OAuthProvider(providerID: "twitter.com")
private func signIn() {
provider.getCredentialWith(nil) {
...
}
}
}

Ok so I resolved this question but not entirely sure how now. I believe it was because the callback wasn't being issued and thus not picked up by the app. The callback wasn't issue due to the authentication and I believe because I hadn't created the terms of service and privacy policy. So make sure you have done that in the Twitter dev page.
In the twitter dev page, the callback link is: https://yourApp.firebaseapp.com/__/auth/handler
You'll find this in your firebase authentication settings when you enable twitter.
Ensure you have in info.plist LSApplicationQueriesSchemes an array of:
twitter
twitterauth
In your swift file:
var provider = OAuthProvider(providerID: "twitter.com")
Your button action:
#IBAction func onCustonTwitterButtonPressed(_ sender: Any) {
MyAppsCoreServicesScripts.logoutSocial() // My logout routine.
provider.getCredentialWith(nil) { credential, error in
if let error = error {
MyAppsCoreServicesScripts.showError(prefix: "Twitter Login",error: error, showMsg: true)
} else if credential != nil {
self.firebaseLogin(credential!)
}
}
}
Good luck! Hope this helps someone.

Got stucked in about:blank page for days, tried all the above but not working, solved by add following code in AppDelegate
func application(_ app: UIApplication, open url: URL, options: func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
let twitterhandeled = Auth.auth().canHandle(url)
return twitterhandeled
}

Related

UIActivityViewController handle Cancel action | Swift/Xcode

I can't find complete code in Swift of how to execute an action when the user presses the "x" in a UIActivityViewController (top right), cancelling the share.
This is what I currently have:
do {
try pdfData.write(to: temporaryFileURL)
let vc = UIActivityViewController(activityItems: [temporaryFileURL], applicationActivities: [])
self.present(vc, animated: true, completion: nil)
} catch {
print(error)
}
I found this Objective-C code that may work. Can someone convert it to Swift for me and show me how I could implement it with what I currently have?
vc.completionWithItemsHandler = ^(NSString *activityType,
BOOL completed,
NSArray *returnedItems,
NSError *error){
// react to the completion
if (completed) {
// user shared an item
NSLog(#"We used activity type%#", activityType);
} else {
// user cancelled
NSLog(#"We didn't want to share anything after all.");
}
if (error) {
NSLog(#"An Error occured: %#, %#", error.localizedDescription, error.localizedFailureReason);
}
};
Thank you!
You can do it this way.
vc.completionWithItemsHandler = { activityType, completed, returnedItems,
error in
// Your logic here
}

CLSLogv logs are not coming in Crashlytics

Its an Ionic app with some code written in native. Its uses cordova-plugin-firebase that for logging Crashlytics.
In the native part for iOS as well, we are trying to use Crashlytics to enable logging. However no matter what I try logs sent using CLSLogv aren't visible in dashboard.
Here is my code.
#objc(ImageUpload) class ImageUpload : CDVPlugin {
var backgroundTask: UIBackgroundTaskIdentifier = UIBackgroundTaskInvalid
//https://docs.fabric.io/apple/crashlytics/enhanced-reports.html#custom-logging-in-swift
private func sendErrorToCrashlytics(error: String) {
NSLog("Error in send error function is \(error)")
CLSLogv("%#", getVaList([error]))
}
#objc(imageUpload:)
func imageUpload(command: CDVInvokedUrlCommand) {
registerBackgroundTask()
func execute() {
let db = SQLiteDatabase()
var recToUpLoad: PayloadModel? = nil
while(toCheck) {
do {
let record = try db.readValues() // THIS METHOD THROWS EXCEPTION
} catch Exceptions.SQLiteError(let error) {
self.sendErrorToCrashlytics(error: error) // IT COMES HERE AFTER EXCEPTION
}
}
}
DispatchQueue(label: "imageUploadPlugin",qos: .background).async
{
execute()
}
}
}
However CLSLogv is not visible at all in Crashlytics. However when I do Crashlytics.sharedInstance().throwException()
, I can see it in the dashboard.
Exceptions is enum
enum Exceptions: Error {
case SQLiteError(message: String)
case JSONError(message: String)
}
Hoping it may help someone. Somehow I couldn't get CLSLogv to work. I ended up creating an NSError object and log that in Crashlytics in catch block.
catch Exceptions.SQLiteError(let error) {
let userInfo = [NSLocalizedDescriptionKey: error.message, "query": error.query]
let errorObj = NSError(domain: "sqlite", code: 400, userInfo: userInfo)
Crashlytics.sharedInstance().recordError(errorObj)
}

How to get login username with AWS Mobile-hub SDK

I've been working for a while on the login part of my app. I'm trying to use ASW Mobile Hub for this matter. I found a way to get it work with the different providers I need: my own user pool, FB and Google.
The problem is that I've been searching here and all over the AWS documentation trying to find the way to get user data (Username and some othe user data like picture, email and so on). I can get it if I'm using the FBSDK directly (usingFBSDKGraphRequest) but I don't know how to do it if the user choose to login in my cognito-user-pool. Also I cannot see what provider the user used once succeeded.
I can find some other ways to get that, but using the old SDK o directly Cognito calls and initially is not what I need. Here's the code I'm using to present the login window:
override func viewDidLoad() {
super.viewDidLoad()
if !AWSSignInManager.sharedInstance().isLoggedIn {
presentAuthUIViewController()
}
}
func presentAuthUIViewController() {
let config = AWSAuthUIConfiguration()
config.enableUserPoolsUI = true
config.addSignInButtonView(class: AWSFacebookSignInButton.self)
config.addSignInButtonView(class: AWSGoogleSignInButton.self)
AWSAuthUIViewController.presentViewController(
with: self.navigationController!,
configuration: config, completionHandler: { (provider:
AWSSignInProvider, error: Error?) in
if error == nil {
// SignIn succeeded.
} else {
// end user faced error while loggin in, take any
required action here.
}
})
}
So, the question is, how can I get the relevant user info, once the signin is succeeded?
If the user used cognito login, you can use the below code to get the username.
let identityManager = AWSIdentityManager.default()
let identityUserName = identityManager.identityProfile?.userName
For retrieving the provider once user succeeds, keep it in the session as below
func onLogin(signInProvider: AWSSignInProvider, result: Any?,
authState: AWSIdentityManagerAuthState, error: Error?) {
let defaults = UserDefaults.standard
defaults.set(signInProvider.identityProviderName, forKey:
"identityProviderName")
}
Hope this answer helps.
Updated Code to get Username:
let pool = AWSCognitoIdentityUserPool.init(forKey: "CognitoUserPools")
let username = pool.currentUser()?.username
I've been working on a workaround till I sort this out in a more elegant way. I guess that I need to go deeper in Cognito's understanding. But the fact is even the sample provided by Amazon doesen't show the User's Name...
Sample Amazon app screen
So, in the meantime, I modified the source code of the Cognito library AWSUserPoolsUIOperations to send me the data directly to my app, on a message:
#implementation AWSUserPoolsUIOperations
-(void)loginWithUserName:(NSString *)userName
password:(NSString *)password
navigationController:(UINavigationController *)navController
completionHandler:(nonnull void (^)(id _Nullable, NSError *
_Nullable))completionHandler {
self.userName = userName;
NSDictionary* userInfo = #{#"username": self.userName};
[[NSNotificationCenter defaultCenter]
postNotificationName:#"UsernameNotification"
object:nil userInfo:userInfo];
And then just getting the message in the app and storing the value.
#objc private func TestNotification(_ notification: NSNotification){
if let dict = notification.userInfo as NSDictionary? {
if let username = dict["username"] as? String {
appEstats.username = username
defaults.set(username, forKey: sUserName)
}
}
}
As I said is not the solution but in the meantime it works.

Signing Out of Firebase in Swift

I am attempting to sign out of the Firebase API, but I can't seem to figure out how to handle any errors that may occur.
The Firebase pod provides a method for signing out:
FIRAuth.auth()?.signOut()
It is marked with throws, so I have wrapped it in a do/try/catch block in a method to test the signing out process:
do {
try FIRAuth.auth()?.signOut()
} catch (let error) {
print((error as NSError).code)
}
I see that the signOut method is marked with throws in the Firebase pod, but I don't see how it can handle any errors asynchronously. I have tried entering Airplane Mode, which triggers a network error in my code everywhere else that a network request takes place, but with the signOut method, that error isn't caught because I have no completion handler to execute from. All of the other authentication methods from the Firebase pods have a completion handler, in which I am able to handle errors.
Here is the documentation for the signOut method from the Firebase pod:
/** #fn signOut:
#brief Signs out the current user.
#param error Optionally; if an error occurs, upon return contains an NSError object that
describes the problem; is nil otherwise.
#return #YES when the sign out request was successful. #NO otherwise.
#remarks Possible error codes:
- #c FIRAuthErrorCodeKeychainError Indicates an error occurred when accessing the keychain.
The #c NSLocalizedFailureReasonErrorKey field in the #c NSError.userInfo dictionary
will contain more information about the error encountered.
*/
open func signOut() throws
Do you have any suggestions for an appropriate way to handle the signing out of a user when I don't have a completion handler that allows me to check for an error?
You can catch the error like this
do
{
try Auth.auth().signOut()
}
catch let error as NSError
{
print(error.localizedDescription)
}
Edited from Milli's answer to add sending user back to initial page of the app.
// log out
func logout(){
do
{
try Auth.auth().signOut()
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let IntroVC = storyboard.instantiateViewController(withIdentifier: "IntroVC") as! introVC
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.window?.rootViewController = IntroVC
}
catch let error as NSError
{
print(error.localizedDescription)
}
}
An error is highly unlikely to occur but it's never good to assume anything. By the sound of the documentation, it wipes out your keychain which is the only way you'd be able to log back into your firebase application. From trying logging out of my own firebase app I was surprise that 0 errors occured. Here is the original code.
#IBAction func logOutTapped(_ sender: Any) {
let firebaseAuth = FIRAuth.auth()
do {
try firebaseAuth?.signOut()
} catch let signOutError as NSError {
print ("Error signing out: %#", signOutError)
}
if Utility.hasFacebook {
let login = FBSDKLoginManager()
login.logOut()
}
if Utility.hasTwitter {
Twitter.sharedInstance().sessionStore.logOutUserID((Twitter.sharedInstance().sessionStore.session()?.userID)!)
}
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewController = storyboard.instantiateViewController(withIdentifier: "LoginVC")
self.present(initialViewController, animated: false)
}
Anyways if you really want a completion handler then here's something I tossed up quickly
func logOut(completion:#escaping(_ errorOccured: Bool) -> Void) {
let firebaseAuth = FIRAuth.auth()
do {
try firebaseAuth?.signOut()
} catch let signOutError as NSError {
completion(true)
}
completion(false)
}

How can I do a Facebook login using Swift 2.0 and iOS 9.1?

I have added the latest Facebook SDK to my XCode 7.1 project written for iOS 9.1. Unfortunately all I know is Swift, not Objective C. Facebook's developer site documentation only has the documentation in Objective C. Every search on Google reveals documentation that is just too old because things have changed so much just in the past 6 months. So I'm kind of lost.
I did all the easy stuff with the plist file.
I was able to use:
import FBSDKCoreKit
import FBSDKLoginKit
I also added:
return FBSDKApplicationDelegate.sharedInstance().application(application, openURL: url, sourceApplication: sourceApplication, annotation: annotation)
and
return FBSDKApplicationDelegate.sharedInstance().application(application, didFinishLaunchingWithOptions: launchOptions)
To my AppDelegate.swift file. This all works and builds successfully. I have the correct frameworks added as well, obviously. Beyond this point I'm at a standstill because I don't know the syntax for adding a login button, for capturing what I assume would be returned json strings with tokens and other profile information I can use to store in the user's account, etc.
You're on the right track.
Have you set up your pList yet?
You're gonna have to add to your VC, add a login button, deal with delegate, and then deal with FB info. Something like (but not exactly) this:
class yourVC: UIViewController, FBSDKLoginButtonDelegate, UITextFieldDelegate
{
var loginView : FBSDKLoginButton = FBSDKLoginButton()
viewDidLoad() {
loginView.frame = CGRectMake(20, 20, theWidth-40, 40)
self.view.addSubview(loginView)
loginView.readPermissions = ["public_profile", "email", "user_friends","user_birthday"]
loginView.delegate = self
}
func loginButton(loginButton: FBSDKLoginButton!, didCompleteWithResult result: FBSDKLoginManagerLoginResult!, error: NSError!) {
if ((error) != nil)
{
//handle error
} else {
returnUserData()
}
}
func returnUserData()
{
let graphRequest : FBSDKGraphRequest = FBSDKGraphRequest(graphPath: "me", parameters: ["fields":"id,interested_in,gender,birthday,email,age_range,name,picture.width(480).height(480)"])
graphRequest.startWithCompletionHandler({ (connection, result, error) -> Void in
if ((error) != nil)
{
// Process error
print("Error: \(error)")
}
else
{
print("fetched user: \(result)")
let id : NSString = result.valueForKey("id") as! String
print("User ID is: \(id)")
//etc...
}
})
}
You're gonna have to play around with the returnData() part to get it right, but this code should get you 90% of the way there.
I've included a wide range of permissions (user age, interested in, etc), but you'll have to configure them yourself. Honestly this part (the hard part) is really similar with it's object C counterpart, which is much better documented. Just download some get projects where it's working, and try to parse them together into something that suits your fancy.
It can be hard to get it all working, but give it a go, and keep at it!
If you want to perform login from Facebook on click of your custom button, this will help:
#IBAction func onClickFacebookButton(sender: UIButton){
let login = FBSDKLoginManager()
login.loginBehavior = FBSDKLoginBehavior.SystemAccount
login.logInWithReadPermissions(["public_profile", "email"], fromViewController: self, handler: {(result, error) in
if error != nil {
print("Error : \(error.description)")
}
else if result.isCancelled {
}
else {
FBSDKGraphRequest(graphPath: "me", parameters: ["fields": "first_name, last_name, picture.type(large), email, name, id, gender"]).startWithCompletionHandler({(connection, result, error) -> Void in
if error != nil{
print("Error : \(error.description)")
}else{
print("userInfo is \(result))")
}
})
}
})
}