Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to verify if a user with a given email already exists in User Pool? #1067

Open
wzup opened this issue Jun 20, 2018 · 45 comments
Open

How to verify if a user with a given email already exists in User Pool? #1067

wzup opened this issue Jun 20, 2018 · 45 comments
Labels
Auth Related to Auth components/category Cognito Related to cognito issues feature-request Request a new feature Service Team Issues asked to the Service Team

Comments

@wzup
Copy link

wzup commented Jun 20, 2018

Do you want to request a feature or report a bug?

Question

What is the expected behavior?

Check is user exists

example

Is there such a method?

Auth.verifyIfUserExists({
    email: 'foo@bar.com'
})
.then(res => {
    // user with this email already exists
})

Because now I can find out if a user exists ONLY during signUp action. But I want to check it before I do signUp. Because if a user doesn't exist it will be created right off the bat. And it is not what expected

Auth.signUp({
        username,
        password,
        attributes: {
        },
    })
    .then(data => console.log(data))
    .catch(err =>{
        // User exists !!
    });
@haverchuck haverchuck added the feature-request Request a new feature label Jun 20, 2018
@haverchuck
Copy link
Contributor

@wzup

There is not function that does this and only this; however, I think if you use the confirmSignUp function and are using email as an alias you will get back an AliasExistsException error.

In any case I am marking this as a feature request, as it seems useful.

Thanks for your feedback.

@michelmob
Copy link

michelmob commented Jun 21, 2018

I did:

 userExist(userName: string) {
      return Auth.signIn(userName, '123');
    }
and

userExist(email: string) {
    return this.cognitoService.userExist(email.toLowerCase()).then(res => {
        return false;
    }).catch(error => {
        const code = error.code;
        console.log(error);
        switch (code) {
            case 'UserNotFoundException':
                return !this.redirectToRegister(email);
            case 'NotAuthorizedException':
                return true;
            case 'PasswordResetRequiredException':
              return !this.forgotPassword(email);
            case 'UserNotConfirmedException':
                return !this.redirectToCompleteRegister(email);
            default:
                return false;
        }
    });
    }

@wzup
Copy link
Author

wzup commented Jun 21, 2018

// one
userExist( userName: string ) {
    return Auth.signIn( userName, '123' );
}

// two
userExist( email: string ) {
    return this.cognitoService.userExist( email.toLowerCase() )
        .then( res => {
            return false;
        } )
        .catch( error => {
            const code = error.code;
            console.log( error );
            switch ( code ) {
                case 'UserNotFoundException':
                    return !this.redirectToRegister( email );
                case 'NotAuthorizedException':
                    return true;
                case 'PasswordResetRequiredException':
                    return !this.forgotPassword( email );
                case 'UserNotConfirmedException':
                    return !this.redirectToCompleteRegister( email );
                default:
                    return false;
            }
        } );
}

@wzup
Copy link
Author

wzup commented Jun 21, 2018

@michelmob , thank you.

One question though.
What is this in your example? Where does .cognito live?

return this.cognitoService.userExist( email.toLowerCase() )

@michelmob
Copy link

michelmob commented Jun 21, 2018 via email

@haverchuck haverchuck added the Cognito Related to cognito issues label Jun 22, 2018
@wzup
Copy link
Author

wzup commented Jun 26, 2018

@michelmob
@haverchuck

But what if credentials are correct?

Auth.signIn(userName, '123');

Then a user that wants to sign up will be suddenly signed in instead.

This is definitely bad experience from aws-amplify.

@wzup
Copy link
Author

wzup commented Jun 27, 2018

@haverchuck

Here is why requested method I ask for is important.

With current authflow we have to signin and then signout a user just to check if an email (username) already exists:

// 1. In order to check if an email already exists in Cognito we have to call .signIn.
// Because there is no special method for that, like Auth.doesUsernameExists(username)
Auth.signIn( email, password )
    .then( user => {
        // 2. I a user found, they get signin
        // You have to log out a user if found
        // Security vulnerability
        return Auth.signOut();
    } )
    .then( res => {
        // 3. Here we show a user that email is taken
        // After logging them in and logging them out. LOL
        this.setState((state, props) => {
            return {
                emailError: 'This email is already taken'
            };
        });
        return;     
    } )
    .catch( err => {
        switch ( err.code ) {
            case 'UserNotFoundException':
                // Only here, in .catch error block we actually send a user to sign up
                return this.signUp();
            case 'NotAuthorizedException':
                return true;
            case 'PasswordResetRequiredException':
                return false;
            case 'UserNotConfirmedException':
                return this.props.navigation.navigate('ConfirmRegistrationScreen', {
                    username: email,
                });
            default:
                return false;
        }
    } )

