// // FacebookAuth.swift // GitHub: ethanhuang13 // Twitter: @ethanhuang13 import AuthenticationServices import SafariServices /* Updated: Pointed out by [@hal_lee on Twitter](https://twitter.com/hal_lee/status/1259132680828043265?s=21) this approach violates [Facebook Platform Policy 8.2](https://developers.facebook.com/policy/) and might be contacted by them to stop doing it. The best way to not using Facebook SDK is not to support Facebook Login at all. I leave the code here and let you make the decision. Original: FacebookAuth is for iOS app developers who need to support Facebook login but don't want to use the official SDK. iOS 11+ is required since it uses SFAuthenticationSession or ASWebAuthenticationSession. You need to setup the client id in Facebook Developer console, see https://github.com/fullstackreact/react-native-oauth/issues/76#issuecomment-335902057 For more information, visit https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow/ */ // MARK: Usage /* FacebookAuth.start(clientId: "12345678", scope: ["public_profile", "email"], window: UIApplication.shared.keyWindow) { result in switch result { case .success(let token): print(token) // TODO: Pass token to backend case .failure(let error): print(error.localizedDescription) // TODO: Handle error } } */ // MARK: Implementation enum FacebookAuth { enum AuthError: String, Error, LocalizedError { case invalidQuery case noURL case stateInconsistent case noAccessToken var errorDescription: String? { rawValue } } static func start(clientId: String, scope: [String], window: UIWindow?, completion: @escaping (Result) -> Void) { let state = UUID().uuidString var urlComponents = URLComponents() urlComponents.scheme = "https" urlComponents.host = "www.facebook.com" urlComponents.path = "/v7.0/dialog/oauth" urlComponents.queryItems = [ "client_id": clientId, "response_type": "token", "redirect_uri": "fb\(clientId)://authorize", "state": state, "scope": scope.joined(separator: ",")] .map { URLQueryItem(name: $0.key, value: $0.value) } guard let url = urlComponents.url else { completion(.failure(AuthError.invalidQuery)) return } let callbackURLScheme = "fb\(clientId)" var session: AuthenticationSession? let completionHandler: (URL?, Error?) -> Void = { url, error in session = nil print("auth callback url: \(url?.absoluteString ?? "failed")") if let error = error { completion(.failure(error)) return } guard let url = url else { completion(.failure(AuthError.noURL)) return } var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) urlComponents?.query = url.fragment guard let returnedState = urlComponents?.queryItems?.first(where: { $0.name == "state" })?.value, returnedState == state else { completion(.failure(AuthError.stateInconsistent)) return } guard let token = urlComponents?.queryItems?.first(where: { $0.name == "access_token" })?.value else { completion(.failure(AuthError.noAccessToken)) return } completion(.success(token)) } if #available(iOS 12.0, *) { session = ASWebAuthenticationSession(url: url, callbackURLScheme: callbackURLScheme, completionHandler: completionHandler) if #available(iOS 13.0, *) { (session as? ASWebAuthenticationSession)?.presentationContextProvider = window } } else { session = SFAuthenticationSession(url: url, callbackURLScheme: callbackURLScheme, completionHandler: completionHandler) } _ = session?.start() } } // MARK: Extensions protocol AuthenticationSession { func start() -> Bool } extension SFAuthenticationSession: AuthenticationSession { } @available(iOS 12.0, *) extension ASWebAuthenticationSession: AuthenticationSession { } extension UIWindow: ASWebAuthenticationPresentationContextProviding { @available(iOS 13.0, *) public func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { self } }