Swift/Firebase - Check if user is logged in via Facebook & Google - swift

I use CurrentSession environment object to store user info. A user can login into the app via Google or Facebook. I wanna keep the user logged in even after the app is closed and opened again. So I was going to check provider specific objects/variables to perform these checks. Works well for FB but unfortunately doesn't work for Google (!)
So I login via Facebook or Google and close the app..
When I launch my app again the CurrentSession is initialised. I check if the user was already logged in and it works well for Facebook by performing AccessToken.current != nil check.
But once I do the same for the user logged in via Google, when i relaunch the application GIDSignIn.sharedInstance()?.currentUser is always nil ;(
How should I perform this check for Google user? I know i can use Auth.auth() but at this moment I am looking for the way to do that via GIDSignIn object..
import Combine
import FirebaseCore
import FirebaseAuth
import GoogleSignIn
import FBSDKLoginKit
import FBSDKCoreKit
class CurrentSession: ObservableObject {
#Published var userId: String? = nil
init() {
// check if user logged via Facebook
if AccessToken.current != nil { <-- WORKS WELL FOR FACEBOOK
let credential = FacebookAuthProvider.credential(withAccessToken: AccessToken.current!.tokenString)
Auth.auth().signIn(with: credential) { (res,er) in
if er != nil{
print((er?.localizedDescription)!)
return
}
print("email: \(String(describing: res?.user.email))")
print("name: \(String(describing: res?.user.displayName))")
DispatchQueue.main.async {
self.userId = String(describing: res?.user.displayName)
}
}
}
// check if user logged via Google
if let user = GIDSignIn.sharedInstance()?.currentUser, let authentication = user.authentication { <<-- HERE (CHECK DOESN'T WORK AS I EXPECT)
let credential = GoogleAuthProvider.credential(withIDToken: authentication.idToken,
accessToken: authentication.accessToken)
Auth.auth().signIn(with: credential) { (res, err) in
if err != nil {
print("\(String(describing: err?.localizedDescription))")
return
}
print("email: \(String(describing: res?.user.email))")
print("name: \(String(describing: res?.user.displayName))")
DispatchQueue.main.async {
self.userId = String(describing: res?.user.displayName)
}
}
} else {
print("No current user found")
}
}
}

First thing, make sure to import FirebaseAuth.
Then you'll may want to create some enum:
enum AuthProviders: String {
case password
case phone
case facebook = "facebook.com"
case google = "google.com"
case apple = "apple.com"
}
Then perform this check to determine the current provider that used to sign-in:
if let providerId = Auth.auth().currentUser?.providerData.first?.providerID,
let provider = AuthProviders(rawValue: providerId){
switch provider {
case .password:
// Signed-in with Firebase Password
case .phone:
// Signed-in with Firebase Phone
case .google:
// Signed-in with Google
case .facebook:
// Signed-in with Facebook
case .apple:
// Signed-in with Apple
}
}
*if your app uses other provider(s), you may get its ProviderID from FIRAuthProvider.m and add it to the enum
*Note that anonymous sign-in providerId's will be nil as of this PR

It seems like i found a solution.
Here's the code that makes these checks:
if let signIn = GIDSignIn.sharedInstance(), signIn.hasPreviousSignIn() {
signIn.restorePreviousSignIn()
DispatchQueue.main.async {
self.userId = String(describing: GIDSignIn.sharedInstance()?.currentUser.userID)
print("userId = self.userId")
}
} else {
print("ATTEMPT FAILED")
}
}

Related

How to renew the id token using auth0 in SwiftUI app

I'm using Auth0 for login and logout in my iOS app. after the user logs in I get an id token which I use to make the further api calls in my app. we need to keep updating the token with auth0 as mentioned in their doc
My function is as follows
struct UpdateToken {
let credentialsManager: CredentialsManager
init() {
self.credentialsManager = CredentialsManager(authentication: Auth0.authentication())
}
func updateToken() {
guard credentialsManager.canRenew() else {
// Present login screen
print("not renewing")
return
}
Auth0
.webAuth()
.scope("openid profile offline_access")
.audience("\(audience)/userinfo")
.start {
switch $0 {
case .failure(let error):
print("token update failed")
break
// Handle error
case .success(let credentials):
// Pass the credentials over to the Credentials Manager
credentialsManager.store(credentials: credentials)
UserDefaults.standard.set(credentials.idToken, forKey: "id_token")
print("token updated")
}
}
}
}
it is printing not renewing in my console. I'm not sure what I am missing here.
the login function works perfectly fine
func login() {
Auth0
.webAuth()
.start { result in
// Handle the result of the authentication
switch result {
case .failure(let error):
// If the authentication fails, print the error message
print("Failed with: \(error)")
case .success(let credentials):
// If the authentication is successful, store the credentials and user information in UserDefaults
self.userProfile = Profile.from(credentials.idToken)
self.userIsAuthenticated = "1"
print("Credentials: \(credentials)")
// Store the ID token
print("ID token: \(credentials.idToken)")
UserDefaults.standard.set(credentials.idToken, forKey: "id_token")
// Print and store the token type and access token
print("token type: \(credentials.tokenType)")
print("access token \(credentials.accessToken)")
// Extract and store the user ID, name, and email from the user profile
print("userID is \(userProfile.id)")
let fullString = userProfile.id
let parts = fullString.split(separator: "|")
let desiredPart = String(parts[1])
print(desiredPart)
UserDefaults.standard.set(desiredPart, forKey: "userId")
UserDefaults.standard.set(userProfile.name, forKey: "userName")
UserDefaults.standard.set(userProfile.email, forKey: "userEmail")
}
}
}
It sounds like canRenew() is unable to find any stored credentials - Try using credentialsManager.store on initial login similar to how you are in updateToken(). This way the credentials are stored in the keychain when a user logs in to begin with.

How do I grab credential values for re authentication? (Question updated)

I am trying to allow users to change their email or password. I did some research on how re authentication work, most of the questions I had were answered however when creating the credential how do I grab the users email / password in the .credentialWithEmail(email, password: password) section? I'm not sure what to enter in those fields.
When I take a look at the quick help tab, it explains the Parameters:
email
The user's email address.
password
The user's password.
Here is the code
let credential = FIREmailPasswordAuthProvider.credentialWithEmail(email, password: password)
func updateEmail() {
guard let updatedEmail = self.updatedEmail else { return }
let user = Auth.auth().currentUser
guard let currentUid = Auth.auth().currentUser?.uid else { return }
// re authenticate the user
user?.reauthenticate(with: credential, completion: { (result, error) in
if let error = error {
// An error happened.
print(error._code)
self.handleError(error)
} else {
guard self.emailChanged == true else { return }
user?.updateEmail(to: self.emailTextField.text!, completion: { (error) in
if let error = error {
self.handleError(error)
} else {
print("Email Change Success")
USER_REF.child(currentUid).child("email").setValue(updatedEmail) { (err, ref) in
self.dismiss(animated: true, completion: nil)
}
}
}
)}
}
)}
If you are asking why you can't get the email or password after you set FIREmailPasswordAuthProvider, it is because it is set only. There is no function to retrieve email/password after you set FIREmailPasswordAuthProvider. Firebase does not have a way to retrieve set passwords from their Auth class. The user email can be retrieved with let userEmail = Auth.auth().currentUser?.email
You may need to redesign you code to allow the app to save passwords locally using NSUserDefaults or simply make the user reenter them.

How to get username from AWS Cognito - Swift

Q & A Style: See Answer Below
How Can I get the username from a user logged in with Cognito?
I've done this and my user is logged in, now what?
AWSAuthUIViewController.presentViewController(
with: self.navigationController!,
configuration: config, completionHandler: { (provider: AWSSignInProvider, error: Error?) in
if error == nil {
//get parameters
}
} else {
print(error as Any)
}
})
}
Prerequisites:
App registered with MobileHub
Cognito Setup in MobileHub
Mobilehub integrated with Swift Project using AWS SDK
If you're like me, you did this with little to no difficulty and now you're stuck trying to get the username and other parameters from the logged in user. There are a lot of answers, but thus far, I haven't stumbled upon one that gets you all the way there.
I was able to piece this together from various sources:
func getUsername() {
//to check if user is logged in with Cognito... not sure if this is necessary
let identityManager = AWSIdentityManager.default()
let identityProvider = identityManager.credentialsProvider.identityProvider.identityProviderName
if identityProvider == "cognito-identity.amazonaws.com" {
print("************LOGGED IN WITH COGNITO************")
let serviceConfiguration = AWSServiceConfiguration(region: .USWest2, credentialsProvider: nil)
let userPoolConfiguration = AWSCognitoIdentityUserPoolConfiguration(clientId: "YourClientID", clientSecret: "YourSecretKey", poolId: "YourPoolID")
AWSCognitoIdentityUserPool.register(with: serviceConfiguration, userPoolConfiguration: userPoolConfiguration, forKey: "YourPoolName (typically formatted as YourAppName_userpoool_MOBILEHUB_12345678")
let pool = AWSCognitoIdentityUserPool(forKey: "YourPoolName")
// the following line doesn't seem to be necessary and isn't used so I've commented it out, but it is included in official documentation
// let credentialsProvider = AWSCognitoCredentialsProvider(regionType: .USWest2, identityPoolId: "YourPoolID", identityProviderManager:pool)
if let username = pool.currentUser()?.username {
print("Username Retrieved Successfully: \(username)")
} else {
print("Error getting username from current user - attempt to get user")
let user = pool.getUser()
let username = user.username
print("Username: \(username)")
}
}
}
To get your ClientID, Secret Key, and PoolID, check your awsconfiguration.json
To get your PoolName, login to MobileHub, and in your project's backend, go to User Sign in, click Email and Password, then click Edit in Cognito. The following page will have your Pool Name as "YourAppName_userpool_MOBILEHUB_12345678"
Edit: To get all of the attributes as well:
if let userFromPool = pool.currentUser() {
userFromPool.getDetails().continueOnSuccessWith(block: { (task) -> Any? in
DispatchQueue.main.async {
if let error = task.error as NSError? {
print("Error getting user attributes from Cognito: \(error)")
} else {
let response = task.result
if let userAttributes = response?.userAttributes {
print("user attributes found: \(userAttributes)")
for attribute in userAttributes {
if attribute.name == "email" {
if let email = attribute.value {
print("User Email: \(email)")
}
}
}
If you're using Cognito User Pools, you can use this:
import AWSUserPoolsSignIn
AWSCognitoUserPoolsSignInProvider.sharedInstance()
.getUserPool()
.currentUser()?
.username

How to check if user needs to re-authenticate using Firebase Authentication

I am using Firebase to log in users into my app, but when I am adding the capability to manage their account like changing their email, password and so on. The documentation says that if the user have not recently signed in they need to re-authenticate, but my question is: How can I check if the user have signed in recently or not? According to the docs the error will return FIRAuthErrorCodeCredentialTooOld, but how can I check this?
Swift 3
I had to do this yesterday when trying to delete a user. One thing to note is FIRAuthErrorCodeCredentialTooOld is now FIRAuthErrorCode.errorCodeRequiresRecentLogin
What I did was trigger a UIView to ask for log in details if that error is thrown. Since I was using email and password, that's what I collected from the user in my example.
private func deleteUser() {
//get the current user
guard let currentUser = FIRAuth.auth()?.currentUser else { return }
currentUser.delete { (error) in
if error == nil {
//currentUser is deleted
} else {
//this gets the error code
guard let errorCode = FIRAuthErrorCode(rawValue: error!._code) else { return }
if errorCode == FIRAuthErrorCode.errorCodeRequiresRecentLogin {
//create UIView to get user login information
let loginView = [yourLoginUIViewController]
self.present(loginView, animated: true, completion: nil)
}
}
}
Once I had the login information I ran this function to reauthenticate the user. In my case I ran it the loginView in the above code if the login in was successful:
func reauthenticateUserWith(email: String, password: String) {
FIRAuth.auth()?.signIn(withEmail: email, password: password) { (user, error) in
if error == nil {
//display UIView to delete user again
let deleteUserView = deleteUserView()
present(deleteUserView, animated: true, completion: nil)
} else {
//handle error
print(error!.localizedDescription)
}
}
}
The deleteUserView in my case calls deleteUser() on a button tap from the user. You can also use UIAlertController in place of the custom UIViews, but that's up to you.
Hope this helps.
Update for current Swift 5
let user = Auth.auth().currentUser
user?.delete { error in
if let error = error {
let authErr = AuthErrorCode(rawValue: error.code)
if authErr == .requiresRecentLogin {
// reauthenticate
}
// other error
} else {
// delete success
}
}
According to the documents, there is currently no way to check FIRAuthErrorCodeCredentialTooOld other than going through the deleting of the account or the other sensitive cases mentioned.
If you are like me and ended up here because you are trying to figure out how to handle removing someone from Auth and removing other user data in Cloud Firestore, Realtime Database, and/or Cloud Storage, then there is a better solution.
Check out the Delete User Data Extension from Firebase to handle this. In short, when a user profile is deleted from Auth, you can use this also to delete data associated with the uid from those other Firebase data storage tools.

Firebase authentication: linking multiple accounts in Swift

I've set up Firebase authentication for my iOS app using Facebook, Google & email/password sign in and it's all working fine. This authentication only happens when the user wants to access high-priority parts of my app (i.e. I don't require users to sign in to start using the app).
On app start up, I sign users in anonymously in the background and that's working fine too.
I've read the documentation but I'm struggling to understand the code required to enable me to link an anonymous account to a Facebook/email signed in account in the following flow:
new user opens app
user signed in anonymously in the background (new user.uid "A" created)
low priority data stored against anonymous user in Firebase realtime DB
user hits a high-priority area so needs to authenticate
user signs in using Facebook (new user.uid "B" created)
previous user.uid "A" needs to be linked to user.uid "B"
My method currently looks like this:
func signupWithFacebook(){
// track the anonymous user to link later
let prevUser = FIRAuth.auth()?.currentUser
FBSDKLoginManager().logInWithReadPermissions(["public_profile", "email"], fromViewController: self) { (result, error) in
if let token = result?.token?.tokenString {
let credential = FIRFacebookAuthProvider.credentialWithAccessToken(token)
FIRAuth.auth()?.signInWithCredential(credential, completion: { (user, error) in
if user != nil && error == nil {
// Success
self.success?(user: user!)
dispatch_async(dispatch_get_main_queue(), {
self.dismissViewControllerAnimated(true, completion: nil)
})
}
})
}
}
}
Any pointers to remove the confusion would be great.
UPDATE:
I've realised I was confused about the app logic because of users being created during testing. Instead of 2 separate users being created for the above scenario (one authenticated via Facebook and another anonymously), all that happens is that the original anonymous user.uid "A" is "linked" to some Facebook authentication credentials. In the Firebase console this is shown by the anonymous uid changing from anonymous to one with the Facebook logo next to it.
This is what my working method looks like:
func signupWithFacebook(){
FBSDKLoginManager().logInWithReadPermissions(["public_profile", "email"], fromViewController: self) { (result, error) in
if let token = result?.token?.tokenString {
let credential = FIRFacebookAuthProvider.credentialWithAccessToken(token)
FIRAuth.auth()?.currentUser!.linkWithCredential(credential) { (user, error) in
if user != nil && error == nil {
// Success
self.success?(user: user!)
dispatch_async(dispatch_get_main_queue(), {
self.dismissViewControllerAnimated(true, completion: nil)
})
} else {
print("linkWithCredential error:", error)
}
}
}
}
}
So your code follows the first 2 steps in this link. But the documentation explicity says not to call signInWithCredential but instead call
FIRAuth.auth()?.currentUser.linkWithCredential(credential) { (user, error) in
// ...
}
After getting your credential from Facebook's SDK.
Quote from link: "If the call to linkWithCredential:completion: succeeds, the user's new account can access the anonymous account's Firebase data."