Parse facebook login issue? - ios

I am trying to implement facebook login using Psrse.
The following code works if the user has facebook login in their iOS settings, but if they don't it doesn't work.
As soon as I call logInWithPermissions safari opens facebook, and my delegate gets called with nil user, and nil error. When user logins on safari, it takes the user back to the app, application:openURL:sourceApplication gets called, but the completion of logInWithPermissions never gets triggered
Been spending 3 days trying to resolve this. Any suggestions would be appreciated. I already added all keys to info.plist as well
there is a duplicate here that doesn't solve the issue: iOS/Swift: PFFacebookUtils.logInWithPermissions returns nil user and error
let permissionArray = ["public_profile", "email"]
PFFacebookUtils.logInWithPermissions(permissionArray) { user, error in
println(user)
println(error)
}
Appdelegate
func applicationDidBecomeActive(application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
FBAppCall.handleDidBecomeActiveWithSession(PFFacebookUtils.session())
}
func application(application: UIApplication, openURL url: NSURL, sourceApplication: String, annotation: AnyObject?) -> Bool {
return FBAppCall.handleOpenURL(url, sourceApplication: sourceApplication, withSession: PFFacebookUtils.session())
}
EDIT:
Added this to application:openUrl:sourceApplication and all values are there and valid after login.
println(url)
println(sourceApplication)
println(PFFacebookUtils.session())

The code causing the issue was this.
Session doesn't exist already so calling close was causing a crash when opening safari with no exception logs. Then coming back to the app it would start the app from scratch, but since it would go back to the login page it was hard to tell.
Need to check to see if session exists or not before calling clode.
func applicationDidEnterBackground(application: UIApplication) {
PFFacebookUtils.session().close()
}

Related

Testing passwordless auth in Firebase test lab for iOS

I am trying to figure out how to perform e2e test via firebase test lab for iOS that allow to check passwordless authentication flow, which essentially should do following
Enters email within my app
Firebase sends auth link to such email
Somehow I need to be logged into such email somewhere in firebases test device, I assume either in mail app, or gmail?
I need to know when new email arrives and open it
Once I opened an email I need to click on auth link
This should bring me back into the app and authenticate
My biggest issue at the moment is figuring out steps that happen outside my app i.e. how can I prepare for this test and log in under my email address (is it better to log into gmail in safari for example or somehow add this acc to apples mail app?).
Testing email
In my experience, testing your own code to see if an email was sent is not straightforward beyond checking if the method call you expect to send the email has happened.
Add on top of that using Firebase, which does not expose its underlying email send code, and that looks like a challenge to me.
In terms of testing, I suggest you assert that your method calls to send email happened or that the relevant code path was reached. In Firebase web, this looks like:
firebase.auth().sendSignInLinkToEmail(email, actionCodeSettings)
.then(function() {
// The link was successfully sent. Inform the user.
// Save the email locally so you don't need to ask the user for it again
// if they open the link on the same device.
window.localStorage.setItem('emailForSignIn', email);
// TODO save email to something accessible in your iOS tests
// TODO In your tests, confirm that email was saved after it was sent
})
.catch(function(error) {
// Some error occurred, you can inspect the code: error.code
});
See: https://firebase.google.com/docs/auth/web/email-link-auth#send_an_authentication_link_to_the_users_email_address
Another option:
You could setup a test user with an email address on a mail server that you manage, and check for incoming mail for that test user with your own custom mail reading code.
I would use Firebase Admin tools for this: https://firebase.google.com/docs/auth/admin/manage-users#create_a_user
I think you should first take a look at firebase docs for iOS on how to create dynamic links that you can use for email auth.
https://firebase.google.com/docs/auth/ios/email-link-auth
https://firebase.google.com/docs/auth/ios/passing-state-in-email-actions#configuring_firebase_dynamic_links
After you're done with those two check out the following code:
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
// [END old_delegate]
if handlePasswordlessSignIn(withURL: url) {
return true
}
}
func handlePasswordlessSignIn(withURL url: URL) -> Bool {
let link = url.absoluteString
// [START is_signin_link]
if Auth.auth().isSignIn(withEmailLink: link) {
// [END is_signin_link]
UserDefaults.standard.set(link, forKey: "Link")
(window?.rootViewController as? UINavigationController)?.popToRootViewController(animated: false)
window?.rootViewController?.children[0].performSegue(withIdentifier: "passwordless", sender: nil)
return true
}
return false
}
This is just an example on how you can handle the deep link in your app after the user taps the link. The delegate method
func application(_ application: UIApplication, open url: URL,
sourceApplication: String?, annotation: Any) -> Bool
in AppDelegate is used for all deep links into an app. You could set up for example your own scheme that your app conforms to. And you can send url type links with your custom scheme into your app from the browser for example.
To do this just Open Xcode, go to Project Settings -> Info, and add inside ‘The URL Types” section a new URL scheme. Add something of the sort of com.myApp in order for it to be as unizue as possible. Then you can just type into a browser com.myApp://main and handle that in the appDelegate.
Edit: It says so in their docs that you can present a prompt inside the app for the user to input the email. Where the user opens his email from isn't really your concern as long as your dynamic link is set up properly.

