How to get FBSDKAccessToken from FBSDKAuthenticationToken - ios

In case of LAT FB login, we are just getting FBSDKAuthenticationToken and FBSDKAccessToken is nil.
https://developers.facebook.com/docs/facebook-login/limited-login/ios/
FBSDK Login is now using the Advertiser tracking flag(user consent)for iOS 14.
How to get FBSDKAccessToken from FBSDKAuthenticationToken? Or how to get FB profile from FBSDKAuthenticationToken?

According to this blog post of Facebook, limited mode and classic mode use different authentication methods behind.
Limited Login mode is based on the OpenID Connect standard.
Classic Login mode utilizes oAuth 2.0.
Therefore, I think there's no way to get access token by authentication token.
But we can get basic profile of user under limited login mode. There's a code snippet from Facebook's documentation reveals how to do that.
let loginManager = LoginManager()
// Ensure the configuration object is valid
guard let configuration = LoginConfiguration(tracking: .limited, nonce: "123")
else {
return
}
loginManager.logIn(configuration: configuration) { result in
switch result {
case .cancelled, .failed:
// Handle error
break
case .success:
// getting user ID
let userID = Profile.current?.userID
// getting pre-populated email
let email = Profile.current?.email
// getting id token string
let tokenString = AuthenticationToken.current?.tokenString
}
}
Also note that limited login mode does not support Graph API.

Related

Swift iOS GIDSignIn scopes are not auto selected

