aws-amplify / amplify-js

A declarative JavaScript library for application development using cloud services.
https://docs.amplify.aws/lib/q/platform/js
Apache License 2.0
9.42k stars 2.12k forks source link

Hosted UI returns tokens, but Hub and onAuthUIStateChange continue to give login error. #8337

Closed jmarshall9120 closed 3 years ago

jmarshall9120 commented 3 years ago

Before opening, please confirm:

JavaScript Framework

Vue

Amplify APIs

Authentication

Amplify Categories

auth

Environment information

``` System: OS: Windows 10 10.0.18362 CPU: (8) x64 Intel(R) Xeon(R) CPU E3-1505M v6 @ 3.00GHz Memory: 16.63 GB / 31.81 GB Binaries: Node: 10.16.3 - C:\Program Files\nodejs\node.EXE npm: 6.9.0 - C:\Program Files\nodejs\npm.CMD Browsers: Chrome: 90.0.4430.212 Edge: Spartan (44.18362.267.0) Internet Explorer: 11.0.18362.1 npmPackages: @aws-amplify/ui-vue: ^1.0.9 => 1.0.9 @vue/cli-plugin-babel: ~4.5.0 => 4.5.13 @vue/cli-plugin-eslint: ~4.5.0 => 4.5.13 @vue/cli-plugin-router: ~4.5.0 => 4.5.13 @vue/cli-plugin-unit-mocha: ~4.5.0 => 4.5.13 @vue/cli-plugin-vuex: ~4.5.0 => 4.5.13 @vue/cli-service: ~4.5.0 => 4.5.13 @vue/eslint-config-standard: ^5.1.2 => 5.1.2 @vue/test-utils: ^1.0.3 => 1.2.0 aws-amplify: ^4.0.2 => 4.0.2 babel-eslint: ^10.1.0 => 10.1.0 chai: ^4.1.2 => 4.3.4 core-js: ^3.6.5 => 3.12.1 (2.6.12) dev: 1.0.0 eslint: ^6.7.2 => 6.8.0 eslint-plugin-import: ^2.20.2 => 2.23.3 eslint-plugin-node: ^11.1.0 => 11.1.0 eslint-plugin-promise: ^4.2.1 => 4.3.1 eslint-plugin-standard: ^4.0.0 => 4.1.0 eslint-plugin-vue: ^6.2.2 => 6.2.2 sass: ^1.32.0 => 1.34.0 sass-loader: ^10.0.0 => 10.2.0 vue: ^2.6.11 => 2.6.12 vue-cli-plugin-vuetify: ~2.4.0 => 2.4.0 vue-router: ^3.2.0 => 3.5.1 vue-template-compiler: ^2.6.11 => 2.6.12 vuetify: ^2.4.0 => 2.5.0 vuetify-loader: ^1.7.0 => 1.7.2 vuex: ^3.4.0 => 3.6.2 npmGlobalPackages: @aws-amplify/cli: 4.51.2 @vue/cli-init: 4.5.13 @vue/cli: 4.5.13 eslint: 7.27.0 gulp-cli: 2.2.0 gulp: 4.0.2 ```

Describe the bug

I'm a senior dev digging into cognito for the first time. I've been at this same bug for about 3 days (which is unheard of for me), and I'm fairly sure there has to be something in the lib hurting me here. I'm moving the issue here from the discussion board as it seems pretty likely its bug/documentation related at this point.

I have successfully authenticated against the Hosted UI with both a Username & Password and a SAML federated login, however upon redirect to my site I am not logged in..

I'm using Vue and have a vue component with the following:

<script>
import { Auth, Hub, Cache } from 'aws-amplify'
import { onAuthUIStateChange } from '@aws-amplify/ui-components';