Redirect to a particular viewcontroller when redirecting from reset password mail ios

In my app I have a Home viewcontroller from where user can login, reset password, and skip to other pages.
If I click "Forgot password", I will get a mail where I can reset password. When I redirect back from email, I want the app to stay in the same Home viewcontroller.
If my flow is : click on "Forgot password" from home viewcontroller -> then move to ForgotPassword viewcontrollers -> exit app -> then go to my mail and do reset password , even then I want the app to open in the home page itself when it comes to foreground, not on any other viewcontrollers from where I exited the app or minimised the app.
How can I do this? Thank you
You can do it by manage flag after change password and go app on background.
Let me explain, In the AppDelegate take one global var isChangedPwd = false. When end use will go on reset password screen and successfully reset password then you need to make isChangedPwd = true and then manage flag in applicationDidEnterBackground like below
func applicationDidEnterBackground(_ application: UIApplication) {
if isChangedPwd {
exit(0)
}
}
It means when your isChangedPwd will be true and App will goes in background then your app will exit and will be launch at initial level.
NOTE : If you don't want to use exit(0) and not start app at initial level then you can redirect to Home page by below code.
func applicationDidEnterBackground(_ application: UIApplication) {
if isChangedPwd {
for viewCon in (self.navigationController?.viewControllers)! {
if viewCon is HomePageViewController {
self.navigationController?.popToViewController(viewCon, animated: true)
break
}
}
}
}
As far as I understand your question:
You want that whenever user taps on "Forgot password" on HomeScreen, user will be directed to ForgotPasswordScreen and then user receive a mail(with a link to reset the password [all these done by your server]). Coming back to app, user should be shown HomeScreen, whether its launching the app or already at background.
If above is the scenario then I guess there is an extra task that you are performing.
I would suggest that there is no need of ForgotPasswordScreen, just show an alert with a textfield to let user enter the email id to send password reset mail to it.
And you should just set one flag isLoggedIn
If isLoggedIn == true only then load your other viewControllers.
isLoggedIn has to be a global variable which should be set only at the time of successful login/logout

NSURLSession with background configuration and app killed by user

