Skip to content

Instantly share code, notes, and snippets.

@angelolloqui
Created October 14, 2024 12:08
Show Gist options
  • Save angelolloqui/070a08877c8f3eb01cb1b6f55647f4e4 to your computer and use it in GitHub Desktop.
Save angelolloqui/070a08877c8f3eb01cb1b6f55647f4e4 to your computer and use it in GitHub Desktop.
Facebook login alternative to avoid issues with IDFA
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