iOS Firebase Twitter Login asks for Permission Every Time - swift

I am able to create an account and login via Twitter; however, with each login attempt the user is taken to a Twitter web page where they have to login with their credentials. Given this happens every time, it negates the value of this kind of social login. I have Facebook and Google logins working without this issue on Firebase. Below is the code used to login:
private func loginWithTwitter() {
TWTRTwitter.sharedInstance().logIn { (session, error) in
guard error == nil && session != nil else {
self.spinner.dismiss()
Utilities.displayAlert(title: "Login Error", msg: error?.localizedDescription ?? "Could not login with Twitter at this time.", controller: self)
return
}
let credential = TwitterAuthProvider.credential(withToken: session!.authToken, secret: session!.authTokenSecret)
Auth.auth().signInAndRetrieveData(with: credential, completion: { (result, error) in
if let error = error {
self.spinner.dismiss()
self.present(self.sharedManager.getAlertWith(title: "Login Error", andMsg: error.localizedDescription), animated: true)
return
}
self.loadUserInfo()
})
}
}
I should also add that I do have the use for logins box checked on the Twitter Developers page.
UPDATE:
Based on the post of Pratik below and here is a modified version that does seem to work. However, I am not sure if it's the right long-term answer. I wonder about token expiration for example. Thoughts on if there is a better way than this?
private func loginWithTwitter() {
let token = TWTRTwitter.sharedInstance().sessionStore.session()?.authToken
let secret = TWTRTwitter.sharedInstance().sessionStore.session()?.authTokenSecret
guard token != nil && secret != nil else {
self.present(self.sharedManager.getAlertWith(title: "Login Error", andMsg: "Could not login with Twitter. Please try again."), animated: true)
return
}
let credential = TwitterAuthProvider.credential(withToken: token!, secret: secret!)
Auth.auth().signInAndRetrieveData(with: credential, completion: { (result, error) in
if let error = error {
self.spinner.dismiss()
self.present(self.sharedManager.getAlertWith(title: "Login Error", andMsg: error.localizedDescription), animated: true)
return
}
self.loadUserInfo()
})
}

I'm not sure with will help or not, but im using twitter login in my app and once i authenticate with twitter i used below function to when user open app again for validation.
func silentLoginWithTwitter()
{
let client = TWTRAPIClient.withCurrentUser()
let request = client.urlRequest(withMethod: "GET", urlString: "https://api.twitter.com/1.1/account/verify_credentials.json", parameters: ["include_email": "true", "skip_status": "true"], error: nil)
client.sendTwitterRequest(request, completion: { (response:URLResponse?, data:Data?, error:Error?) in
if error == nil
{
// Open main page of application
}else
{
print("Error: Twitter : \(String(describing: error))")
// Open Login page again
}
})
}
OR you can check session auth
if let authSession = TWTRTwitter.sharedInstance().sessionStore.session()?.authToken {
TWTRTwitter.sharedInstance().sessionStore.isValidOauthToken(authSession)
}
Hope this will help

Related

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 do I get my AWS Cognito access token for updating user attributes?

I have the structure set up for updating user attributes, in this case the preferred username to use as an alias for signing in.
var attributes = [AWSCognitoIdentityUserAttributeType]()
let prefUsername = AWSCognitoIdentityUserAttributeType();
prefUsername?.name = "preferred_username";
prefUsername?.value = usernameField.text!;
attributes.append(prefUsername!);
let attributesRequest = AWSCognitoIdentityProviderUpdateUserAttributesRequest();
attributesRequest.userAttributes = attributes;
idProvider?.updateUserAttributes(attributesRequest)
Only thing I have no idea how to do is get the access token. I've looked in as much documentation as I could think of but I had no luck finding place to get access token.
You can use the api to initiate auth and get an AccessToken from the AuthenticationResult.
https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_InitiateAuth.html
/// Function to retreive the current token for the logged in user.
///
/// - Parameter completion: A completion block with an error or the token. Called back on the main thread.
public func getJWT(completion: #escaping((_ error: Error?, _ token: AWSCognitoIdentityUserSessionToken?) -> Void)) {
guard let user = self.pool.currentUser() else {
let nsError = NSError(domain: "JWT Error", code: 500, userInfo: ["message": "No Logged in user"])
completion(nsError, nil)
return
}
user.getSession().continueWith { (task) -> Any? in
DispatchQueue.main.async {
if let error = task.error {
completion(error, nil)
}else if let token = task.result?.idToken {
completion(nil, token)
}else {
completion(nil, nil)
}
}
}
}
Where self.pool is the AWSCognitoIdentityUserPool you hopefully set up correctly.
You would have to authenticate first to establish a session with Cognito User Pools. That session would contain an access token which you can then pass to every subsequent request. I see you are using the low level SDK methods. Here is a sample in swift for SignIn:
https://github.com/awslabs/aws-sdk-ios-samples/tree/master/CognitoYourUserPools-Sample/Swift

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.