@tcchau
Copy link

tcchau commented Jul 5, 2018

@wzup The sign-in workaround might work just because User Pools require passwords that are 6-characters are longer so in practice, there will never be a user account whose password is '123'.

@haverchuck However, even if this workaround works, it's really bad that the API doesn't support checking the existence of a user name directly. I've been working with Cognito for two years and this feature already exists as a request, but hasn't been implemented yet, along with the ability for an administrator to reset an account's password. The combination of these two problems makes it quite difficult to build enterprise applications.

@nabarunchakma
Copy link

If you can change your User Pool, you can achieve email uniqueness following the step at Forcing Email Uniqueness in Cognito User Pools section in Authentication

It is just below the Sing Up section.

@cor
Copy link

cor commented Sep 9, 2018

Are there any updates on this issue?

@heri16
Copy link

heri16 commented Nov 7, 2018

@wzup

There is not function that does this and only this; however, I think if you use the confirmSignUp function and are using email as an alias you will get back an AliasExistsException error.

In any case I am marking this as a feature request, as it seems useful.

Thanks for your feedback.

I looked at the amplify source code.

Auth.confirmSignup() calls cognitoUser.confirmRegistration(code, forceAliasCreation) which then calls this API: https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_ConfirmSignUp.html

The right way to do this (without signIn & signOut) according to the project contributors seems to be this:

const code = '000000'
Auth.confirmSignUp(username, code, {
    // If set to False, the API will throw an AliasExistsException error if the phone number/email used already exists as an alias with a different user
    forceAliasCreation: false
}).then(data => console.log(data))
  .catch( err => {
        switch ( err.code ) {
            case 'UserNotFoundException':
                return true;
            case 'NotAuthorizedException':
                return false;
            case 'AliasExistsException':
                // Email alias already exists
                return false;
            case 'CodeMismatchException':
                return false;
            case 'ExpiredCodeException':
                return false;
            default:
                return false;
        }
    } )

@stale
Copy link

stale bot commented Jun 15, 2019

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the pending-close-response-required A response is required for this issue to remain open, it will be closed within the next 7 days. label Jun 15, 2019
@sammartinez sammartinez removed the pending-close-response-required A response is required for this issue to remain open, it will be closed within the next 7 days. label Jun 17, 2019
@saikishored
Copy link