This is the scenario:
NSURLSession with background Configuration
Download or upload task start with Bad or No Internet Connection.
User close the App.
If iOS get Internet Connection will star session task. However,
With task still waiting for Internet.
User kills the App
System cancel all pending tasks
The Question
It is possible to know when the user opens the app again that the tasks were cancelled?
If yes, where?
This Answer says yes, it is possible, but I can not get any callback returning me an error.
I'm using Alamofire to handle all my Networking calls. However, I doubt that Alamofire will change the behavior.
Edit 1
/// Networking manager with Background Session Configuration
static var backgroundManager: Alamofire.Manager = {
let configuration = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier("com.xxx.NetworkingManager.Identifier")
configuration.HTTPAdditionalHeaders = Manager.defaultHTTPHeaders
let backgroundManager = Alamofire.Manager(configuration: configuration)
return backgroundManager
}()
Kudos to Rob because he showed me the right path.
So after the user kills the app, the system cancels all the pending tasks.
You can see that with the system.log:
Simulator/Debug/Open System Log...
How to catch what was already ongoing?
Instantiate again your Background NSURLSession. Do it elsewhere, but I'll do it in AppDelegate for this example.
The system knows (thanks to the identifier) that it is the same Background Session that before so it maps the pending tasks.
Then retrieve all the tasks. The canceled tasks are still there
The tasks will have a error that you can check.
Error Domain=NSURLErrorDomain Code=-999 "(null)"
UserInfo={NSErrorFailingURLStringKey=http://your.api.com/url,
NSURLErrorBackgroundTaskCancelledReasonKey=0,
NSErrorFailingURLKey=http://your.api.com/url}
Also, with the tasks, you will get the Request URL, so you can map your app requests and do something.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// This is the code for Swift 2.x. In Swift 3.x this call is a bit different.
NetworkingManager.backgroundManager.session.getTasksWithCompletionHandler { (data, upload, download) in
for task in data {
NSLog("\(task.error)")
}
for task in upload {
NSLog("\(task.error)")
}
for task in download {
NSLog("\(task.error)")
let reason = task.error?.userInfo[NSURLErrorBackgroundTaskCancelledReasonKey] as? Int
let code = task.error?.code
if reason == NSURLErrorCancelledReasonUserForceQuitApplication &&
code == NSURLErrorCancelled {
NSLog("\(task.originalRequest)")
NSLog("\(task.currentRequest?.URL)")
}
}
}
}
NSURLErrorCancelledReasonUserForceQuitApplication -> The operation was canceled because the user forced the app to quit.
So we are on the right track.
If someone has a better solution, please share! I do not really like the mapping solution of my requests urls.

com.facebook.sdk.core error 8

This is more informative than anything. I couldn't for the life of me find anything on error code 8 when trying to access the login prompt (aka safari) when debugging my ios app. After I hit the log into facebook button in my app it would attempt to open safari then dump me back to the login page to my app. The error was being caused by the permissions array. I had the the permission "public_profile" spelled "public profile" which was throwing an error obviously. So make sure your permission are type corrected if you get the com.facebook.sdk.core error 8.
Hope that helps someone.
Make sure your permissions are typed correctly
func loginButton(loginButton: FBSDKLoginButton!, didCompleteWithResult result: FBSDKLoginManagerLoginResult!, error: NSError!) {
if error == nil {
println("login complete")
self.performSegueWithIdentifier("showLogin", sender: self)
}else{
println(error.localizedDescription)
//com.facebook.sdk.core error 8.
}
}
In my case this error was caused by improper bundle id set in facebook settings of the app itself. Facebook "bundle id" is case sensitive, in my Info.plist I had uppercase product name, but in fb settings - lowercase.
In my case, I was using a Facebook account that hadn't yet been added to any of the Facebook app's admins/developers/testers roles.
In my case, after spending several hours of debugging I found that I was using the API,
func application(application: UIApplication,
openURL url: NSURL, options: [String: AnyObject]) -> Bool {
if #available(iOS 9.0, *) {
FBSDKApplicationDelegate.sharedInstance().application(application, didFinishLaunchingWithOptions: options)
} else {
// Fallback on earlier versions
}
return true
}
which is deprecated for iOS 9.So, I used:
func application(application: UIApplication,
openURL url: NSURL, sourceApplication: String?, annotation: AnyObject) -> Bool {
FBSDKApplicationDelegate.sharedInstance().application(application, openURL: url, sourceApplication: sourceApplication, annotation: annotation)
return true
}
Which worked for me. Hope this saves time of someone.
MAN!!! In my case it was the "bio" in the parameter that was causing this error. Facebook has changed the "bio" key to "about". So anyone using "bio" in parameters should change it to "about"
Pheww!!!
In my case It was wrong version. Instead of version: "v2.7", I used version: "2.7"
In my case it was because I listed name twice in the fields array. Assume that would apply to any field requested twice.
I had the same problem. It was because I didn't implement facebook login feature. After adding that, I logged in and my problem got solved.
In my case, I was playing with the Facebook Ads API and I tried to get a field but the name was wrong.
I had insights{date_start,date_end}, instead of insights{date_start, date_stop}.
More info here.
Hope it helps anyone.
In my case, I tried to get Facebook Id without logging into Facebook. Make sure you're logged into Facebook.
let accessToken = FBSDKAccessToken.current()
if accessToken != nil {
self.getCurrentUserFbId()
print("LoggedIn")
} else {
print("Not loggedIn")
self.loginIntoFacebook()
}
Hope this will helpful for anyone.
When it happened to me, I found that Facebook's access token was expired. Someone decided to store access token in UserDefaults and reuse it later. Of course all tokens more than ~2 months old were expired.
In my case it was because of GraphRequest.
The error response is
"com.facebook.sdk:FBSDKErrorDeveloperMessageKey" = "Syntax error
\"Expected end of string instead of \"%\".\" at character 5:
email%2Cname%2Cgender%2Cpicture";
"com.facebook.sdk:FBSDKGraphRequestErrorCategoryKey" = 0;
"com.facebook.sdk:FBSDKGraphRequestErrorGraphErrorCode" = 2500;
"com.facebook.sdk:FBSDKGraphRequestErrorHTTPStatusCodeKey" = 400;
"com.facebook.sdk:FBSDKGraphRequestErrorParsedJSONResponseKey" = {
body = {
error = {
code = 2500;
"fbtrace_id" = AFEUYbcYP39;
message = "Syntax error \"Expected end of string instead of \"%\".\" at character 5: email%2Cname%2Cgender%2Cpicture";
type = OAuthException;
};
};
code = 400;
};
The issue about that is https://github.com/facebook/facebook-swift-sdk/issues/309
In my case was because of birthday,friendlists . removing them started to work.
For me just had to go facebook developer under platform and activate deep linking
In our case we were seeing this issue while trying to log in with some test account (but not all). We were not following Facebook's recommended practice:
Before you test each use case below, make sure you remove your app from your test user's Facebook account using app settings.
After we did it for the failing test accounts, we were able to log in.

