Amplify-js: Questions regarding adminLinkProviderForUser and using external providers

Created on 14 Jun 2019  路  14Comments  路  Source: aws-amplify/amplify-js

* Which Category is your question related to? *
Auth / External Providers

* What AWS Services are you utilizing? *
Cognito User Pools

* Provide additional details e.g. code snippets *
Hello,

I'm having trouble understanding the correct flow for using Google as an external provider for the Cognito User Pool I already have.

Some details of my setup before enabling Google as an external provider:

  • Users sign in using their emails
  • I have a pre-signup lambda that marks user emails as verified

Now for adding Google Sign In, I followed the instructions here: https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-social-idp.html

For testing, on my frontend I redirect the user to the following URL:
https://my-user-pool.auth.us-east-1.amazoncognito.com/oauth2/authorize?identity_provider=Google&redirect_uri=https://my-app-url.com/login_callback&response_type=code&client_id=npcmm5pr10rilaogu1hnsdkgv&scope=profile%20email%20openid&state=test

With this, the user will see the Google authorization page directly, without seeing the Cognito hosted UI, and that is my goal.

Now after the user authorizes my app on Google's end I have two possible scenarios:

1) This is a new user that does not exist on my user pool yet.

Here a new user is created on my user pool with the google mail and the username _Google__. I receive the callback request, create the user on my app's database using that same username as My UUID and set the email correctly. The user is able to use my app normally, everything works so far.

2) This user already registered in my app without Google Sign using his google email.

Here I detected that the user already exists inside my pre-signup lambda and knowing that this request comes from an External Provider I send a request to link the external provider to my existing user using the _AdminLinkProviderForUser_ endpoint and that link works, I get an empty response and can see on AWS console that external provider info is added to my existing user. However, once the pre-signup lambda finishes a second user is created with the username _Google__ like mentioned before. So I end up with two users for the same email (the previous ones gets set to email_verified: false). When coming back to my App login callback I receive the authorization code and fetch the tokens for that Second user, without a reference to my previously existing one.

So one of my questions is: I'm doing something wrong with this process? Shouldn't the linked existing user be the only one on my user pool and when signing in with Google shouldn't I receive the tokens for the linked users instead of Google's newly created one?

A second question: What should be the process for the user in Scenario 1 above to set a password for himself, like if he forgets that signed in with Google and tries to reset his password? I tried using both ForgotPassword and ChangePassword endpoints and they always give me Not Authorized as a response. It is not possible to set a password for external provider users inside my user pool?

I fell like I might be doing something wrong but couldn't find a way around by looking at the docs.

Cognito Service Team pending-close-response-required question

Most helpful comment

Hey @KingAMS

Yeah for me making it so that the user has to click the login button a second time in a row is not acceptable, I had to make it work the way I told you above. It is not good, and I feel that Cognito is making it harder to integrate with an External Provider instead of making it easier for me.

All 14 comments

Anyone?

@fgrillo have absolutely the same questions, let me know if you figure some solutions/workarounds for those please

@fgrillo have absolutely the same questions, let me know if you figure some solutions/workarounds for those please

Hi @AntonPuko, I couldn't figure this one out, it seems to behave very differently from what I expected based on the documentation pages.

I'm using a workaround, I keep both my local user and the Google user in the user pool, being that only my local one has the email verified (I use email as an alias for sign-in)

After the Google sign-in I exchange the Google User token for my Local User tokens using a Custom Auth Flow. This way, I don't have to keep a mapping on my side of which Google user is attached to which Local user.

Not pretty, but it works a charm.

I would prefer if the AdminLinkProviderForUser worked and I got my user tokens in the first place.

@fgrillo , did u try to link and then return an error on the pre-signup lambda? I suspect that this will not create the user and once the user tries to log in again it will work.

can u share both of your lambdas please,

Here is my solution, I hope that helps anyone. If someone has a betther solution, please let me know:

In my pre-signup Lambda I extract Google's SUB and email, look for the user and link them with AdminLinkProviderForUser once the like is done I return MARGED_GOOGLE string in the error. The error string is sent in the callback url to the browser where I identify the MARGED_GOOGLE string and tell the user that we just finished to marge his account and he needs to relogin. next time he login the link is there and there is no signup.