//.... removed boiler plate code here....
   data: () => {
      signedIn: false,
      username: null,
   },
   created () {
       this.unsubscribeAuth = onAuthUIStateChange((authState, authData) => {
            console.log("AuthState Change")
            console.log(authState)
            console.log(authData)
       })
    },
    mounted () {
        console.log("HelloWorld mounted()");
        Hub.listen("auth", ({ payload: { event, data } }) => {
          console.log(`Auth event: ${event}`);
          switch (event) {
             case "signIn":
                 console.log("signIn data: " + JSON.stringify(data));
                 this.signedIn = true;
                 this.username = data.username;
                 break;
             case "signOut":
                 this.signedIn = false;
                 this.username = null;
                 break;
         }
   });
    Auth.currentAuthenticatedUser()
      .then(user => {
        console.log("Username: " + user.username);
        console.log("Refreshing access and id tokens");
        Auth.currentSession()
          .then(data => console.log(data))
          .catch(err => console.log("Error refreshing tokens: " + err));
        this.signedIn = true;
        this.username = user.username;
      })
      .catch(err => console.log("Auth.currentAuthenticatedUser(): " + err));
  }

The gist of the above is I'm trying to capture data from several login methods found in the docs, but I have tried each of them on their own as well.

I get this as a result:

CognitoFailLogin

I've probably spent two days doing nothing but troubleshooting this, so its getting quite frustrating. I don't think I'll be able to move forward till I can get better diagnostic strategies. Anyone know how to run this down and determine the cause of the failure events?

Those failed calls in the source that came up 400 had this error message:

1 validation error detected: Value '{cognito-idp.us-west-2.amazonaws.com/us-west-2_c3c3ZULfA=}' at 'logins' failed to satisfy constraint: Map value must satisfy constraint: [Member must have length less than or equal to 50000, Member must have length greater than or equal to 1]

I've since debugged pretty deep into the library and found that there's middleware which is expected to be picking the login up from the URL, but isn't doing so, and thus the login event is not triggered properly.

Expected behavior

I expect to be logged in when the page returns.

Reproduction steps

vue create myproject //exclude typescript in the project creation, otherwise use the default.

cd my project vue add vuetify

amplify init //use defaults

amplify import auth //choose user pool and identity pool //choose exiting user pool

amplify push

//create a button for redirecting to the aws hosted ui (can use )

npm run serve

//attempt login through the hosted ui

Code Snippet

// Put your code below this line.

Log output

``` // Put your logs below this line ```

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-west-2",
    "aws_cognito_identity_pool_id": "us-west-2:5xxxxxxxxxxxxxxxxxxxxxx",
    "aws_cognito_region": "us-west-2",
    "aws_user_pools_id": "us-west-2_c3c3ZULfA",
    "aws_user_pools_web_client_id": "xxxxxxxxxxxxxxxxxxx",
    "oauth": {
        "domain": "mdgintranet.auth.us-west-2.amazoncognito.com",
        "scope": [
            "aws.cognito.signin.user.admin",
            "email",
            "openid",
            "phone",
            "profile"
        ],
        "redirectSignIn": "http://localhost:8080",
        "redirectSignOut": "http://localhost:8080/login/",
        "responseType": "token"
    },
    "federationTarget": "COGNITO_USER_POOLS"
};

export default awsmobile;

Manual configuration

No response

Additional configuration

No response

Mobile Device

No response

Mobile Operating System

No response

Mobile Browser

No response

Mobile Browser Version

No response

Additional information and screenshots

CognitoFailLogin

Edit 1

This is what's being passed in the URL upon redirect to my site.
I replaced the & with \n\t for readability.

http://localhost:8080/#/
    id_token=xxxxxxRedactedxxxxxxx
    access_token=xxxxxxRedactedxxxxxxx
    expires_in=3600
    token_type=Bearer
    state=INnP5IY28c2KwJ4nBoRMhUHSOpyvUqYN

Debugging the class: CredentialsClass.prototype._setCredentialsFromSession: At: @aws-amplify > core > lib-esm > Credentials.js?475d

Yields:

    CognitoUserSession:
        {
        "idToken": {
            "jwtToken": "",
            "payload": {}
        },
        "refreshToken": {
            "token": ""
        },
        "accessToken": {
            "jwtToken": "xxxxxxRedactedxxxxxxx",
            "payload": {
                "sub": "07db26d4-da1d-4266-bb80-xxxxxxRedactedxxxxxxx",
                "event_id": "c0f5accc-9987-46c2-aca2-688fc47769d5",
                "token_use": "access",
                "scope": "aws.cognito.signin.user.admin phone openid profile email",
                "auth_time": 1621975659,
                "iss": "https://cognito-idp.us-west-2.amazonaws.com/us-west-2_xxxxxxRedactedxxxxxxx",
                "exp": 1621979259,
                "iat": 1621975659,
                "version": 2,
                "jti": "8f7fb51c-e131-4693-956c-baa7224c27b5",
                "client_id": "xxxxxxRedactedxxxxxxx",
                "username": "xxxxxxRedactedxxxxxxx"
            }
        },
        "clockDrift": null
    }