iOS linkedin authentication

I started developing an app for iOS in Swift. Now I am at the part where I need to create a login system. However we need the LinkedIn information from people.
How can I use the OAuth2 API in iOS to achieve this?
I already created an app in the LinkedIn developers area, but now I am stuck. I got some advice from someone that I need to use the UIWebView but I have no clue how this works.
Integrating LinkedIn Login in a Swift application
First, download the LinkedIn iOS SDK. I'll be using the 1.07 stable version for this example. I'll be following the integration guide here.
Create a new Developer Application.
Add your iOS app's Bundle Identifier to your LinkedIn App under Mobile.
Add your LinkedIn app Id and URL Scheme to your app's Info.plist file.
Whitelist the specified LinkedIn URL schemes and ATS URLs.
Copy the linkedin-sdk.framework library to your application. Make sure "copy files if necessary" and "create groups for folder references" are selected.
Project setup complete, now let's write some code!
Create a new Header file called BridgingHeader.h. Under Targets -> YourApp -> Build Settings -> Swift Compiler - Code Generation, add MyApp/BridgingHeader.h to "Objective-C Bridging Header."
In your BridgingHeader.h, add these two lines:
#import <Foundation/Foundation.h>
#import <linkedin-sdk/LISDK.h>
In your AppDelegate.swift, add this code to handle the OAuth URL callback:
Swift 3:
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
if LISDKCallbackHandler.shouldHandle(url) {
return LISDKCallbackHandler.application(application, open: url, sourceApplication: sourceApplication, annotation: annotation)
}
return true
}
Swift 2.x:
func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject) -> Bool {
if LISDKCallbackHandler.shouldHandleUrl(url) {
return LISDKCallbackHandler.application(application, openURL: url, sourceApplication: sourceApplication, annotation: annotation)
}
return true
}
Now it's time to log in the user. In your view controller, say you have a "Login" button. Your IBAction might look like this:
#IBAction func doLogin(sender: AnyObject) {
LISDKSessionManager.createSessionWithAuth([LISDK_BASIC_PROFILE_PERMISSION], state: nil, showGoToAppStoreDialog: true, successBlock: { (returnState) -> Void in
print("success called!")
let session = LISDKSessionManager.sharedInstance().session
}) { (error) -> Void in
print("Error: \(error)")
}
}
When logging in, the user will be asked to authenticate with your application:
If the user allows, the success block will be called, and you can get information about the authenticated user. If the login fails or the user does not allow access, then the failure block will be called, and you can alert the user on the issue that occurred.
To get information about the user we authenticated with, call a GET request on the user's profile:
let url = "https://api.linkedin.com/v1/people/~"
if LISDKSessionManager.hasValidSession() {
LISDKAPIHelper.sharedInstance().getRequest(url, success: { (response) -> Void in
print(response)
}, error: { (error) -> Void in
print(error)
})
}
The response.data will contain information on the authenticated user:
"{\n \"firstName\": \"Josh\",\n \"headline\": \"Senior Mobile Engineer at A+E Networks\",\n ... }"
Read the docs further for more things you can do with the API.
A sample project (with my App ID obfuscated) can be found here.
LinkedIn is an interesting beast, since their mobile SDKs have two flaws:
An end user NEEDS the LinkedIn app to be installed, otherwise the "login" button will redirect the user to the App Store.
The mobile access token cannot be used on the server. See this screenshot from LinkedIn's iOS documentation
So while JAL's answer is sufficient, you may want to look into implementing LinkedIn's authorization_code OAuth flow in your mobile app instead of the LinkedIn SDK. This would look roughly like the following flow:
The app will redirect the user to your webserver.
The webserver begins the LinkedIn authentication flow, and redirects the user to LinkedIn.
The user logs into LinkedIn, and gets redirected back to your webserver.
The webserver reads the response, and exchanges the Authorization Code with LinkedIn for an access token.
The webserver redirects your user back to the app, using a custom url scheme to send it the LinkedIn access token.
The app uses the LinkedIn access token to login to Stormpath.
Sound complicated? It's actually more straightforward than it seems. I actually wrote some demo code for this flow using Express.js & Swift. This example ultimately sends the access token to Stormpath to ultimately authenticate the user, but you can always replace it with your own code that calls the LinkedIn REST API to grab the profile's information.
In Swift 3.0, UIApplicationOpenURLOptionsKey should be add for Facebook and LinkedIn.
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
if LISDKCallbackHandler.shouldHandle(url)
{
return LISDKCallbackHandler.application(app, open: url, sourceApplication: options[UIApplicationOpenURLOptionsKey.sourceApplication] as! String, annotation: nil)
}
else
{
return FBSDKApplicationDelegate.sharedInstance().application(app, open: url
, options: options)
}
}
I know this has already been answered but I faced this issue as well and had done everything set in the Accepted answer, but for whatever reason the code still was not hitting success or failure. It turned out that with iOS 9 the following is deprecated.
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation
The solution was to use this instead :
- (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
options:(NSDictionary<NSString *,
id> *)options
For example, you could do:
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString *,id> *)options {
if ([LISDKCallbackHandler shouldHandleUrl:url]) {
return [LISDKCallbackHandler application:app openURL:url sourceApplication:options[UIApplicationLaunchOptionsSourceApplicationKey] annotation:options[UIApplicationLaunchOptionsAnnotationKey]];
}
return YES;
}
Update for Edward Jiang's answer; changes is token access on server + mobile side. Source: https://developer.linkedin.com/docs/ios-sdk-auth
As #Edward Jiang already explained, LinkedIn turns the whole authentication process very clumsy due to the requirement of having the LinkedIn app installed. I wrote a Swift library that handles the authentication flux within an embeded WKWebView: LinkedInAuth-Swift.
This library even eliminates the necessity of handling LinkedIn's response in a server. The WKWebView instance which is presenting the authentication flux is in charge of capturing the authorization code returned by LinkedIn and subsequently requesting the access token.

Resources