aws-amplify / amplify-cli

The AWS Amplify CLI is a toolchain for simplifying serverless web and mobile development.
Apache License 2.0
2.81k stars 821 forks source link

aws_cognito_identity_pool_id missing in the final aws-exports.js #12520

Closed mkbctrl closed 7 months ago

mkbctrl commented 1 year ago

Before opening, please confirm:

App Id

NEW_APP

AWS Region

us-east-1

Amplify Hosting feature

Environment variables, Frontend builds

Describe the bug

While documenting the Cognito setup flow for my project documentation I stumbled upon an issue where using CLI for initial setup with social providers fails to include the aws_cognito_identity_pool_id in the final aws-exports.js output.

Below I included the images of choices I made while settings up the environment from scratch.

Funny thing is that last week, when running the wizard, the final output did include the aws_cognito_identity_pool_id. I didn't document the flow that, but I am sure I was using the social provider auto setup as well, I would it expect to always include the id in the final config object

Expected behavior

Instead of aws-exports looking like this:

const awsmobile = {
  aws_project_region: 'us-east-1',
  aws_cognito_region: 'us-east-1',
  aws_user_pools_id: ':Redacted',
  aws_user_pools_web_client_id: ':Redacted',
  oauth: {
    domain: ':Redacted',
  },
  aws_cognito_username_attributes: ['EMAIL'],
  aws_cognito_social_providers: [],
  aws_cognito_signup_attributes: ['EMAIL'],
  aws_cognito_mfa_configuration: 'OFF',
  aws_cognito_mfa_types: [],
  aws_cognito_password_protection_settings: {
    passwordPolicyMinLength: 8,
    passwordPolicyCharacters: [
      'REQUIRES_LOWERCASE',
      'REQUIRES_UPPERCASE',
      'REQUIRES_NUMBERS',
      'REQUIRES_SYMBOLS',
    ],
  },
  aws_cognito_verification_mechanisms: ['EMAIL'],
}