iOS app using GIDSignIn for o-Authentication.
We are wondering why when the scopes are presented and not auto selected
Our Client ID and app is verified in our google console and we do NOT show an unsafe app upon sign in.
Does this change when the app is live in the app store? We do not understand why we have to select the scope ourselves when in
func signInWithGoogle() {
guard let clientID = FirebaseApp.app()?.options.clientID else { return }
// Create Google Sign In configuration object.
let config = GIDConfiguration(clientID: clientID)
print("Client ID: \(clientID)")
let additionalScopes = ["https://www.googleapis.com/auth/youtube.readonly", "https://www.googleapis.com/auth/yt-analytics.readonly"]
// Start the sign in flow! GIDSignIn
GIDSignIn.sharedInstance.signIn(with: config, presenting: self, hint: nil, additionalScopes: additionalScopes) { [unowned self] user, error in
if let error = error {
// ...
return
}
}
GIDGoogleSignIn Framework Reference
"This is the intended behavior. We require the user to provide explicit consent for each scope being requested in addition to basic profile.
Note that you can check which scopes the user grants after a successful sign-in and, at an appropriate moment, re-request the additional scopes via
addScopes
if needed."
GoogleSignIn-iOS Github Source

When I use the apple to log in, the selection box will pop up. I choose to use the password to continue and the prompt is not complete

iOS13 (beta) Apple Login error
#available(iOS 13.0, *)
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
// Handle error.
crprint(error.localizedDescription)
}
Failed to complete operation. (com.apple.AuthenticationServices.AuthorizationError error 1000.)
I've encountered the same issue yesterday and I've managed to fix it following these steps:
Go to https://appleid.apple.com/account/manage, under the Devices section you should find devices on which you are signed in with your Apple ID,
Find device/simulator on which Apple SSO is not working, click on it and click remove from the account,
Go back to your device/simulator settings, it will ask you to authenticate again. When you successfully authenticate, Apple SSO should work again!
I'm not sure what caused this issue, probably some issue between the simulator and Apple ID.
In my case, launching ASAuthorizationController including a request for ASAuthorizationPasswordProvider was causing the error.
Failed to complete operation. (com.apple.AuthenticationServices.AuthorizationError error 1000.)
From the ASAuthorizationError.Code documentation; 1000 is for unknown
ASAuthorizationError.Code.unknown
The authorization attempt failed for an unknown reason.
Declaration
case unknown = 1000
Ref: https://developer.apple.com/documentation/authenticationservices/asauthorizationerror/code/unknown
Now that's not particularly helpful but did give me a clue to check my ASAuthorizationController setup which I was trying to launch with 2 requests from ASAuthorizationAppleIDProvider & ASAuthorizationPasswordProvider, like so:
func loginWithAppleButtonPressed() {
let appleSignInRequest = ASAuthorizationAppleIDProvider().createRequest()
appleSignInRequest.requestedScopes = [.fullName, .email]
let anySignInRequest = ASAuthorizationPasswordProvider().createRequest()
let controller = ASAuthorizationController(authorizationRequests: [appleSignInRequest,
anySignInRequest])
controller.delegate = self
controller.presentationContextProvider = self
controller.performRequests()
}
I tried this on a simulator that had an Apple ID with 2FA enabled and also on a device with another Apple ID without 2FA, and both times it would just go to authorizationController(controller:didCompleteWithError error:) and that's it.
Solution:
So to keep it simple, I launched ASAuthorizationController with only ASAuthorizationAppleIDProvider like so:
func loginWithAppleButtonPressed() {
let appleSignInRequest = ASAuthorizationAppleIDProvider().createRequest()
appleSignInRequest.requestedScopes = [.fullName, .email]
let controller = ASAuthorizationController(authorizationRequests: [appleSignInRequest])
controller.delegate = self
controller.presentationContextProvider = self
controller.performRequests()
}
And voilà! This time things worked as expected:
When using an Apple ID with 2FA
popped up with the login request
When using an Apple ID without 2FA
popped up an error telling me to enable 2FA
called authorizationController(controller:didCompleteWithError error:) with error 1000
So seems that in my case ASAuthorizationPasswordProvider was the culprit but since ASAuthorizationError.Code.unknown is a generic error case, this solution may not work for you.
Also, In my case I need only ASAuthorizationAppleIDProvider for Apple ID sign in so dropped the support for ASAuthorizationPasswordProvider.
In my case i needed to first check ASAuthorizationPasswordProvider, then, if there are no stored credential, use ASAuthorizationAppleIDProvider. For this case i had to make some crunches. Code below:
// Initial point
public func fire(appleIDCompletion: #escaping AppleIDServiceCompletion) {
self.completion = appleIDCompletion
let requestPassword = ASAuthorizationPasswordProvider().createRequest()
performRequest(requestPassword)
}
// help function
private func performRequest(_ request: ASAuthorizationRequest) {
let controller = ASAuthorizationController(authorizationRequests: [request])
controller.delegate = self
controller.presentationContextProvider = self
controller.performRequests()
}
// delegate
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
if let e = error as? ASAuthorizationError {
switch e.code {
case .canceled:
trace("User did cancel authorization.")
return
case .failed:
trace("Authorization failed.")
case .invalidResponse:
trace("Authorization returned invalid response.")
case .notHandled:
trace("Authorization not handled.")
case .unknown:
if controller.authorizationRequests.contains(where: { $0 is ASAuthorizationPasswordRequest }) {
trace("Unknown error with password auth, trying to request for appleID auth..")
let requestAppleID = ASAuthorizationAppleIDProvider().createRequest()
requestAppleID.requestedScopes = [.email, .fullName]
requestAppleID.requestedOperation = .operationImplicit
performRequest(requestAppleID)
return
} else {
trace("Unknown error for appleID auth.")
}
default:
trace("Unsupported error code.")
}
}
completion?(.rejected(error))
}
Works like a charm 🔥
Simply Add + "Sign In with Apple" from Capability.
I've resolved it by adding sign in with apple as key in entitlements plist .
From Apple's example,
performExistingAccountSetupFlows, only call this method once on viewDidAppear. If user info exists already then Apple will show it to login. If not then it will throw error.
handleAuthorizationAppleIDButtonPress, whenever user taps on Sign in with Apple button, note that if an account already had existed it would have shown it to the user already. I believe its still in progress and not all use cases are covered, for example if user sees the login info initially from ViewDidAppear call and cancels it then user have to create a new account when tapping on this method since its missing ASAuthorizationPasswordProvider request. If user had some login info then in that case this call (with ASAuthorizationPasswordProvider) will succeed but if no data is available then user will not see any action on tapping this button since it will throw error.
I am still figuring this out, if I have anything more to add then I will update the answer. So, for now we can only have this one use case to use this Sign in with Apple option.
Update:
Once I created a new account, I was offered by this same flow to login with the already existing account. So, I can say that there is no need to include call to ASAuthorizationPasswordProvider request in handleAuthorizationAppleIDButtonPress method. I am doing all the testing on device.
You can always go to Settings -> AppleId -> Password & Security -> Apple ID Logins to check and delete account if you need to test various scenarios.
Update 2:
Everything seems to work fine in other scenarios too if you already have a saved password or App Id account created, so even if I pass ASAuthorizationPasswordProvider in the handleAuthorizationAppleIDButtonPress call, it is working fine. I would suggest to not pass ASAuthorizationPasswordProvider in the next call and keep the flow as described above, this way if no saved password is present or Apple Id created then it will provide option to the user to create a new id, if there is already an id that exists then it will show that id.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
performExistingAccountSetupFlows()
}
func performExistingAccountSetupFlows() {
// Prepare requests for both Apple ID and password providers.
let requests = [ASAuthorizationAppleIDProvider().createRequest(),
ASAuthorizationPasswordProvider().createRequest()]
// Create an authorization controller with the given requests.
let authorizationController = ASAuthorizationController(authorizationRequests: requests)
authorizationController.delegate = self
authorizationController.presentationContextProvider = self
authorizationController.performRequests()
}
#objc
func handleAuthorizationAppleIDButtonPress() {
let appleIDProvider = ASAuthorizationAppleIDProvider()
let request = appleIDProvider.createRequest()
request.requestedScopes = [.fullName, .email]
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
authorizationController.delegate = self
authorizationController.presentationContextProvider = self
authorizationController.performRequests()
}
I resolved this by holding my finger down on finger print scanner til completion. I'm not an iphone user so I'm not used to the finger print scanner. If you pull your finger off too soon you get this error.