Requesting user's email from Twitter

I am trying to request a user's email address from Twitter and put it into Firebase. I got my app whitelisted by Twitter and activated it in the apps.twitter.com portal. As per the Fabric documentation here, I wrote this code out:
let twitterEmailClient = TWTRAPIClient.clientWithCurrentUser()
let twitterEmailRequest = twitterEmailClient.URLRequestWithMethod("GET", URL: "https://api.twitter.com/1.1/account/verify_credentials.json", parameters: ["include_email": "true", "skip_status": "true"], error: nil)
twitterEmailClient.sendTwitterRequest(twitterEmailRequest, completion: { (TWTREmailClientResponse: NSURLResponse?, TWTREmailClientEmail: NSData?, TWTREmailClientError: NSError?) in
if TWTREmailClientError != nil {
print("Twitter Email Client Error - \(TWTREmailClientError!.code): \(TWTREmailClientError!.localizedDescription)")
} else if TWTREmailClientResponse == nil {
print("Twitter Email Client Error - valid connection not available")
} else if TWTREmailClientEmail != nil {
print("Twitter Client Email - \(String(data: TWTREmailClientEmail!, encoding: NSUTF8StringEncoding))")
FIRAuth.auth()?.currentUser?.updateEmail("\(TWTREmailClientEmail)", completion: { (updateEmailError: NSError?) in
if updateEmailError != nil {
print("Set Email from Twitter Error - \(updateEmailError)")
}
})
}
})
Again, as per Fabric's docs, I should get a JSON result including the 'email'. Here is the result:
Twitter Client Email -
Optional("{\"id\":560366005,\"id_str\":\"560366005\",\"name\":\"Dan
Levy\",\"screen_name\":\"DanLevy114\",\"location\":\"Buffalo,
NY\",\"description\":\"Florida Tech \'20, Amherst \'16, iOS
Developer\",\"url\":\"https:\/\/t.co\/KOtATAEV3X\",\"entities\":{\"url\":{\"urls\":[{\"url\":\"https:\/\/t.co\/KOtATAEV3X\",\"expanded_url\":\"http:\/\/Instagr.am\/danlevy114\",\"display_url\":\"Instagr.am\/danlevy114\",\"indices\":[0,23]}]},\"description\":{\"urls\":[]}},\"protected\":false,\"followers_count\":292,\"friends_count\":196,\"listed_count\":4,\"created_at\":\"Sun
Apr 22 15:20:46 +0000
2012\",\"favourites_count\":1151,\"utc_offset\":-10800,\"time_zone\":\"Atlantic
Time
(Canada)\",\"geo_enabled\":true,\"verified\":false,\"statuses_count\":1305,\"lang\":\"en\",\"contributors_enabled\":false,\"is_translator\":false,\"is_translation_enabled\":false,\"profile_background_color\":\"C0DEED\",\"profile_background_image_url\":\"http:\/\/pbs.twimg.com\/profile_background_images\/743634202\/69dd45bc569542274b017cc25c1e464d.png\",\"profile_background_image_url_https\":\"https:\/\/pbs.twimg.com\/profile_background_images\/743634202\/69dd45bc569542274b017cc25c1e464d.png\",\"profile_background_tile\":false,\"profile_image_url\":\"http:\/\/pbs.twimg.com\/profile_images\/745047852796289024\/BWFfrEoI_normal.jpg\",\"profile_image_url_https\":\"https:\/\/pbs.twimg.com\/profile_images\/745047852796289024\/BWFfrEoI_normal.jpg\",\"profile_banner_url\":\"https:\/\/pbs.twimg.com\/profile_banners\/560366005\/1466468226\",\"profile_link_color\":\"0084B4\",\"profile_sidebar_border_color\":\"FFFFFF\",\"profile_sidebar_fill_color\":\"33FF33\",\"profile_text_color\":\"E05151\",\"profile_use_background_image\":false,\"has_extended_profile\":true,\"default_profile\":false,\"default_profile_image\":false,\"following\":false,\"follow_request_sent\":false,\"notifications\":false}")
I logged in with my Twitter account and an email did not appear. I know I have an email associated with my Twitter account. Any ideas?
I had the same problem with Twitter. First of all change Permissions in dev.twitter to "Read only".
Next, use this code to login with Twitter:
Twitter.sharedInstance().logInWithCompletion() { session, error in
if let session = session {
let credential = FIRTwitterAuthProvider.credentialWithToken(session.authToken, secret: session.authTokenSecret)
FIRAuth.auth()?.signInWithCredential(credential) { (user, error) in
if let error = error {
Alert.sharedInstance.showAlert("Error", message: error.localizedDescription)
return
}
//Logged!
}
} else {
Alert.sharedInstance.showAlert("Error", message: error!.localizedDescription)
}
}
This work without request to Twitter.
API Key and API Secret in Firebase Console get from dev.twitter.