It's been a long time since this thread is closed, but I could find a right solution in Cognito SDK.
You may use listUsers function to get user details:
let params = {
UserPoolId: "eu-central-1_xxxxxxx",
Filter: "email="abc@xyz.com""
};
cognito.listUsers(params,function(err,data){
// data object contains all the attributes of the user
}

Note: Filter can be of any other attributes in user pool and "=" can be replaced by few other operators. Please refer "Cognitoidentityserviceprovider SDK

Cognitoidentityserviceprovider SDK
" for more details

@logi-dc
Copy link

logi-dc commented Dec 19, 2019

With the above solution when you try to add the credentials to the CISP it doesn't work

@prog585
Copy link

prog585 commented Dec 19, 2019

Amplify's default way to handle this scenario is at signUp call. If user exists it will throw UsernameExistsException exception. I have tested it with Usernames
.

@logi-dc
Copy link

logi-dc commented Dec 19, 2019

I want to check if a userExists in the pool at a different time to signup is there a method that allows that

@prog585
Copy link

prog585 commented Dec 19, 2019

The default way I mentioned above (as per my research which was a thorough exercise), if otherwise you want to check before final signUp then that can be achieved by using some admin functions (please see the list of admin functions on cognito sdk docs). In calling admin functions you need to think about security perspectives though.

@logi-dc
Copy link

logi-dc commented Dec 19, 2019

okay thank you

@akeditzz
Copy link

Hi, i there any update on this issue?

@anees17861
Copy link

anees17861 commented Sep 23, 2020

@akeditzz confirm signup test works perfectly as mentioned previously in this thread. just pass an obvious wrong otp and it will give error. one error is user not exist or something like that. if you got that error then email doesn't exist in pool. this only works if you use email as an alias for logging in and was verified though

@akeditzz
Copy link

@anees17861 dose it not work for mobile number ?

@anees17861
Copy link

@akeditzz it'll work, i use mobile number personally but have tested both. but same rules apply as for email. if signed up it needs to be verified or it won't work properly. Personally in my case if something goes wrong and user wasn't able to confirm, i just make him signup again with another username (random uuid in my case). So there will be two accounts created in cognito pool but only one will be confirmed and thus used for future login.

@anees17861
Copy link

@akeditzz if you are using mobile number remember to use e164 format only. Country specific phone strings will give you issues

@akeditzz
Copy link

Thank you @anees17861 i will try.

@PavolHlavaty
Copy link

When I try to use solution proposed by @heri16. I always get error code ExpiredCodeException with message Invalid code provided, please request a code again.

@anees17861
Copy link

@PavolHlavaty are you sending the code as empty string or a non numerical value? Cognito may be doing format check before proceeding. I send only '00' and it works perfectly.

@PavolHlavaty
Copy link

@anees17861 I am sending same string as @heri16 in his answer.

@anees17861
Copy link

anees17861 commented Oct 3, 2020

@PavolHlavaty have you checked for both signed up and non signed up user? And what is the type of username you've selected. When I had set up, i was provided with 2 options. one is a username with email and phone as alias and other is using no separate username but rather directly phone number/email as username. I had chosen the first one. Maybe that makes a difference

@lfur
Copy link

lfur commented Jan 25, 2021

I always get error code ExpiredCodeException with message Invalid code provided, please request a code again.

@PavolHlavaty I was experiencing the same issue as you until reading these docs: https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pool-managing-errors.html

Basically the PreventUserExistenceErrors option has to be disabled (ie. enable user existence errors) for your app client, where it was enabled by default in my config.

It can be changed in Cognito console with:
User pools > Selecting your user pool > General settings > App clients > Show Details > Security configuration > Legacy > Save app client changes

With my default setup of amplify I have two app clients (native and web), and ended up changing it for both of them, although don't know if it that was necessary.

@fkunecke
Copy link

fkunecke commented Jun 4, 2021

Came across this issue and @heri16's solution worked perfect. If you're looking for something to just paste in and go, here's a snippet:

const usernameAvailable = async (username) => {
  // adapted from @herri16's solution: https://github.com/aws-amplify/amplify-js/issues/1067#issuecomment-436492775
  try {
    const res = await Auth.confirmSignUp(username, '000000', {
      // If set to False, the API will throw an AliasExistsException error if the phone number/email used already exists as an alias with a different user
      forceAliasCreation: false
    });
    // this should always throw an error of some kind, but if for some reason this succeeds then the user probably exists.
    return false;
  } catch (err) {
    switch ( err.code ) {
      case 'UserNotFoundException':
          return true;
      case 'NotAuthorizedException':
          return false;
      case 'AliasExistsException':
          // Email alias already exists
          return false;
      case 'CodeMismatchException':
          return false;
      case 'ExpiredCodeException':
          return false;
      default:
          return false;
    }
  }
}

To use:

const available = await usernameAvailable(emailAddress);
console.log(`user ${available ? 'available' : 'not available'}`);

@avi-l
Copy link

avi-l commented Jul 15, 2021

@fkunecke

I tried your example today, but I get 'ExpiredCodeException' even when the user doesn't exist in the user pool

@fkunecke
Copy link

fkunecke commented Jul 15, 2021

@avi-leeker You need to follow what @lfur mentioned in your cognito settings (disable PreventUserExistenceErrors).
Also I am no longer using this method and have since migrated my email checking code into a lambda. Here's what I use now:

const fetch = require('node-fetch');
const aws = require('aws-sdk');
const APP_CLIENT_ID = process.env.APP_CLIENT_ID;

const checkEmailAvailability = async (email) => {
  let available = false;
  let code = '';
  let message = "An error occurred, please try again.";
  let response;

  try {
    var requestOptions = {
      method: 'GET',
      redirect: 'follow'
    };

    const zerobounceApiKey = 'API_KEY';

    if (process.env.ENV === 'master') {
      response = await fetch(`https://api.zerobounce.net/v2/validate?api_key=${zerobounceApiKey}&email=${encodeURIComponent(email)}\n&ip_address=`, requestOptions).then(res => res.json());
      console.log('got response from zerobounce');
      console.log(response);
      const deliverable = response.status === 'valid' || response.status === 'catch-all';
      if (!deliverable) {
        console.log('this email address is not deliverable according to zerobounce');
        available = false;
        message = "We can't deliver emails to this address. Please try a different address.";
        code = 'EmailNotDeliverableException';
        return;
      }
    }

    // check with the cognito pool to see if the username is already registered
    var payload = {
      ClientId: APP_CLIENT_ID,
      ConfirmationCode: '0000',
      Username: email, /* required */
    };

    console.log('payload for cognito check');
    console.log(payload);

    response = await (new aws.CognitoIdentityServiceProvider()).confirmSignUp(payload).promise();

    console.log('got response from cognito check');
    console.log(response);
  } catch (e) {
    console.log('caught error in checkEmailAvailability');
    console.log(e);

    code = e.code;
    switch (e.code) {
      case 'UserNotFoundException':
        available = true;
        message = 'Email is available';
        break;
      case 'NotAuthorizedException':
        available = false;
        message = 'Email already registered.';
        break;
      case 'AliasExistsException':
        // Email alias already exists
        available = false;
        message = 'Email already registered.';
        break;
      case 'CodeMismatchException':
        available = false;
        message = 'Email already registered.';
        break;
      case 'ExpiredCodeException':
        available = false;
        message = 'Email already registered.';
        break;
      default:
        available = false;
        message = 'An error occurred, please try again.';
        break;
    }
  } finally {
    return { available, message, code };
  }
};
exports.checkEmailAvailability = checkEmailAvailability;

You will need to put this in a lambda and modify your CF template so it gets the cognito app client ID parameter.

I am also using ZeroBounce to check if the email is deliverable (but I only run it on production). I have had about 10% of my users misspell their emails (my favorite was @33gmail.com). I've had pretty good results with their api. You can remove that part if you want.

@avi-l
Copy link

avi-l commented Jul 19, 2021

@fkunecke Thanks! I actually realized sometime after my reply I needed to disable PreventUserExistenceErrors. After doing that I was able to use the below example with success:

export const signIn = async (username, password) => {
    const user = await Auth.signIn(username, password);
    return user;
}

export const cognitoEmailUsed = async (email) => {
    return signIn(email, '123')
        .then(res => {
            console.log(JSON.stringify(res))
            return false;
        })
        .catch(error => {
            const code = error.code;
            console.error(error);
            switch (code) {
                case 'UserNotFoundException':
                    return false;
                case 'NotAuthorizedException':
                    return true;
                case 'PasswordResetRequiredException':
                    return true;
                case 'UserNotConfirmedException':
                    return true;
                default:
                    return false;
            }
        });
}

await cognitoEmailUsed(email)
                .then((res) => {
                    console.log(res)
                    return res;
                })
                .catch(() => {
                    return false;
                })

@paulsjohnson91
Copy link

paulsjohnson91 commented Sep 15, 2021

So it would appear Amazon have changed something in either cognito or amplify that has broken the sign up process in my production app which meant I had to create an emergency update to allow sign ups.
Previously using

export const cognitoEmailUsed = async (email) => {
    return signIn(email, '123')
        .then(res => {
            console.log(JSON.stringify(res))
            return false;
        })
        .catch(error => {
            const code = error.code;
            console.error(error);
            switch (code) {
                case 'UserNotFoundException':
                    return false;
                case 'NotAuthorizedException':
                    return true;
                case 'PasswordResetRequiredException':
                    return true;
                case 'UserNotConfirmedException':
                    return true;
                default:
                    return false;
            }
        });
}

Would allow you to work out if a user exists or not, however I'm now getting the same NotAuthoriedException for both use cases so this workaround no longer works.

It's been 3 years since this issue was raised and Amazon have shown no interest in fixing it. I think the only way to safely do this at this point is to write a pre signup lambda to verify instead.

@ashika01
Copy link
Contributor

ashika01 commented Oct 6, 2021

I think during the time the issue was raised there lambda trigger through amplify might not be introduced. But my suggestion would be to write a pre-signup lambda, is there something that would stop you from not wanting to use the lambda?

@sammartinez sammartinez added the Service Team Issues asked to the Service Team label Oct 27, 2021
@ajgoldenwings
Copy link

I found that none of these answers are acceptable. If you hit these endpoints several times, you will encounter LimitExceededException. Is there a lambda example that exists as @ashika01 commented? Use a separate User Entity? Use a standard User Group and check if member? Become a contributor to this git repository ;)