Firebase Auth Login must allow single device login

I am developing app with the help of Firebase backend and I am using Firebase Auth for login into my application. I have done all integration and every thing and my app is working fine.
But I want only single session with single user as right now with single userId I am able to login through multiple devices.
So I want to restrict user that at a time user can login in in single device.
I am using Custom auth with username password login :
Auth.auth().signIn(withCustomToken: customToken ?? "") { (user, error) in
// ...
}
If user login with same id in another device I want to show alert that "You are already logged in another device".
Is there any possibility in Firebase Auth lib for single user single session?
Edit : Suggested duplicate question will not solve my query fully though it help me to understand scenireo and help to solve my problem.
Thanks #Frenk for pointing this out.
I search a lot on above issue which I was facing through firebase authentication and after lots of research I ended up with below solution which was working as per my requirements.
First of all firebase not providing this in their library so we need to apply our custom logic here to achieve this 1 session user login in our app.
Step 1: You need to add new child "SignIn" at your root of Database.
Step 2: While Auth.auth().signIn() return success in that block we need to check below Flag that is User already signIn in any other device ? for that I have create one method as mention below.
func alreadySignedIn() {
// [START single_value_read]
let userID = Auth.auth().currentUser?.uid
ref.child("SignIn").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
if let dict = snapshot.value as? [String: Any] {
if let signedIn = dict["signIn"] as? Bool {
if signedIn {
self.signOut()
}
else {
// change the screen like normal
print("First Session of user")
self.writeNewUserSigin(withUserID: userID!)
}
}else{
self.writeNewUserSigin(withUserID: userID!)
}
}else{
print(snapshot)
self.writeNewUserSigin(withUserID: userID!)
}
}) { (error) in
print(error.localizedDescription)
}
// [END single_value_read]
}
By this method we are checking that current user uId have in our SignIn Child with True value if data is there in our database with Boll value True we need to handle that and show some alert and signOut from firebase.
Note : As we allowed user to sign-in and than we are checking that
user already signin in any other device so if its returning True we
need to SignOut() from firebase.
Now last step while user manually signOut from the app
Step 3: While user click on SignOut button in app we need to update our Child with False value in it so after onwards user can able to SignIn in any other device. For that we can use below method.
func updateUserSigIn(withUserID userID: String) {
//Update SignIn Child with flase value on current UID
// [START write_fan_out]
let post = ["signIn": false]
let childUpdates = ["/SignIn/\(userID)": post]
let ref = Database.database().reference()
ref.updateChildValues(childUpdates) { (error, refDatabase) in
if (error != nil) {
print("error \(String(describing: error))")
}else {
print("New user Saved successfully")
self.signOut()
}
}
// [END write_fan_out]
}
Thats it now only one app user session will allow.
Hope this will helps others.
Thanks for this thread as I got some hints from this answer.

Twitter / Fabric login button not behaving as expected