Note in the above, the username is still encrypted. Not sure if thats intended.

CredentialsClass.prototype.set = function (params, source) source === 'session' evals true

Source is set by: CredentialsClass.prototype.set = function (params, source) Which states: /**

My guess is it's not picking up the idToken, because its running the source === 'session' auth flow.

How to let it know it should be using the Hosted UI redirect?

Edit 2

I'm getting closer

Debugging: AuthClass.prototype._handleAuthResponse At: @aws-amplify > auth > lib-esm > Auth.js?bf82

In case 1 there is the following logic:

hasTokenOrError = !!(parse(currentUrl).hash || '#')
      .substr(1)
      .split('&')
      .map(function (entry) { return entry.split('='); })
      .find(function (_a) {
      var _b = __read(_a, 1), k = _b[0];
      return k === 'access_token' || k === 'error';
  });

You're calling .substr, .split, .map, and .find on a truth value. !! must be an operator added from some external library I don't know. However, it is picking up the '/access_token' value instead of 'access_token'.

I'm not sure if this going to be due to vue routing or a deficiency in the redirect logic for amplify

jmarshall9120 commented 3 years ago

The vue router will keep putting a slash at the end of the path so there's no reason to fight it. Pretty sue this the answer to this bug is to escape '/' and '?' when comparing query string keys.

return k.match(/^(?:\/\?|\/|\?)access_token/g) !== null || k === 'error';

source here

Happy to submit pull request if that will speed up a fix.

Edit

Just tried the fix above, still doesn't result in a login being sent to the endpoint. Will have to track down more parsing issue to fix this me thinks.

chrisbonifacio commented 3 years ago

Good afternoon, @jmarshall9120! 😃 Thank you for submitting this and trying to fix it! If you can track it down and submit a PR, that would be awesome. In the meantime, I will report this to the team and see if we can help figure out what the cause might be.

jmarshall9120 commented 3 years ago

Another place this occurs

source here source here source here

jmarshall9120 commented 3 years ago

Hi @chrisbonifacio

I have a fork here to test the fix.

I'm having a hard time building it into a project for testing.

I've Swaped package.json

    "aws-amplify": "^4.0.2",

with:

   "aws-amplify": "https://github.com/jmarshall9120/amplify-js.git",

I have my

    "prepare": "npm run build"

but my imports still won't resolve. It looks like you have a bunch of build tools in the repo I'm not familiar with. Any chance you have some build instruction laying about?

chrisbonifacio commented 3 years ago

@jmarshall9120 Hey! Yes, there's this README of our contributing guidelines with instructions on how to build the project 😃

let me know if that helps!

https://github.com/aws-amplify/amplify-js/blob/main/CONTRIBUTING.md#development-process

chrisbonifacio commented 3 years ago

@jmarshall9120 We did some testing on this issue in Vue and found that the default router mode is hash, and changing that to history helped prevent this issue from happening. Would you mind trying this in your project and letting us know if this is a viable solution?

In regards to the PR, we cannot merge it because it affects the redirect in other scenarios.

jmarshall9120 commented 3 years ago

@chrisbonifacio - I'm trying to avoid history mode for simplicity sake, but I can go that route if necessary. Can you give me a sample of the faulty redirects the PR causes? We could easily change the part that searches for a key term such as 'access_token' to include '/'.

manueliglesias commented 3 years ago

Hey @jmarshall9120

Are you constructing the cognito hosted ui url yourself?

If so, can you instead call Auth.federatedSignIn() ?

jmarshall9120 commented 3 years ago

Hi @manueliglesias,

The problem here is with the redirect, so everything I've tried has not worked as the redirects end up the same. My OAuth flow, is a SAML flow and so uses the Hosted UI or a custom button. I'm pretty sure in my testing with Auth.federatedSignIn() only allowed me to use Google, Facebook, Amazon, or the Hosted UI, so we end up in the same place.

For instance, a test I did was to set up the <amplify-authenticator> component. If log directly into the UI that shows up on my page with a user name and password it will log me in. However if I hit button that comes with the <amplify-authenticator> that takes me to the hosted UI and use the same user and password, it won't log me in.

If you have a set up you think would work, I'm willing to try.

manueliglesias commented 3 years ago

I am not super familiar with vue and vue router, but I went ahead to https://router.vuejs.org/guide/ and modified the code a little and got it working with amplify (calling Auth.federatedSignIn()), could you try it with your aws-exports ? (e.g. you can copy/paste the code below to an index.html file and serve it with npx serve -l 3000)

And just to verify it wasn't a vue router specific thing, I also tried it with react-router (example at the bottom)

Kapture 2021-06-04 at 14 49 14

index.html (vue) ```html

Hello App!

Go to Foo Go to Bar

```
index.html (react) ```html
```

As of going straight to an identity provider, you can call Auth.federatedSignIn({customProvider: 'NAME_OF PROVIDER'})

image

jmarshall9120 commented 3 years ago

@manueliglesias - I served your test page and it worked!

So that's an interesting result because I know the method for parsing the url in the Auth library won't clean a #/ as shown in my earlier posts. Now if I set up a simple project like this using the vue cli, it does not work. This should theoretically be a slight variation on your example but it doesn't work

import Vue from 'vue'
// import './plugins/axios'
import App from './App.vue'
import router from './router'
import store from './store'
import vuetify from './plugins/vuetify'
import Amplify from 'aws-amplify'
import aws_exports from './aws-exports'

Vue.config.productionTip = false

const aws_configs = aws_exports
Amplify.configure(aws_configs)
new Vue({
  router,
  store,
  vuetify,
  render: h => h(App)
}).$mount('#app')
<template>
<div>

  <v-app>
  <div>
    <v-main>
      <v-container>
        <v-btn @click="signIn">Sign In</v-btn>
        <v-btn @click="test">test</v-btn>
        <v-btn @click="logOut">Logout</v-btn>
        <router-view/>
      </v-container>
    </v-main>
  </div>
  </v-app>
</div>
</template>

<script>
import Vue from 'vue'
import { Amplify, Auth } from 'aws-amplify'
Amplify.Logger.LOG_LEVEL = 'DEBUG'

export default Vue.extend({
  name: 'App',
  data: () => ({
  }),
  methods: {
    signIn: () => {
      console.log('HelloWorld signIn()')
      Auth.federatedSignIn({ customProvider: 'Office365' })
    },
    logOut: () => {
      console.log('HelloWorld logout()')
      Auth.signOut()
    },
    test: () => {
      Auth.currentAuthenticatedUser().then(({ username, attributes: { sub } }) => alert(`Hello ${username} (${sub})`))
    }
  },
}
</script>

I'm including some other basic stuff like vuetify, vuex, and axios here but they should not be interfering. I guess I have two tests I can do:

  1. do a basic build of vue cli with just amplify and router to rule out vuetify, vuex, and axios.
  2. try using @manueliglesias distro instead of the npm built amplify
  3. track down the parsing method being used in the url in @manueliglesias example and try to glean some insight.

I actually tried to debug the dist from @manueliglesias already, but chrome is having a hard time of it. Any ideas there would be nice. Notice how in the pick below Amplify completely cleans the url, removing the parameters and leaving a clean 'http://localhost:3000'. Compare that with my previous pic, where even after parsing the url remains with all the parameters. Something's up.

image

manueliglesias commented 3 years ago

@jmarshall9120

Can you try using code flow instead of token?

-          "responseType": "token
+          "responseType": "code"

regarding:

try using @manueliglesias distro instead of the npm built amplify

and

track down the parsing method being used in the url in @manueliglesias example and try to glean some insight.

I didn't do a special build, the one referenced in my examples is the official amplify library in umd format published to npm, but served from a cdn (unpkg)

chrisbonifacio commented 3 years ago

Closing this as we have not heard back from you. If you are still experiencing this issue and in need of assistance, please feel free to re-open and provide us with any information previously requested by our team members so we are better able to assist you. Thank you!

github-actions[bot] commented 2 years ago

This issue has been automatically locked since there hasn't been any recent activity after it was closed. Please open a new issue for related bugs.

Looking for a help forum? We recommend joining the Amplify Community Discord server *-help channels or Discussions for those types of questions.