@aayoushee
Copy link

how about using the admin getuser Cognito call to check if the user exists for that username ?

@abdallahshaban557 abdallahshaban557 added the Auth Related to Auth components/category label Jun 3, 2022
@NoxinDVictus
Copy link

Is there an update on this issue? Tried all the solutions above and nothing worked. This issue has been open for too long. Will Amazon ever consider implementing this feature?

@tcchau
Copy link

tcchau commented Aug 19, 2022

Is there an update on this issue? Tried all the solutions above and nothing worked. This issue has been open for too long. Will Amazon ever consider implementing this feature?

The only way I’ve found to do this consistently in a way that’s not too much of a hack is the following:

  • allow email addresses to be used as an alias for username and use some custom method to create the actual username, e.g. UUID
  • Use the listUser API call with the candidate new user account’s email address
  • If no results are returned then the email address does not exist for any user

The consequence of this is email addresses must be unique per account.

The reason this will work and not getUser is that getUser only returns accounts with verified email address. To be complete you need to check to see if the user has tried to register for an account in the past but never confirmed their email address.

Hope this helps a bit.

Clinton

@oemer-aran
Copy link

This is really frustrating.
It's a really bad user experience if the user can only see his username is already taken, when he clicks the "register" button. It should already be checked on blur or focusout event.