const AWS = require("aws-sdk");
exports.handler = (event, context, callback) => {
  if (event.triggerSource === "PreSignUp_ExternalProvider") {
    if (event.userName.startsWith("Google_")) {
      AWS.config.update({ region: "us-east-2" });
      const COGNITO_CLIENT = new AWS.CognitoIdentityServiceProvider({
        apiVersion: "2016-04-18",
        region: "us-east-2"
      });

      const  adminGetUserParams = {
        UserPoolId: "<UserPoolId>" /* required */,
        Username: event.request.userAttributes.email /* required */
      };
      COGNITO_CLIENT.adminGetUser(adminGetUserParams, function(err, data) {
        if (err) {
          callback("no signed in", null);
        } else {
          // successful response
          const params = {
            DestinationUser: {
              /* required */
              ProviderAttributeValue: data.Username,
              ProviderName: "Cognito"
            },
            SourceUser: {
              /* required */
              ProviderAttributeName: "Cognito_Subject",
              ProviderAttributeValue: event.userName.substr(7),
              ProviderName: "Google"
            },
            UserPoolId: "<UserPoolId>" /* required */
          };
          COGNITO_CLIENT.adminLinkProviderForUser(params, (e, d) => {
            if (e) callback("no signed in", null);
            else {
              callback("MARGED_GOOGLE", null);
            }
          });
        }
      });
    } else {
      callback("no signed in", null);
    }
  } else {
    callback(null, event);
  }
};

Hey @KingAMS

Yeah for me making it so that the user has to click the login button a second time in a row is not acceptable, I had to make it work the way I told you above. It is not good, and I feel that Cognito is making it harder to integrate with an External Provider instead of making it easier for me.

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.

This issue has been automatically closed because of inactivity. Please open a new issue if are still encountering problems.

@fgrillo have absolutely the same questions, let me know if you figure some solutions/workarounds for those please

Hi @AntonPuko, I couldn't figure this one out, it seems to behave very differently from what I expected based on the documentation pages.

I'm using a workaround, I keep both my local user and the Google user in the user pool, being that only my local one has the email verified (I use email as an alias for sign-in)

After the Google sign-in I exchange the Google User token for my Local User tokens using a Custom Auth Flow. This way, I don't have to keep a mapping on my side of which Google user is attached to which Local user.

Not pretty, but it works a charm.

I would prefer if the AdminLinkProviderForUser worked and I got my user tokens in the first place.

Is there some code regarding your solution you could share? Im facing a similar issue (only 1 user gets generated for Google, but getting the "Already found an entry for username")

This problems seems to have been there since 2017 already (https://forums.aws.amazon.com/thread.jspa?threadID=267154&tstart=0) .... I can't understand how something this critical is not working. Makes me reconsider using Cognito at all to be honest.

Thank you so much! Documentation from AWS contains no helpful example at all. I wasn't aware of how to use AWS SDK. Thanks for your help!

Btw, you can use async await by adding .promise() to the API calls.

@fgrillo
Hi Filipe, did you find another way to make it one click?

Hey @Jun711 I solved it the way I described earlier, by keeping both users in my userpool and once the user sign-in with Google User I exchange the tokens for the Local User tokens using Custom Auth Flow.

It is not ideal and we have to pay for both active users. All this time and I have other issues as well without any word back from AWS, feels to me like this project is abandoned on their end. I wouldn't recommend using it if you have other options.

@fgrillo Thanks for replying. Could you share the documentation that you used for exchange the tokens for the Local User tokens using Custom Auth Flow.?
I will give it a try if I can't find other solution.

Here are some of the stuff I used:

https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html#amazon-cognito-user-pools-custom-authentication-flow

https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-challenge.html

In my case I sent the Google user JWT token as the custom auth response and checked in the lambda if the user was correct and the token valid in order to authorize fetching the local user token.

Was this page helpful?
0 / 5 - 0 ratings