Microsoft Azure mobile SDK custom provider login IOS - swift

I have followed this tutorial to implement a login with custom provider on an Azure mobile app. The backend works perfectly but when I try to login to my new custom controller I'm not able to do it. It's possible to implement it with Xamarin and also with java Android but is no way to do it with Objective C or Swift.
The object MSClient on Microsoft Azure mobile SDK only has two login implementations.
I have tried both but without luck, the callback always returns an empty client.
I also have tried to store the token created by own API use it for login call but without luck again.
Here is my Swift code:
let client = MSClient(applicationURLString: "https://myApp.azurewebsites.net")
client.login(withProvider: "custom", urlScheme: "myApp", parameters: ["username": "pau", "password": "123456"], controller: self, animated: true) {user, error in
print("USER", user)
print("ERROR", error)
}

We found a solution, it's really easy but the msclient documentation is not clear enough. You just need to pass whatever you need (i.e: username,password) as dictionary in token parameter.
Client.msClient.login(withProvider: "auth", token: params, completion: {user, error in
print("USER", user)
print("ERROR", error)
print("USER ID", user?.userId)
print("TOKEN", user?.mobileServiceAuthenticationToken)
if let user: MSUser = user {
guard let username: String = user.userId else { return }
guard let token: String = user.mobileServiceAuthenticationToken else { return }
Client.username = username
Client.msClient.currentUser = user
completion(true)
}else {
completion(false)
}
})

Related

Is there a credential for Custom Auth System in Firebase?

I'm signing users into my app with a custom token using their WordPress ID.
Auth.auth().signIn(withCustomToken: customToken ?? "") { (user, error) in
// ...
}
That works ok but I need to update the rules which will keep users from writing to the database. This is counterproductive because I want to track the products they view etc...
I want to sign the user anonymously when the app opens.
Auth.auth().signInAnonymously() { (authResult, error) in
// ...
}
I need to link the anonymous user to the WordPress user. Unfortunately, according to the documentation https://firebase.google.com/docs/auth/ios/account-linking the user needs to be linked with a credential but the credentials provided are GoogleAuthProvider, FacebookAuthProvider and EmailAuthProvider and several others but no Custom Provider.
Is it possible to create a credential out of the custom token?
The examples you see in the documentation are not (in theory) the only options. Look at the API documentation for link():
Associates a user account from a third-party identity provider with this user and returns additional identity provider data.
It's signature in Swift is this:
func link(with credential: FIRAuthCredential, completion: ((AuthDataResult?, Error?) -> Void)? = nil)
It takes any FIRAuthCredential, of which there are many subclasses available. One of those is OAuthCredential. If you have custom token, try creating a credential with OAuthProvider.crediential() and link that to the account.
Following Doug Stevenson and Frank van Puffelen comments, I was able to link the anonymous user account using a credential.
I have my original customToken
guard let customToken = result?.data as? String else { return }
Because the user signs in now anonymously when the app first opens, I now have a user.
guard let authUser = Auth.auth().currentUser else { return }
I get the provider id from the user.
let providerId = authUser.providerID
With the provider id now I can get a credential using OAuthProvider
let credential = OAuthProvider.credential(withProviderID: providerId, accessToken: customToken)
Now I can link the user to the anonymous user.
authUser.link(with: credential) { (result, error) in

iOS Firebase Twitter Login asks for Permission Every Time

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

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.

Validate AWS Cognito Token with Swift

I have nearly completed the process for a developer authenticated sign in using AWS. I cannot seem to authenticate the back-end token that I receive and cannot seem to find any current implementations that are performing developer authentication via a third-party back-end. The error that I get is listed below.
As of right now my code looks like this:
Class containing Custom identity provider:
import Foundation
import AWSCognitoIdentityProvider
class CustomIdentityProvider: NSObject, AWSIdentityProviderManager {
var tokens: [NSString: NSString]?
init(tokens: [NSString: NSString]) {
self.tokens = tokens
}
#objc func logins() -> AWSTask<NSDictionary> {
return AWSTask(result: tokens! as NSDictionary)
}
}
AWS-APIManager.swift {snippet}
/* obtained cognito token from my back-end via getOpenIdTokenForDeveloperIdentity*/
/* From here I my app receives an IdentityId and Token */
let client_cognito_id = String(describing: valid_response)
let session_token = json.dictionaryValue["Token"]!
let login_with_amazon = NSString(string: "cognito-identity.amazonaws.com")
let token = NSString(string: String(describing: session_token))
let customProviderManager = CustomIdentityProvider(tokens: [login_with_amazon: token])
let credentialsProvider = AWSCognitoCredentialsProvider(
regionType: self.AWS_REGION,
identityPoolId: "us-east-1:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
identityProviderManager: customProviderManager
)
credentialsProvider.setIdentityProviderManagerOnce(customProviderManager)
credentialsProvider.getIdentityId().continue ({ (task: AWSTask!) -> AnyObject! in
if (task.error != nil) {
print("Error!!!: " + (task.error?.localizedDescription)!)
} else {
// the task result will contain the identity id
let cognitoId = task.result
print(cognitoId)
print("SUCCESS!!!")
}
return nil
})
}
For some odd reason odd reason I can cannot authenticate the token that I have received. I get an error "Invalid login token. Can't pass in a Cognito token.". I've tried to follow the documentation and piece together working code that I have found literally hundreds of sources and cannot seem to be able to move past this part of the authentication process. Any help would be greatly appreciated. Thanks!
I believe the issue here is that although you are supplying the token, you are not setting the identity id that you are getting from your backend. As such, it is calling GetId with a Cognito OpenIdConnectToken, which is not supported.
The simplest client implementation of Developer Authenticated Identities is to extend AWSCognitoCredentialsProviderHelper
Apologies for providing this in Objective C instead of Swift. In your implementation just override the token method.
- (AWSTask<NSString *> *)token {
//get the identity id and token from your server
//You can use AWSTaskCompletionSource if you don't have it and need to get it asynchronously.
//Once you have this information, simply set the identity id and return the token
self.identityId = identityId;
return [AWSTask taskWithResult:token];
}

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."