I have a very simple iOS project where I'm using Twitter/Fabric login button for user login to my app.
I've managed to get the Fabric login button working. When the user clicks on the Twitter login button they are automatically authenticated (that's if they are logged into the Twitter app) otherwise the user is presented with a Twitter login screen.
I'm not sure why the user is automatically authenticated when they are logged into the Twitter app on their phone.
Is there a way to use the Twitter/Fabic API to open the Twitter app and ask for permission to grant access to my app similar to Facebook login even if the user is logged into the Twitter App.
This is what my AppDelegate looks like:
Twitter.sharedInstance().start(withConsumerKey: "someKey", consumerSecret: "someSecret")
Fabric.with([Twitter.self])
This is what my ViewController looks like:
#IBOutlet private weak var twitterLoginButton: TWTRLogInButton!
// and
twitterLoginButton.logInCompletion = {(session, error) in
if error != nil {
print("ERROR: \(error)")
} else {
if let unwrappedSession = session {
print(unwrappedSession.userName)
}
}
}
Twitter.sharedInstance().logIn { (session, error) in
if let unwrappedSession = session {
print("Signed in as: \(unwrappedSession.userName)")
} else {
print("ERROR: \(error)")
}
}
Fabric documentation says that the first default for login is to go through the Twitter app (that may be why your user is automatically authenticated if they're already logged in the app), otherwise it will go through the webAuth login flow.
"To force the log in flow to use the web OAuth flow pass the TWTRLoginMethodWebBased method to the relevant log in methods."
// If using the TWTRLoginButton
let logInButton = TWTRLogInButton() { session, error in
}
logInButton.loginMethods = [.webBased]
So if you want to force the user to go through the web flow, try adding to your code: twitterLoginButton.loginMethods = [.webBased]

Login impossible with Spotify iOS 9

I can't login with ios sdk spotify.
I followed the Brian's tutorial (https://www.youtube.com/watch?v=GeO00YdJ3cE) and there is a difference with the current spotify tutorial :
In the video it talks about token exchange and show a spotify webpage. However on current spotify webpage the paragraph is missing.
Does this exchange token must be installed ?
I defined all elements in my spotify app account.
I also defined in the URL schemes : "spotify-action", "my-app-Name" but I can't sucess login.
Anyone help please ?
There are two options:
1) Implicit Grant Flow - grants users access tokens that will expire in 60 minutes - it is much simpler but has it's limits if you want to build a usable application. I will give you a simple example with spotify's updated sdk framework (you don't have to use safari)..
class ViewController: UIViewController, SPTAuthViewDelegate {
let kclientID = ""
let kcallbackURL = ""
#IBAction func loginSpotify(sender: AnyObject){
SPTAuth.defaultInstance().clientID = kclientID
SPTAuth.defaultInstance().redirectURL = NSURL(string: kcallbackURL)
SPTAuth.defaultInstance().requestedScopes = [SPTAuthStreamingScope]
SPTAuth.defaultInstance().sessionUserDefaultsKey = "SpotifySession"
SPTAuth.defaultInstance().tokenSwapURL = NSURL(string: ktokenSwapURL) //you will not need this initially, unless you want to refresh tokens
SPTAuth.defaultInstance().tokenRefreshURL = NSURL(string: ktokenRefreshServiceURL)//you will not need this unless you want to refresh tokens
spotifyAuthViewController = SPTAuthViewController.authenticationViewController()
spotifyAuthViewController.delegate = self
spotifyAuthViewController.modalPresentationStyle = UIModalPresentationStyle.OverCurrentContext
spotifyAuthViewController.definesPresentationContext = true
presentViewController(spotifyAuthViewController, animated: false, completion: nil)
}
func authenticationViewController(authenticationViewController: SPTAuthViewController!, didLoginWithSession session: SPTSession!) {
print("Logged In")
}
func authenticationViewController(authenticationViewController: SPTAuthViewController!, didFailToLogin error: NSError!) {
print("Failed to Log In")
print(error)
authenticationViewController.clearCookies(nil)
}
func authenticationViewControllerDidCancelLogin(authenticationViewController: SPTAuthViewController!) {
print("User Canceled Log In")
authenticationViewController.clearCookies(nil)
}
}
2) Authorization Code Flow - Spotify's authentication server sends an encrypted refresh token which you store, for example,
SPTAuth.defaultInstance().sessionUserDefaultsKey = "SpotifySession". When that sessions expires you must trigger a function on your server... Hope this helps you get started

Resources