iOS linkedin authentication - ios

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.

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.

Authorization callback URL GitHub

I'm beginning iOS developer. I create an application that uses GitHub authorization. When I register a new OAuth application in GitHub developer program I must enter Authorization callback URL. But I do not have any site for my app. What do I need to specify in this field?
You can use deep linking.
you can read more about it here
The deeplink will try to open the app or redirect to it. The web browser or SFAuthenticationSession will close the browser and call the completion hander where you can check for the response code without any implementation for the deeplink.
To add the deep link in the app you can this below:
Select the project in Xcode navigator.
then select your target that you want to add the deep link to it.
select info from the top bar
at the bottom open the URL Types
add a name for the scheme
when you generate the URL for the oauth you can pass anything you want I just pass login in this example:
func getAuthenticateURL() -> URL {
var urlComponent = URLComponents(string: "https://github.com/login/oauth/authorize")!
var queryItems = urlComponent.queryItems ?? []
queryItems.append(URLQueryItem(name: "client_id", value: "YOUR_CLIENT_ID_HERE"))
queryItems.append(URLQueryItem(name: "redirect_uri", value: "APP_SCHEME_GOES_HERE://login"))
urlComponent.queryItems = queryItems
return urlComponent.url!
}
Then when you need to login do this:
import SafariServices
var authSession: SFAuthenticationSession?
func authenticate(with url: URL, completion: #escaping ((_ token: String?, _ error: Error?) -> Void)) {
authSession?.cancel()
authSession = SFAuthenticationSession(url: url, callbackURLScheme: nil, completionHandler: { url, error in
//get the token and call the completion handler
})
authSession?.start()
}
or use ASWebAuthenticationSession the same way if you're on iOS 12
Using SFAuthenticationSession you can do something like this. On your App add URLType:
Then on GitHub 'Developer Settings' for your app, add Authorization callback URL like this:
This way, after you login and authorize, Git Hub will call back //yourappname and Safari will redirect it back to your app completing the flow.

Firebase iOS / Swift and Deep Links

We have just integrated firebase, and all of a sudden our deep links are no longer working. We're using AppAuth for authentication, so we're reliant on deep links to direct us to the right place. I'm getting the following error:
<Debug> [Firebase/Analytics][I-ACS023001] Deep Link does not contain valid required params. URL params: {...}
Initialization of firebase as follows:
let bundleId = Bundle.main.bundleIdentifier
let filePath = Bundle.main.path(forResource: "GoogleService-Info-" + bundleId!, ofType: "plist")!
let options = FIROptions(contentsOfFile: filePath)
FIRApp.configure(with: options!)
And here's the deep linking functions:
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any]) -> Bool {
return application(app, open: url, sourceApplication: nil, annotation: [:])
}
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
if url.host == AppHost.deeplink {
...
}
If i remove the call to FIRApp.configure, everything in the app works fine. My AppAuth redirects flow into the function above without any issue. However, with the call to configure(), it never gets into either one of the functions. As a result, i can't do a token exchange and complete authentication.
I suspected the AppDelegate proxy might be the issue, so i tried disabling it in the plist file. I've validated that the plist file passed to FIRApp.configure has the appropriate keys:
<key>FirebaseAppDelegateProxyEnabled</key>
<false/>
But no matter what i do, it's still activating the proxy:
[Firebase/Analytics][I-ACS003007] Successfully created Firebase Analytics App Delegate Proxy automatically. To disable the proxy, set the flag FirebaseAppDelegateProxyEnabled to NO in the Info.plist
I'm using only FirebaseCrash and FirebaseCore (and FirebaseAnalytics indirectly through crash)
Instead of adding the FirebaseAppDelegateProxyEnabled key to the GoogleServices-Info.plist, add it to your App's info.plist. The Google Services plist should not be modified once it's generated.
As for disabling the proxy, it's fine to do this long term. The proxy is a convenience thing (it's just swizzling some methods), and you can reimplement it manually. There's some examples here of how to handle the lack of the proxy (non-swizzling case).

How Can I Unit Test Calling of iOS Application Delegate Methods?

I have an iOS application that integrates with the GitHub API. I am unit testing my OAuth requests, which requires testing the receipt of a code from the GitHub API that I will use to exchange for a token.
In my AppDelegate.swift, I have the following method, which is used to handle the callback from GitHub when the user authorizes my application to use their GitHub account:
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
return true
}
The steps are as follows:
Open the application.
Using the URL for authorizing GitHub account access (https://github.com/login/oauth/authorize), an instance of SFSafariViewController is presented, allowing the user to press the 'Authorize' button.
GitHub uses the callback URL to my application that I provided when registering my application with GitHub, which sends a notification to open my app.
The method above is executed, where I retrieve the code parameter from url.
However, I am stuck trying to find a way to test this without actually making a request to the GitHub API. I can create a URL instance that mimics what GitHub supplies my application with, but I would like to test this without making an actual request.
Is there a way to unit test this, or is this something that I shouldn't worry about since it's handled by the OS, and instead, only test my code for parsing the code parameter of a test URL?
UPDATE
After taking Jon's advice, I created a test class to allow me to simulate the GitHub callback in action:
class GitHubAuthorizationCallbackTests: XCTestCase {
let delegate = AppDelegateMock()
func test_AuthorizationCallbackFromGitHub_ApplicationOpensURL() {
guard let url = URL(string: "xxxxxxxxxxxxxx://?code=********************") else { return XCTFail("Could not construct URL") }
let isURLOpened = delegate.application(UIApplication.shared, open: url)
XCTAssertTrue(isURLOpened, "URL is not opened from GitHub authorization callback. Expected URL to be opened from GitHub authorization callback.")
}
}
Then, I created AppDelegateMock.swift to be used instead of AppDelegate.swift, adding in the intended method to be called when the GitHub callback is executed to open my app:
import UIKit
class AppDelegateMock: NSObject, UIApplicationDelegate {
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
return true
}
}
The test passes, allowing me to test the logic that I need to test for handling the code parameter that is returned from GitHub int he url parameter of the method.
Because you want to test a callback… just have tests invoke the callback directly, as if the GitHub framework invoked it.
Write simple tests around the happy path. Then, because you're dealing with external data you can't control, write tests that (if Swift allows) invoke the callback with strange options.

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.

Resources