iOS - AWS Cognito - Check if user already exists - ios

I want to allow a user to enter their email address/password in a field. Upon continuing, I want to run a check to see if that user already exists. If they do, log them in and continue with app, if they do not, move to account creation flow where they will be instructed to add name, phone number, etc.
I cannot for the life of me find documentation on how to log a user in using AWS Cognito. I should be able to pass email/pass in a call and get a response back that says User Exists/User does not exist or whatever! Am I missing something here?
Any help would be greatly appreciated. I've scoured the documentation..this is my last resort.

In the current SDK, calling getUser on your AWSCognitoIdentityUserPool just constructs the in-memory user object. To make the call over the network, you need to call the getSession method on the constructed user. Here's a Swift 3 method I wrote to check whether an email is available:
/// Check whether an email address is available.
///
/// - Parameters:
/// - email: Check whether this email is available.
/// - completion: Called on completion with parameter true if email is available, and false otherwise.
func checkEmail(_ email: String, completion: #escaping (Bool) -> Void) {
let proposedUser = CognitoIdentityUserPoolManager.shared.pool.getUser(email)
UIApplication.shared.isNetworkActivityIndicatorVisible = true
proposedUser.getSession(email, password: "deadbeef", validationData: nil).continueWith(executor: AWSExecutor.mainThread(), block: { (awsTask) in
UIApplication.shared.isNetworkActivityIndicatorVisible = false
if let error = awsTask.error as? NSError {
// Error implies login failed. Check reason for failure
let exceptionString = error.userInfo["__type"] as! String
if let exception = AWSConstants.ExceptionString(rawValue: exceptionString) {
switch exception {
case .notAuthorizedException, .resourceConflictException:
// Account with this email does exist.
completion(false)
default:
// Some other exception (e.g., UserNotFoundException). Allow user to proceed.
completion(true)
}
} else {
// Some error we did not recognize. Optimistically allow user to proceed.
completion(true)
}
} else {
// No error implies login worked (edge case where proposed email
// is linked with an account which has password 'deadbeef').
completion(false)
}
return nil
})
}
For reference, my ExceptionString enum looks like this:
public enum ExceptionString: String {
/// Thrown during sign-up when email is already taken.
case aliasExistsException = "AliasExistsException"
/// Thrown when a user is not authorized to access the requested resource.
case notAuthorizedException = "NotAuthorizedException"
/// Thrown when the requested resource (for example, a dataset or record) does not exist.
case resourceNotFoundException = "ResourceNotFoundException"
/// Thrown when a user tries to use a login which is already linked to another account.
case resourceConflictException = "ResourceConflictException"
/// Thrown for missing or bad input parameter(s).
case invalidParameterException = "InvalidParameterException"
/// Thrown during sign-up when username is taken.
case usernameExistsException = "UsernameExistsException"
/// Thrown when user has not confirmed his email address.
case userNotConfirmedException = "UserNotConfirmedException"
/// Thrown when specified user does not exist.
case userNotFoundException = "UserNotFoundException"
}

