Created
October 14, 2024 12:08
-
-
Save angelolloqui/070a08877c8f3eb01cb1b6f55647f4e4 to your computer and use it in GitHub Desktop.
Facebook login alternative to avoid issues with IDFA
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import AuthenticationServices | |
import SafariServices | |
import FacebookCore | |
// This helper is a workaroung for the issue in https://github.com/facebook/facebook-ios-sdk/issues/2387 | |
// Starting with new FBSDK 17.0.0, the login method is not working as expected when the user has not given IDFA consent | |
// The workaround is to use ASWebAuthenticationSession to open the SafariViewController to perform the login manually | |
public class FacebookSignInHelper: NSObject, ASWebAuthenticationPresentationContextProviding { | |
private static var shared: FacebookSignInHelper? | |
private let appID: String | |
private let redirectURI: String | |
private var completionHandler: ((Result<(accessToken: String, grantedPermissions: [String]), Error>) -> Void)? | |
private var permissions: [String] | |
public static func login( | |
permissions: [String] = ["public_profile", "email"], | |
from viewController: UIViewController, | |
completion: @escaping (Result<(accessToken: String, grantedPermissions: [String]), Error>) -> Void | |
) { | |
guard shared == nil else { | |
completion(.failure(NSError(domain: "FacebookLogin", code: -1, userInfo: [NSLocalizedDescriptionKey: "Already logging in"]))) | |
return | |
} | |
let appID = FacebookCore.Settings.shared.appID ?? "" | |
let redirectURI = "fb\(appID)" | |
shared = FacebookSignInHelper(appID: appID, redirectURI: redirectURI, permissions: permissions) | |
shared?.login(from: viewController) { result in | |
shared = nil | |
completion(result) | |
} | |
} | |
private init(appID: String, redirectURI: String, permissions: [String]) { | |
self.appID = appID | |
self.redirectURI = redirectURI | |
self.permissions = permissions | |
super.init() | |
} | |
private func login(from viewController: UIViewController, completion: @escaping (Result<(accessToken: String, grantedPermissions: [String]), Error>) -> Void) { | |
self.completionHandler = completion | |
guard let authURL = constructFacebookAuthURL() else { | |
completion(.failure(NSError(domain: "FacebookLogin", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to construct Facebook auth URL"]))) | |
return | |
} | |
let session = ASWebAuthenticationSession(url: authURL, callbackURLScheme: redirectURI) { callbackURL, error in | |
self.handleAuthResult(callbackURL: callbackURL, error: error) | |
} | |
session.presentationContextProvider = self | |
session.start() | |
} | |
private func constructFacebookAuthURL() -> URL? { | |
guard var components = URLComponents(string: "https://www.facebook.com/v21.0/dialog/oauth") else { | |
return nil | |
} | |
components.queryItems = [ | |
URLQueryItem(name: "client_id", value: appID), | |
URLQueryItem(name: "display", value: "touch"), | |
URLQueryItem(name: "return_scopes", value: "true"), | |
URLQueryItem(name: "redirect_uri", value: "\(redirectURI)://authorize"), | |
URLQueryItem(name: "response_type", value: "token"), | |
URLQueryItem(name: "scope", value: permissions.joined(separator: ",")), | |
URLQueryItem(name: "auth_type", value: "rerequest") | |
] | |
return components.url | |
} | |
private func handleAuthResult(callbackURL: URL?, error: Error?) { | |
if let error = error { | |
completionHandler?(.failure(error)) | |
return | |
} | |
guard let callbackURL = callbackURL, | |
let fragment = callbackURL.fragment else { | |
completionHandler?(.failure(NSError(domain: "FacebookLogin", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid callback URL"]))) | |
return | |
} | |
let params = fragment.components(separatedBy: "&") | |
.map { $0.components(separatedBy: "=") } | |
.reduce(into: [String: String]()) { result, param in | |
if param.count == 2 { | |
result[param[0]] = param[1].removingPercentEncoding | |
} | |
} | |
if let accessToken = params["access_token"], let grantedScopes = params["granted_scopes"]?.split(separator: ",").map(String.init) { | |
completionHandler?(.success((accessToken: accessToken, grantedPermissions: grantedScopes))) | |
} else if let error = params["error"] { | |
completionHandler?(.failure(NSError(domain: "FacebookLogin", code: -1, userInfo: [NSLocalizedDescriptionKey: error]))) | |
} else { | |
completionHandler?(.failure(NSError(domain: "FacebookLogin", code: -1, userInfo: [NSLocalizedDescriptionKey: "Unknown error occurred"]))) | |
} | |
} | |
public func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { | |
UIApplication.shared.windows[0] | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment