Get providerData from Firebase User - ios

I'm trying to add the unlink provider function in my app. I want to add a button that unlinks a user's Facebook account from their Firebase user account. The Firebase documentation says to use the providerData array, but I don't know how to extract the Facebook provider ID from it. If I just use let providerID = self.currentUser.providerID, it just defaults to the first provider in the array, regardless of what the type is (Facebook, Twitter, Firebase, etc..).
How can I extract the Facebook provider ID from providerData? This is how I tried to get it but it errors ("cannot subscript a value of type [String] with an index type of String"):
guard let providerData = self.currentUser?.providerData else {return}
var providerArray: [String] = []
for provider in providerData{
providerArray.append(provider as! String)
}
var providerID: String = providerArray["Facebook"]
FIRAuth.auth()?.currentUser?.unlink(fromProvider: providerID) { (user, error) in
EDIT:
Fixed by using
FIRAuth.auth()?.currentUser?.unlink(fromProvider: "facebook.com" ) { (user, error) in

You are trying to access providerArray like a Dictionary and that will not work. What you are looking for is the providerId specifically for Facebook. In order to retrieve this, you need to get the FIRAuthCredential for Facebook. In order to do this you should use FIRFacebookAuthProvider. This will get you the credential, from the credential you can grab the providerId, match it to the current user's array of providerIds like you are doing. Then unlink as you are attempting to do.

I fixed this by only showing the unlink button if a Facebook auth token is present. Then, when the button is pressed, just pass in "facebook.com" as the providerID:
FIRAuth.auth()?.currentUser?.unlink(fromProvider: "facebook.com" ) { (user, error) in

Related

Firebase Authentication - Retrieve Custom Claims key for iOS

A custom claim luid has been added to the Firebase Authentication from the backend, I'm looking for a way to access this key from the front end for an iOS application.
First I need to check if the key exists and if it exists then get its value.
What have I tried? Everything under Auth.auth().currentUser
Attaching a picture of the decoded JWT data, which shows the key luid
You can check the custom claims this way:
user.getIDTokenResult(completion: { (result, error) in
guard let luid = result?.claims?["luid"] as? NSNumber else {
// luid absent
return
}
//Check for value here and use if-else
})
Detailed explanation can be found in documentation
This will display a Dictionary with all the keys available for the current user.
Auth.auth().currentUser?.getIDTokenResult(completion: { (authresult, error) in
print("CurrentUser Keys", authresult!.claims.keys)
})
This will return Bool value based on a particular key's availability
Auth.auth().currentUser?.getIDTokenResult(completion: { (authresult, error) in
print("CurrentUser Keys", authresult!.claims.keys.contains("luid"))
})

How to get AWS Cognito user attributes using AWSMobileClient in iOS?

Question is very simple: I've added user authentication to iOS app using AWS Cognito and AWS Amplify. I have successfully implemented sign in and sign up, but how to get user attributes such as email, full name or phone number?
UPDATE:
For AWSMobileClient ~> 2.12.0, you can fetch user attributes as follows.
AWSMobileClient.default().getUserAttributes { (attributes, error) in
if(error != nil){
print("ERROR: \(error)")
}else{
if let attributesDict = attributes{
print(attributesDict["email"])
print(attributesDict["given_name"])
}
}
}
Per the documentation there are several property helpers for common attributes like username:
AWSMobileClient.getInstance().getUsername()
AWSMobileClient.getInstance().isSignedIn()
AWSMobileClient.getInstance().getIdentityId()
You can also get the JWT token and then pull out any user attributes:
AWSMobileClient.getInstance().getTokens().getIdToken().getTokenString()
You can use the getUserAttributes with the following API in the latest SDK version 2.8.x:
public func getUserAttributes(completionHandler: #escaping (([String: String]?, Error?) -> Void))
You can find the source code here:
https://github.com/aws-amplify/aws-sdk-ios/blob/master/AWSAuthSDK/Sources/AWSMobileClient/AWSMobileClientExtensions.swift#L532
In case you're looking for the email address specifically, and need to do so potentially offline, this would work for you:
AWSMobileClient.sharedInstance().getTokens { (tokens, error) in
if let error = error { print(error.localizedDescription) }
if let tokens = tokens {
let email = tokens.idToken?.claims?["email"] as? String
//completionHandler(email)... etc.
}
While AWSMobileClient.sharedInstance().getUsername() would be convenient, it will return the id of a User Pool user even if the User Pool is set to use email as the username. I consider this a bug, but have yet to report it to AWS.
I also research it on android (Kotlin).
// retrieve username
val username = AWSMobileClient.sharedInstance().username
When you sign in with "email" and "password", "username" is "email".
On the other hand, when the case of iOS (Swift), "username" is really "username" of cognito User Pool, even if you sign in with "email" and "password".
It is so confusing...

Unable to link Facebook and Google in Firebase Authentication

I am trying to link Facebook and Google. So, the scenario is this:
I have already authenticated with Google. So, now I am logging in Facebook, having same email id which was used earlier with Google. So, I get the error of account Exists with a different credential. And, I did this:
func fetchUserInfo()
{
Auth.auth().signInAndRetrieveData(with:FacebookAuthProvider.credential(withAccessToken: (FBSDKAccessToken.current().tokenString)!), completion: { (result, error) in
if let error = AuthErrorCode.init(rawValue: error!._code)
{
switch error
{
case .accountExistsWithDifferentCredential :
let credential = FacebookAuthProvider.credential(withAccessToken: (FBSDKAccessToken.current()?.tokenString)!)
Auth.auth().currentUser?.linkAndRetrieveData(with: credential, completion: { (result, error) in
if let error = error
{
print("Unable to link Facebook Account", error.localizedDescription)
}
else
{
NavigationHelper.shared.moveToHome(fromVC: self)
}
})
default: break
}
}
else
{
GeneralHelper.shared.keepLoggedIn()
if let currentUser = Auth.auth().currentUser
{
print(currentUser.email!)
}
NavigationHelper.shared.moveToHome(fromVC: self)
}
})
}
Here Firebase Documentation says that we need to just link the currentUser and retrieve data. But, the issue I am facing is that the currentUser is always nil. So, how can I get the current user? I have already tried this months ago and then I was able to link Facebook, Google and Email. Do, I need to signInAndRetrieve the data from Google in order to get the currentUser?
The Error "account Exists with a different credential" is because, by default, Firebase do not allow to use the same email address for two (or more) different Sing In methods. You need to enable this option.
1 - Go to Authentication > Sign-in method
2 - Scroll down to Advanced: Multiple accounts per email address
3 - Change the option to Allow creation of multiple accounts with the same email address
FYI: You need to do whole login process for each Sign In method in your app. Each method has is own credentials.
Hope this helps.

Facebook login using Firebase - Swift iOS

I'm implementing login with Facebook using Firebase, I have this code which searches my database after a successful facebook authentication for the email if exists in database and logs in the app if found, I want to direct the user to registration view controller if not found but its not working since this method is asynchronous. I appreciate if anyone can help. Here is my code :
func getFacebookUserInfo() {
if(FBSDKAccessToken.current() != nil){
let graphRequest = FBSDKGraphRequest(graphPath: "me", parameters: ["fields" : "id,name,gender,email,education"])
let connection = FBSDKGraphRequestConnection()
connection.add(graphRequest, completionHandler: { (connection, result, error) -> Void in
let data = result as! [String : AnyObject]
let email = data["email"] as? String
let emailRef = FIRDatabase.database().reference().child("usernameEmailLink")
emailRef.queryOrderedByValue().observe(.childAdded, with: { snapshot in
if let snapshotValue = snapshot.value as? [String: AnyObject] {
for (key, value) in snapshotValue {
if(value as? String == email){
self.stringMode = snapshotValue["mode"]! as! String
self.username = key
self.parseUserInfoFromJSON()
return
}
}
}
})
})
connection.start()
}
}
Thank you.
The registration/existence of the user in Firebase should probably be determined before the graphRequest code in the question.
Most importantly, (and this is critical), email addresses are dynamic so they should not be used to verify if a user exists. i.e. user with email address of 'leroy#gmail.com' updates his email to 'leroy.j#gmail.com'. If emails are used to verify registration, it can totally break if that email changes.
Please use Firebase uid's for that purpose as they are static and unique.
Since we only have a small snippet of code, we don't know the exact sequence being used. This answer is pseudo-code to outline a possible sequence.
We assume that by 'registered' it means that the user has gone through some kind of app registration sequence and the user has been created (and now exists/is registered) in Firebase.
In general there would be a login button and a delegate method to handle the actual login action.
The user enters their login and taps the login button
func loginButton(loginButton: FBSDKLoginButton!,
didCompleteWithResult result: FBSDKLoginManagerLoginResult!,
error: NSError?) {
Firebase can then get the credentials for that user (see Firebase doc quote below)
let credential = FIRFacebookAuthProvider.credential(withAccessToken: FBSDKAccessToken.current().tokenString)
At that point, sign in the user and check to see if they are registered (exist) in the Firebase user node.
FIRAuth.auth()?.signIn(with: credential) { (user, error) in
if let error = error { //failed due to an error
return
}
let uid = user.uid //the firebase uid
let thisUserRef = userRef.child(uid) //a reference to the user node
//check to see if the user exists in firebase (i.e. is Registered)
thisUserRef.observeSingleEvent(of: .value, with: { (snapshot) in
//if snapshot exists
//then the user is already 'registered' in the user node
// so continue the app with a registered user
//if not, then need to have the user go through a registration sequence and
// then create the user (make them registered) in the user node
doRegisterUser(user)
})
func doRegisterUser(user: FIRUser) {
//get what you need from the user to register them
// and write it to the users node. This could be from additional
// questions or from their Facebook graph, as in the code in the
// question
//for this example, we'll just write their email address
let email = user.email
let dict = ["email": email]
//create a child node in the users node with a parent of uid
// and a child of email: their email
thisUserRef.setValue(node)
//next time the user logs in via FB authentication, their user node
// will be found as they are now a 'registered' user
}
From the Firebase docs
After a user signs in for the first time, a new user account is
created and linked to the credentials—that is, the user name and
password, or auth provider information—the user signed in with. This
new account is stored as part of your Firebase project, and can be
used to identify a user across every app in your project, regardless
of how the user signs in.
As I mentioned, this is very pseudo code but offers a possible sequence for a solution.

Firebase Facebook login check if user exist

I have an facebook login system which works with firebase but I want to check if user exist on my firebase (i don't want to add it, just want to make sure if he exist because I want to redirect user to another page to complete its profile, once its done I'll want to send it to firebase).
I just need to check if user exist on my db. Here is the code that I try but it returns nil error and it automatically add user to firebase.
let credential = FIRFacebookAuthProvider.credentialWithAccessToken(FBSDKAccessToken.currentAccessToken().tokenString)
FIRAuth.auth()?.signInWithCredential(credential) { (user, error) in
// ...
}
You can use reauthenticateWithCredential method to check user is exist or not.
Check this Doc. , section -> Re-authenticate a user
let user = FIRAuth.auth()?.currentUser
var credential: FIRAuthCredential
// Prompt the user to re-provide their sign-in credentials
user?.reauthenticateWithCredential(credential) { error in
if let error = error {
// An error happened.
} else {
// User re-authenticated.
}
}
If user re-authenticated successfully that means user is existed...

Resources