Some clarification is in order. Cognito has several parts. The part that does "Authentication" (which is what you are talking about) is called "Cognito User Pools". Not to be confused with Cognito Federated Identity Pools.
With User Pools you can create usernames and password combinations with attributes, and these can be used to authenticate and deliver a persistent, cross device, Cognito Federated identity identityId to a user (across multiple devices).
Once logged in, the Federated Identity Pool is hooked to roles which can get your "Authorized" to use AWS services (like Dynamo DB etc).
It can be tricky to get all these parts working together and AWS has an online site called "Mobile Hub" that will build code for you and download an xcode project. This process configures the Federated Identity Pool and the User Pool correctly, and connects them all up to a set of example code.
Connecting the credentials provider to the user pool to the identity pool is a bit counterintuitive, but the AWSIdentityManager in the aws-mobilehub-helper-ios on github manages all that for you. So I would recommend starting with mobile hub on the console.
Cognito is a somewhat confusing system, here is a link to a brief powerpoint that hits the highlights of how it works (for people that can't understand the AWS docs (like me)).
With that said, "how to check if a user already exists?"
The most reasonable approach is to create the user (via signup), and get a reject if the name is in use, and suggest that your user try a different username. With respect to the email being in use, you will get that reject upon confirmation (signup sends confirmation id's by email and/or via text). This can be overridden to reclaim the email address, or you can do a test beforehand to see if the email is in use by attempting to log in and looking at the failure code.
you can fetch the user as the other answer suggests, however if you have established in user pools an alias for login (like email) you will find this problematic, because this just tells you if someone has the user name, not if someone is already using the email address, and you will get a reject later at confirmation time.

ListUsers is now a nice way to check for existing usernames.
https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_ListUsers.html
You can also look for existing emails, phone numbers, and other default attributes.
Here is a simple .NET example:
Dim userRequest = New ListUsersRequest With {
.UserPoolId = "poolId",
.Filter = "username = bob#email.com"
}
Dim response = amazonCognitoIdentityProviderClient.ListUsers(userRequest)
Debug.WriteLine(response.Users.Count)

Related

AWS Amplify iOS SDK : FederatedSignIn Failed to retrieve authorization token on Amplify.API.post

I've been working with the Amplify SDK to get federatedSignIn working with my iOS app with "Sign in with Apple" and Cognito to eventually make calls to API Gateway / Lambda functions.
TL;DR : My access token does not appear to be "automatically included in outbound requests" to my API as per the last paragraph of this section of the docs : Cognito User pool authorization
I have successfully authenticated using the tutorial found here Authentication Getting Started and other various Youtube videos on the Amazon Web Services channel.
Upon successful sign in through Apple I'm given an ASAuthorizationAppleIDCredential object. This contains the user field (token) which I pass to the Amplify.Auth class using the following Swift code :
func signIn (with userId: String)
{
guard
let plugin = try? Amplify.Auth.getPlugin(for: AWSCognitoAuthPlugin().key),
let authPlugin = plugin as? AWSCognitoAuthPlugin,
case .awsMobileClient (let client) = authPlugin.getEscapeHatch()
else
{
return
}
client.federatedSignIn(providerName: AuthProvider.signInWithApple.rawValue, token: userId) { (state, error) in
if let unwrappedError = error
{
print (unwrappedError)
}
else if let unwrappedState = state
{
print ("Successful federated sign in:", unwrappedState)
}
}
}
All appears to be successful and to double check I use the following bit of code to ensure I'm authorized :
func getCredentialsState (for userId:String)
{
let provider = ASAuthorizationAppleIDProvider()
provider.getCredentialState(forUserID: userId) { (credentialsState, error) in
if let unwrappedError = error
{
print (unwrappedError)
}
switch credentialsState
{
case .authorized:
print ("User Authorized")
case .notFound, .revoked:
print ("User Unauthenticated")
case .transferred:
print ("User Needs Transfer")
#unknown default:
print ("User Handle new use cases")
}
}
}
In the console I see "User Authorized" so everything appears to be working well.
However when I then go to make a call to Amplify.API.post I get the following error:
[Amplify] AWSMobileClient Event listener - signedOutFederatedTokensInvalid
Failed APIError: Failed to retrieve authorization token.
Caused by:
AuthError: Session expired could not fetch cognito tokens
Recovery suggestion: Invoke Auth.signIn to re-authenticate the user
My function for doing the POST is as follows :
func postTest ()
{
let message = #"{'message": "my Test"}"#
let request = RESTRequest (path: "/test", body: message.data(using: .utf8))
Amplify.API.post (request:request)
{
result in switch result
{
case .success(let data):
let str = String (decoding: data, as: UTF8.self)
print ("Success \(str)")
case .failure(let apiError):
print ("Failed", apiError)
}
}
}`
I then went into the API Gateway UI and changed the generated Method Request on my resource from AWS IAM to my Cognito User Pool Authorizer thinking this was the issue. I also changed the awsAPIPlugin authorizationType to "AMAZON_COGNITO_USER_POOLS" in my amplifyconfiguration.json file. This unfortunately did not have any affect.
I've seen posts such as this issue User is not created in Cognito User pool for users logging in with Google federated login #1937 where people discuss the problem of having to to use a web ui to bring up the social sign in. I understand that Apple will reject your app sometimes for this. Therefore this is not a solution.
I then found this post which seems to resolve the issue however this appears to use the old version of the SDK? Get JWT Token using federatedSignIn #1276
I'm not great with Swift (I'm still an Objective C expert, but am slowly learning Swift) so I'm uncertain which path to go here and whether this is actually a solution? It does seem to be quite more complicated than the function I have that does my POST? The RESTRequest does seem to be a simple and easy solution but I'm uncertain how to pass it the Authorization token (or even how to get the token if it is needed here).
However, everything I've read about the SDK is that the authorization should be handled automatically in the background according the docs in my first link above. Specifically pointed out, again, here : Cognito User pool authorization. The last paragraph here states 👍
With this configuration, your access token will automatically be included in outbound requests to your API, as an Authorization header.
Therefore, what am I missing here as this does not appear to automatically include my access token to my outbound requests to my API?

How can I devide users into different groups(Identity pools) automatically?

I'm using AWS Cognito in my iOS app to implement the user signup & signin functions. I used the official Amplify SDK DOCs(https://aws-amplify.github.io/docs/ios/authentication) as a reference, and the app works well. But, actually I want to give my users different access authority which can achieve different contents(like files in S3).
While the user signing up the app, they must choose a group. Based on the group, they are given different access authority.
I've read the SDK DOCs and developer guide but I haven't found a good way to implement this function.
Is there any function in cognito I can use to separate users into different Identity pools? Or can anybody show me some samples which allow users having different access authority.
AWSMobileClient.sharedInstance().signUp(username: "your_username",
password: "Abc#123!",
userAttributes: ["email":"john#doe.com", "phone_number": "+1973123456"]) { (signUpResult, error) in
if let signUpResult = signUpResult {
switch(signUpResult.signUpConfirmationState) {
case .confirmed:
print("User is signed up and confirmed.")
case .unconfirmed:
print("User is not confirmed and needs verification via \(signUpResult.codeDeliveryDetails!.deliveryMedium) sent at \(signUpResult.codeDeliveryDetails!.destination!)")
case .unknown:
print("Unexpected case")
}
} else if let error = error {
if let error = error as? AWSMobileClientError {
switch(error) {
case .usernameExists(let message):
print(message)
default:
break
}
}
print("\(error.localizedDescription)")
}
}
You don't have to put users in different identity pools. Cognito already has user groups that you can use to assign different roles.
I am not familiar with IOS but there should be an admin add user to group method that you can use.

Firebase verifying email while being logged in

I have the following logic in my iOS app:
User registers
Firebase sends an email confirmation
Returns to login screen
Now if the user logs in, without verifying the email, then we have a user session and isEmailVerified is false.
I only need to check the isEmailVerified in a certain point in the app.
Also I think signing the user in, checking the field and signing the user out would be bad practise.
I'd need to reauthenticate the user, what is the best way of doing this? How can I, after the user has logged in, switch the status of isEmailVerified?
Thanks
First, you need to have the email and password to create a credential. Your user already provided this on the login page... So the email and password to persistent storage on iOS. In Android, the equivalent would be SharedPreferences.
I do not code in iOS, but this will give you the idea for the logic.
Then, when you get to that point in your app where email verified is called:
if (user.isEmailVerified) == true {
// you do not need to hold the email and password in persistent storage anymore.
// go into your persistent storage and delete the data.
} else {
// get the email and password the user saved in persistent storage.
String email = persistentStorage.getEmail();
String password = persistentStorage.getPassword();
var user = firebase.auth().currentUser;
var credentials = firebase.auth.EmailAuthProvider.credential(email, password);
user.reauthenticate(credentials);
// then, when finished reauthenticating, check whether isEmailVerified() == true;
}

Can you only allow users with a specific email address to sign up and use your app

I currently have user authentication in my app using firebase and swift 3. Is there a way to only let users with a certain email address to sign up to use my app?
Essentially what you want to do is include a boolean test to determine whether the email text contains the domain you want.
Here is a function that determines whether or not the input text contains a set domain.
func isValidEmail(testEmail:String, domain:String) -> Bool {
let emailRegEx = "[A-Z0-9a-z._%+-]+#[\(domain)]+\\.[com]{3,\(domain.characters.count)}"
let emailTest = NSPredicate(format:"SELF MATCHES %#", emailRegEx)
let result = emailTest.evaluate(with: testEmail)
return result
}
Here's an example of me using it
let customDomain = "mycompanyname"
let test = "frank#mycompanyname.com"
if isValidEmail(testEmail: test, domain: customDomain) == true {
//Test succeedes... Here you would register the users as normal
print("register \(test)")
}else{
//Test fails... Here you might tell the user that their email doesn't check out
print("register failed")
}
Additionally, I would take a look at Apples App Distribution Guide for if you want to distribute your app locally to say an employee base.
Hope this helps.
You can do the following client and backend checks to enforce this:
On the client side, you can block sign-in when an invalid email domain is provided. If you are using some federated sign in with Google or Facebook, etc, you can on return check the currentUser.email and currentUser.delete the user if the email doesn't match your domain. Google also provides an 'hd' parameter to specify the user domain if you are using GSuite.
You can use Firebase Functions onCreate event to delete a user quickly every time one is created with an invalid email domain.
If you are using database rules, you can block access if the email doesn't match:
".read": "auth != null && auth.uid == $uid" && auth.token.email.matches(/.*#mydomain.com$/)
If you are using your own backend, when getting the ID token of a user, validate it, then get the email and make sure it matches your domain, if not, you can delete the user. The Firebase Admin SDKs provide the ability to verify an ID token and delete a user by UID.

AWS Cognito check and get users

I'm building an iOS App that is using Amazon MobileHub. Currently it's associated with Cognito and the sign up and sign in flow works great.
What I'm trying to achieve is for one user to be able to add another user as a friend so to speak. To make that possible, I need to check if a user with a specific username exists and if it does, get some attributes such as the name of that target user.
I've tried to use the get user > get details function but it gives me an error Authentication delegate not set.
Here's the code I used:
var pool: AWSCognitoIdentityUserPool?
let user = pool?.getUser(usernameField.text!)
self.pool = AWSCognitoIdentityUserPool.init(forKey: AWSCognitoUserPoolsSignInProviderKey)
user?.getDetails().continueWith { (task: AWSTask<AWSCognitoIdentityUserGetDetailsResponse>) -> Any? in
if let error = task.error as NSError? {
print("ERROR: " + error.localizedDescription)
return ""
}
print(task.result)
return ""
}
An approach I thought of was to store the username and the attributes I want to access to DynamoDB and then access it there but that will just create double unnecessary entries.
The issue you'll run into is that user attributes aren't publicly visible. Only the user who has signed in can call GetUser. If it's a back end process, you could do this via the AdminGetUser operation, but this looks client side so I wouldn't recommend that. The only real way around this would be to do what you suggested at the bottom of your post, ultimately.

Resources