For me also the above solutions didn't work and are outdated. Also they are very hacky and not future-proof.

@tcchau
Copy link

tcchau commented Feb 2, 2023

This is really frustrating. It's a really bad user experience if the user can only see his username is already taken, when he clicks the "register" button. It should already be checked on blur or focusout event.

For me also the above solutions didn't work and are outdated. Also they are very hacky and not future-proof.

I can confirm that the approach, as long as you are in a similar environment, i.e. email addresses need to be unique, in #1067 (comment) will work.

Since the ListUsers API can use any alias of the username, i.e. actually username, email address, phone number, it should be future-proof, and just short of a dedicated API for this functionality.

Unfortunately, maybe threads like these where users have discovered workarounds actually discourage the AWS team from implementing a full solution themselves.

@MudabbarHussain
Copy link

How to send TOPT code via email and phone number when the user signup. aws-cognito.

@KevL97t
Copy link

KevL97t commented May 26, 2023

Any implementations yet? the feature request happened 5 years ago now.

@gartnerleandro
Copy link

gartnerleandro commented Jun 12, 2023

I have created a custom lambda function using amplify to check if the user exists in the database, in my case the username, because I need a unique username. When creating the function, you must grant access to the users table in the database.

// amplify/backend/function/checkUser/src/index.js

const AWS = require('aws-sdk'); 

AWS.config.update({ region: process.env.REGION });

const documentClient = new AWS.DynamoDB.DocumentClient({ region: process.env.REGION });

exports.handler = async (event, context) => {
  if (event.arguments.userName) {
    let response;
    const params = {
      TableName: process.env.API_USERTABLE_NAME,
      IndexName: 'byName',
      KeyConditionExpression: '#name = :name',
      ExpressionAttributeValues: { ':name': event.arguments.userName },
      ExpressionAttributeNames: { '#name': 'name' },
    };

    try {
      response = await documentClient.query(params).promise();
    } catch (err) {
      context.done(err, null);
    }

    if (response && response.Items && response.Items.length) {
      context.done(null, 'In use');
    } else {
      context.done(null, 'Available');
    }
  } else {
    context.done(new Error('No username provided'), null);
  }
};

Then, if you are using the graphql API, you need to create a custom query for your lambda and grant public access to allow unlogged in users to be able to perform this query and check if the username is available.

# /amplify/backend/api/YOUR_APP_NAME/schema.graphql

type Query {
  checkUser(userName: String!): String! @function(name: "checkUser-${env}") @aws_iam @aws_cognito_user_pools
}

Then you can use it like this:

import { checkUser } from 'graphql/queries.js';

export const checkUsernameAvailability = (userName) =>
  new Promise((resolve, reject) => {
    API.graphql({
      query: checkUser,
      variables: { userName },
      authMode: 'AWS_IAM',
    })
      .then((response) => {
        resolve(response.data.checkUser);
      })
      .catch((error) => {
        reject(error);
      });
  });

This has worked for me up to 5 months ago. Now I'm getting the URL.host is not implemented error when doing the query but I think it's a Graphql API permissions issue in my setup.

Update: This still working after update my API to allow unauthenticated requests

I hope this can help someone.

@theromeapp
Copy link

+1 in seeing URL.host is not implemented, what was the fix you found for that?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Auth Related to Auth components/category Cognito Related to cognito issues feature-request Request a new feature Service Team Issues asked to the Service Team
Projects
None yet
Development

No branches or pull requests