it should look like (include aws_cognito_identity_pool_id: ':Redacted',:

const awsmobile = {
  aws_project_region: 'us-east-1',
  aws_cognito_region: 'us-east-1',
  aws_user_pools_id: ':Redacted',
  aws_user_pools_web_client_id: ':Redacted',
  aws_cognito_identity_pool_id:
    ':Redacted',
  oauth: {
    domain: ':Redacted',
  },
  aws_cognito_username_attributes: ['EMAIL'],
  aws_cognito_social_providers: [],
  aws_cognito_signup_attributes: ['EMAIL'],
  aws_cognito_mfa_configuration: 'OFF',
  aws_cognito_mfa_types: [],
  aws_cognito_password_protection_settings: {
    passwordPolicyMinLength: 8,
    passwordPolicyCharacters: [
      'REQUIRES_LOWERCASE',
      'REQUIRES_UPPERCASE',
      'REQUIRES_NUMBERS',
      'REQUIRES_SYMBOLS',
    ],
  },
  aws_cognito_verification_mechanisms: ['EMAIL'],
}

Reproduction steps

amplify init

amplify add auth

Screenshot 2023-04-24 at 10 18 39 Screenshot 2023-04-24 at 10 26 14

and afterwards running:

amplify env checkout dev

Build Settings

No response

Log output

``` # Put your logs below this line ```

Additional information

No response

ghost commented 1 year ago

Hi @mkbctrl 👋🏽 thanks for raising this issue. I'm transferring this to the Amplify CLI repo for better visibility.

mkbctrl commented 1 year ago

update:

We went with manual setup today. Same issue, no identity id in the output:

const awsmobile = {
    "aws_project_region": "us-east-1",
    "aws_cognito_region": "us-east-1",
    "aws_user_pools_id": ":redacted",
    "aws_user_pools_web_client_id": ":redacted",
    "oauth": {
        "domain": ":redacted.auth.us-east-1.amazoncognito.com",
        "scope": [
            "phone",
            "email",
            "openid",
            "profile",
            "aws.cognito.signin.user.admin"
        ],
        "redirectSignIn": "http://localhost:3000/api/auth/callback/cognito/",
        "redirectSignOut": "http://localhost:3000/api/auth/callback/cognito/",
        "responseType": "code"
    },
    "federationTarget": "COGNITO_USER_POOLS",
    "aws_cognito_username_attributes": [
        "EMAIL"
    ],
    "aws_cognito_social_providers": [
        "FACEBOOK",
        "GOOGLE"
    ],
    "aws_cognito_signup_attributes": [
        "EMAIL",
        "NAME"
    ],
    "aws_cognito_mfa_configuration": "OFF",
    "aws_cognito_mfa_types": [
        "SMS"
    ],
    "aws_cognito_password_protection_settings": {
        "passwordPolicyMinLength": 8,
        "passwordPolicyCharacters": []
    },
    "aws_cognito_verification_mechanisms": [
        "EMAIL"
    ]
};

export default awsmobile;

image-1

mkbctrl commented 1 year ago

update 2: (success) Going with manual configuration: User Sign-Up, Sign-In, connected with AWS IAM controls (Enables per-user Storage fea tures for images or other content, Analytics, and more) ended with federated pool id being present in the end.

remarks:

image (2)

josefaidt commented 1 year ago

Hey @mkbctrl :wave: thanks for raising this! As you have noted, when creating an auth resource we will need to select the option "connected with AWS IAM controls..." in order to provision an Identity Pool. This is also created when choosing the "default" configuration options at the beginning of the amplify add auth prompt flow.

Questions are frased differently, you have this feeling it's slightly different process overall + the number of deployed resources seems to be higher as well (not confirmed with data, subjective)

I agree with you that this prompt flow is quite confusing, and is something that is top of mind for future iterations of the CLI!

mkbctrl commented 1 year ago

By default you mean default with or without the social provider? It's kind of weird if it's for bare default, cause I would assume that that's a scenario where you don't need federated sign in 🤔

...and for the version with SP it didn't work (initial issue) unless I did smth wrong (can you have a look at the path I chose?)

josefaidt commented 1 year ago

Hey @mkbctrl by "default" I am referring to the first prompt for amplify add auth where it prompts for "default config", "default with social...", or "manual configuration". In the path you posted, when choosing "manual configuration" we must specify the option that is "connected with IAM controls"

As an example, using the "default with social provider" option will create the identity pool and associated ID in src/aws-exports.js

 Do you want to use the default authentication and security configuration? Default configu
ration with Social Provider (Federation)
 Warning: you will not be able to edit these selections. 
 How do you want users to be able to sign in? Email
 Do you want to configure advanced settings? No, I am done.
 What domain name prefix do you want to use? 12520a5f00760-a5f00760
 Enter your redirect signin URI: http://localhost:3000/
? Do you want to add another redirect signin URI No
 Enter your redirect signout URI: http://localhost:3000/
? Do you want to add another redirect signout URI No
 Select the social providers you want to configure for your user pool: Facebook

 You've opted to allow users to authenticate via Facebook.  If you haven't already, you'll
 need to go to https://developers.facebook.com and create an App ID. 

 Enter your Facebook App ID for your OAuth flow:  fakeappid
 Enter your Facebook App Secret for your OAuth flow:  fakeappsecret
✅ Successfully added auth resource 12520a5f00760 locally

image

mkbctrl commented 1 year ago

Thank you @josefaidt for explaining it in detail. I have much better understanding of the overall solution right now. I followed the same flow as you "default with social provider", and it did create one user pool, and one federated identities pool.

238313534-5bebad2f-bc9d-4755-a966-2267f9afad0a

When visiting federated identities pool settings you can notice that the connection to user pool is established:

Screenshot 2023-05-15 at 12 02 11

but for the facebook, there is nothing. I would expect otherwise:

Screenshot 2023-05-15 at 12 02 18

especially that, when trying to sign in using Facebook I am getting the following error:

Error during federated sign-in: NotAuthorizedException: Token is not from a supported provider of this identity pool.

Not sure whether it's a bug or a feature, but as an end user I would expect it work out of the box, or get notified in the end of the process what should I do next.

After entering the id manually in the federated identities pool settings, next sign in was successful.

Screenshot 2023-05-15 at 12 09 43

but the problem I am currently stuck with is the difference in the response shape between the regular signIn and federatedSignIn. The idToken is missing in the federated response, which makes my api calls return 401 status (unauth)

I think I understand (thanks to: https://github.com/aws-amplify/amplify-js/issues/703), that Cognito User Pool is technically speaking very similar to Federated Identities Pool, and they are entirely separate things. If I sign in with facebook, the user will be added to federated pool, and the cognito user pool won't be aware about the fact.

However (I can't find the source atm), I googled, that when you connect the Cognito User Pool and Federated Identities User Pool, the federatedSignIn should return you idToken same as with regular signIn method (which I also believe happen when you are using you hosted UI - in my project I can't do that). Also I read that Cognito will handle cases like creating a user inside the Cognito User Pool if an email that just popped out through federated sign in does not exist yet.

After checking up the federated pool settings (screens above) I consider those user pools as connected. Am I wrong to assume that?

The attribute mapping is set as well:

image

Here the chunk of code handling this process (I am using next-auth to setup FB):

// profile and tokens come from next-auth
async profile(profile, tokens) {
        try {
          if (!tokens.expires_at || !tokens.access_token) {
            throw new Error('Facebook provider error: missing tokens')
          }

          const response = await Auth.federatedSignIn(
            'facebook',
            { token: tokens.access_token, expires_at: tokens.expires_at },
            { name: profile.name, email: profile.email }
          )

          const user = await Auth.currentAuthenticatedUser()

          console.log('user', user)
          console.log('response', response)

user:

{
  id: 'us-east-1:redacted',
  name: 'redacted',
  email: 'redacted',
  token: 'redacted'
}

response

{
  identityId: 'us-east-1:redacted',
  accessKeyId: 'ASIAredacted',
  secretAccessKey: 'yroredacted',
  sessionToken: 'redacted',
  expiration: 2023-05-15T11:07:53.000Z,
  authenticated: true
}

So my question to you @josefaidt , can you confirm it? And if that's the case, what my current setup is missing to make it work? (The only thing added as an extra on top of default output was the FB app id)

Thinking out loud:

  1. Should I setup a lambda, that will create Cognito User Pool if I sign up with facebook?
  2. Should I setup HUB listener and catch and event that will have the idToken in the payload?
  3. Should I just ask the backend team to make api recognize different tokens, and this is the way it should be handled (thus considered best practice)?
josefaidt commented 1 year ago

Hey @mkbctrl what were the steps taken prior to noticing the Facebook credentials were missing? I was not able to reproduce that behavior when updating an existing auth resource with the social config and fake Facebook credentials image

However I did notice these are not also in the Identity Pool image

From the code snippet, are you using next-auth on top of Cognito by chance?

// profile and tokens come from next-auth

Do you see success from calling without passing the additional details to Auth.federatedSignIn? https://docs.amplify.aws/lib/auth/social/q/platform/js/#setup-frontend

mkbctrl commented 1 year ago

Since I was documenting the entire flow for my team all my steps were recorded. I started from scratch by running amplify init:

Screenshot 2023-05-16 at 21 07 07

afterwards I checked if the app exist:

Screenshot 2023-05-16 at 21 07 11

once I was sure the project was initiated I run all the steps mentioned above, which is running the next development server locally, trying the facebook sign in by hand, running into issue that it's not supported (NotAuthorizedError mentioned above as well), fixing it by adding the FB app id to the Identity Pool, and trying again.

Once I noticed I have different sets of tokens (again), and that idToken is still missing, I decided to post my findings here hoping your knowledge and experience will unstuck me :)

Regarding next-auth, you are correct. I am running it on top of Cognito. Here is my next-auth configuration file:

export const authOptions: NextAuthOptions = {
  debug: process.env.NODE_ENV !== 'production',
  session: {
    strategy: 'jwt',
    maxAge: getAuthSessionLength(),
  },
  // https://next-auth.js.org/configuration/providers/oauth
  providers: [
    CredentialsProvider({
      id: 'credentials',
      name: 'Sign in with email',
      credentials: {
        username: { label: 'email', type: 'text' },
        password: { label: 'Password', type: 'password' },
      },
      authorize: authAuthorizeCredentials,
    }),
    CredentialsProvider({
      id: 'credentials-auto-login',
      name: 'Create session from token',
      // @ts-ignore
      authorize: authAuthorizedAutologin,
    }),
  // WIP
   FacebookProvider<FacebookProviderProps>({
      clientId: 'redacted',
      clientSecret: 'redacted',
      async profile(profile, tokens) {
        try {
          if (!tokens.expires_at || !tokens.access_token) {
            throw new Error('Facebook provider error: missing tokens')
          }

          // await Auth.signOut()

          console.log(profile)

          const response = await Auth.federatedSignIn(
            'facebook',
            { token: tokens.access_token, expires_at: tokens.expires_at },
            { name: profile.name, email: profile.email }
          )
          const user = await Auth.currentAuthenticatedUser()
          const session = await Auth.currentSession()

          console.log('user', user)
          console.log('response', response)
          console.log('session', session)

          const { sessionToken, expiration } = response

          if (!expiration) {
            throw new Error('Facebook provider error: missing expiration date')
          }

          return {
            email: 'fake@email.com',
            id: profile.id,
            uid: profile.id,
            idToken: sessionToken,
            idTokenExpiryDate: Date.now() + expiration.getTime() * 1000,
            refreshToken,
            // TODO: handle onboarding check
            isOnboarded: true,
            error: undefined,
          }
        } catch (error) {
          console.error('Error during federated sign-in:', error)
          return null
        }
      },
    }),
  ],
  pages: {
    signIn: PublicPath.SignIn,
    signOut: PublicPath.SignIn,
    error: PublicPath.SignIn,
  },
  // docs: https://next-auth.js.org/configuration/callbacks
  callbacks: {
    signIn: authCallbackSignIn,
    jwt: authCallbackJwt,
    session: authCallbackSession,
  },
  events: {
    signOut: authEventSignOut,
  },
}

authAuthorizeCredentials is using Cognito signIn (and works well), whereas authAuthorizedAutologin creates session based on Hub autologin event and creates the session on the fly.

Having Google and Facebook on top of that is currently my dream :D

Edit 1: here is also the contents of my aws-exports.js:

/* eslint-disable */
// WARNING: DO NOT EDIT. This file is automatically generated by AWS Amplify. It will be overwritten.

const awsmobile = {
    "aws_project_region": "us-east-1",
    "aws_cognito_identity_pool_id": "us-east-1:redacted",
    "aws_cognito_region": "us-east-1",
    "aws_user_pools_id": "us-east-redacted",
    "aws_user_pools_web_client_id": "redacted",
    "oauth": {
        "domain": "redacted-dev.auth.us-east-1.amazoncognito.com",
        "scope": [
            "phone",
            "email",
            "openid",
            "profile",
            "aws.cognito.signin.user.admin"
        ],
        "redirectSignIn": "http://localhost:3000/",
        "redirectSignOut": "http://localhost:3000/",
        "responseType": "code"
    },
    "federationTarget": "COGNITO_USER_POOLS",
    "aws_cognito_username_attributes": [
        "EMAIL"
    ],
    "aws_cognito_social_providers": [
        "FACEBOOK"
    ],
    "aws_cognito_signup_attributes": [
        "EMAIL",
        "NAME"
    ],
    "aws_cognito_mfa_configuration": "OFF",
    "aws_cognito_mfa_types": [
        "SMS"
    ],
    "aws_cognito_password_protection_settings": {
        "passwordPolicyMinLength": 8,
        "passwordPolicyCharacters": []
    },
    "aws_cognito_verification_mechanisms": [
        "EMAIL"
    ]
};

export default awsmobile;
mkbctrl commented 1 year ago

As per your suggestion, running it empty would require me to create entirely different setup. Currently the place where I am executing the code is server-side (nextjs api endpoint), and federatedSignIn requires a window to be operational.

Still, just for experiment sake I attached function Auth.federatedSignIn() to a button and clicked it. Hosted UI opened, I was able to click on the Facebook button, and I got succefully redirected back to my app with the following query params attached to the url:

http://localhost:3000/access/sign-in?code=redacted-ceed-4c7c-b20b-9d32ba0ac82d&state=redactedKMUn4kA8wG9YbmbMO6KWUCJyC#_=_

surprise, surprise I also found a new arrival in my Cognito User Pool users list:

image

How is that possible?! 🤔 🤔 🤔

So basically running federatedSignIn() with params produces entirely different outcome (user is added to identity pool) than running it empty (user is added to user pool) 🤔

image

the redirect was accompanied by the failed 400 call:

Request URL: https://cognito-identity.us-east-1.amazonaws.com/
Request Method: POST
Status Code: 400 
Remote Address: 54.152.6.52:443
Referrer Policy: strict-origin-when-cross-origin

with the following payload:

{IdentityPoolId: "us-east-1:redacted-b373-4aa1-81fe-efd0a6f3a710",…}
IdentityPoolId
: 
"us-east-1:redacted"
Logins
: 
{cognito-idp.us-east-1.amazonaws.com/us-east-: ""}
mkbctrl commented 1 year ago

I tried to use Hub listener in hopes to catch some events, unfortunately after upon redirect the Post 400 call is sometimes issued before the Hub listener is initiated (I am doing it inside _app.tsx so at the very top) , which leads to sometimes catching an event and sometimes not.

The events I was able to listen to were about failure when getting back from cognito hosted UI.

image
josefaidt commented 1 year ago

Hey @mkbctrl wanted to follow up and mention that I am still investigating this use case with next-auth/auth.js on top of Cognito, but I touched base with our friends over at amplify-js and it is confirmed that when manually passing the user credentials as the second and third arguments of federatedSignIn it will not create a Cognito-native user. However, it is possible to link these federated identities to Cognito-native users, which I believe next-auth does by default when using a database provider. Creating the federated users and linking to Cognito-native users is what I am investigating for this use case.

mkbctrl commented 1 year ago

Hey @josefaidt, thanks for your effort. Do you have any news maybe? I will investigate the account linking as you suggest. Will keep you posted about any findings.

josefaidt commented 11 months ago

Hey @mkbctrl apologies for the delay here! Have you found success with next-auth on Cognito? I noticed there is also now auth.js, however this would typically be used on top of a database in place of Cognito. Out of curiosity, what is the intended use case for using next-auth/auth.js on top of Cognito? Would you mind filing an issue in our docs repo requesting a guide for this with those details? https://github.com/aws-amplify/docs/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc

josefaidt commented 7 months ago

Closing due to inactivity

github-actions[bot] commented 7 months ago

⚠️COMMENT VISIBILITY WARNING⚠️

Comments on closed issues are hard for our team to see. If you need more assistance, please open a new issue that references this one. If you wish to keep having a conversation with other community members under this issue feel free to do so.