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

Amplify 6 logout issue with Single sign on #13638

Open SajithTS opened 1 month ago

SajithTS commented 1 month ago

Before opening, please confirm:

JavaScript Framework

Angular

Amplify APIs

Authentication, Storage

Amplify Version

v6

Amplify Categories

auth, storage

Backend

Other

Environment information

``` System: OS: Linux 5.15 Ubuntu 20.04.6 LTS (Focal Fossa) CPU: (8) x64 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz Memory: 8.94 GB / 15.36 GB Container: Yes Shell: 5.0.17 - /bin/bash Binaries: Node: 20.15.1 - ~/.nvm/versions/node/v20.15.1/bin/node npm: 10.7.0 - ~/.nvm/versions/node/v20.15.1/bin/npm Browsers: Chrome: 126.0.6478.182 npmPackages: @angular-devkit/build-angular: ^17.3.8 => 17.3.8 @angular/animations: ^17.3.11 => 17.3.12 @angular/cdk: ^17.3.10 => 17.3.10 @angular/cli: ~17.3.8 => 17.3.8 @angular/common: ^17.3.11 => 17.3.12 @angular/compiler: ^17.3.11 => 17.3.12 @angular/compiler-cli: ^17.3.11 => 17.3.12 @angular/core: ^17.3.11 => 17.3.12 @angular/forms: ^17.3.11 => 17.3.12 @angular/material: ^17.3.10 => 17.3.10 @angular/platform-browser: ^17.3.11 => 17.3.12 @angular/platform-browser-dynamic: ^17.3.11 => 17.3.12 @angular/router: ^17.3.11 => 17.3.12 @types/google.maps: ^3.51.0 => 3.55.11 @types/jasmine: ~4.3.0 => 4.3.6 aws-amplify: ^6.4.2 => 6.4.3 aws-amplify/adapter-core: undefined () aws-amplify/analytics: undefined () aws-amplify/analytics/kinesis: undefined () aws-amplify/analytics/kinesis-firehose: undefined () aws-amplify/analytics/personalize: undefined () aws-amplify/analytics/pinpoint: undefined () aws-amplify/api: undefined () aws-amplify/api/server: undefined () aws-amplify/auth: undefined () aws-amplify/auth/cognito: undefined () aws-amplify/auth/cognito/server: undefined () aws-amplify/auth/enable-oauth-listener: undefined () aws-amplify/auth/server: undefined () aws-amplify/data: undefined () aws-amplify/data/server: undefined () aws-amplify/datastore: undefined () aws-amplify/in-app-messaging: undefined () aws-amplify/in-app-messaging/pinpoint: undefined () aws-amplify/push-notifications: undefined () aws-amplify/push-notifications/pinpoint: undefined () aws-amplify/storage: undefined () aws-amplify/storage/s3: undefined () aws-amplify/storage/s3/server: undefined () aws-amplify/storage/server: undefined () aws-amplify/utils: undefined () buffer: ^4.9.2 => 4.9.2 (5.7.1) jasmine-core: ~4.5.0 => 4.5.0 karma: ~6.4.0 => 6.4.3 karma-chrome-launcher: ~3.1.0 => 3.1.1 karma-coverage: ~2.2.0 => 2.2.1 karma-coverage-coffee-example: 1.0.0 karma-jasmine: ~5.1.0 => 5.1.0 karma-jasmine-html-reporter: ~2.0.0 => 2.0.0 moment: ^2.29.4 => 2.30.1 rxjs: ~7.8.0 => 7.8.1 rxjs/ajax: undefined () rxjs/fetch: undefined () rxjs/operators: undefined () rxjs/testing: undefined () rxjs/webSocket: undefined () tslib: ^2.3.0 => 2.6.3 (2.6.2, 1.14.1) typescript: ~5.4.5 => 5.4.5 zone.js: ~0.14.7 => 0.14.8 ```

Describe the bug

I am using Angular version 17.3.12 and Amplify v6.4.2. Using hosted UI as login method. We are make use of the Single sign on since we have multiple angular applications. Also, using Cookie storage as the storage mechanism for the amplify library.

The scenario is, [Consider I have 2 applications named App1 & App2]

  1. Login from App1(Have a landing page which contain a button which redirect to the cognito hosted ui (login page)
  2. If the login is success, i am able to switch the applications, so i can go with App2.
  3. Both applications using our custom library(A header section) , that contain logout button
Case 1:
              If I try to logout from the App1, it will successfully logout, and redirect to landing page

Case 2:
              If I try to logout from App2, it WON'T LOGOUT and no action is happening.
              Note: Observed that cookie is cleared. But if we refresh the tab, it will redirect to the landing page. 
              But upon clicking the login button, it will directly enter to the application home page 
              without prompting the credentials.

Expected behavior

While using Single sign on with my applications having same domain, expected to completely logout from all the linked applications seamlessly.

Reproduction steps

  1. Enable single sign on for multiple applications; say App1, App2, etc..
  2. Login using one application; say App1
  3. Navigate to App2 [Use cookie storage to share the token and other auth informations]
  4. Try to logout from the App2.

Code Snippet


import { Hub } from "aws-amplify/utils";
import { getCurrentUser, signInWithRedirect } from "aws-amplify/auth";

// Constructor of the landing page
constructor(private router: Router,
    private zone: NgZone) {
    // Used for listening to login events
    Hub.listen("auth", ({ payload }) => {
      switch (payload.event) {
        case 'signedIn':
        case "signInWithRedirect":
          this.zone.run(() => this.router.navigate(['/app/home']));
          break;
        default:
          // handle sign in failure
          this.zone.run(() => this.router.navigate(['/login']));
          break;
      }
    });

    //currentAuthenticatedUser: when user comes to login page again
    getCurrentUser()
      .then(() => {
        this.router.navigate(['/app/home'], { replaceUrl: true });
      }).catch((err) => {
        this.router.navigate(['/login']);
      })

  }

// This function is triggered when clicking "Login"
onLoginClick() {
    signInWithRedirect();;
}

// This function is triggered when we click on Logout
logout(){
    signOut({ global: true })
      .then(data => {
        console.log(data)
  })
      .catch(err => {
        console.log(err)
        signOut();
      });
  }

Log output

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

aws-exports.js

import { environment } from "../src/environments/environment";
import { ResourcesConfig } from "aws-amplify";
const awsAmplifyConfig: ResourcesConfig = {
    Auth: {
        Cognito: {
            userPoolId: environment.cognitoUserPoolId,
            userPoolClientId: environment.cognitoAppClientId,
            signUpVerificationMethod: 'code',
            loginWith: {
                oauth: {
                    domain: environment.cognitoAuthDomain,
                    scopes: [
                        "phone",
                        "email",
                        "openid",
                        "profile",
                        "aws.cognito.signin.user.admin"
                    ],
                    redirectSignIn: ['https://app1.dev.mydomain.com/app/home'],
                    redirectSignOut: ['https://app1.dev.mydomain.com/login'],
                    responseType: 'code'
                }
            }
        }
    }
};

export default awsAmplifyConfig;

Manual configuration

import { Amplify } from 'aws-amplify';
import { CookieStorage } from 'aws-amplify/utils';
import { cognitoUserPoolsTokenProvider } from 'aws-amplify/auth/cognito';
import awsAmplifyConfig from './aws-exports';

Amplify.configure(awsAmplifyConfig);

cognitoUserPoolsTokenProvider.setKeyValueStorage(new CookieStorage(environment.cookieStorage));
environment.cookieStorage

cookieStorage: {
    // - Cookie domain (only required if cookieStorage is provided)
    domain: `.mydomain.com`,
    // (optional) - Cookie path
    path: '/',
    // (optional) - Cookie expiration in days
    expires: 1,
    // (optional) - See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite
    sameSite: `strict` as const,

    // (optional) - Cookie secure flag
    // Either true or false, indicating if the cookie transmission requires a secure protocol (https).
    secure: true,
  },

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

No response

israx commented 1 month ago

hello @SajithTS. Sorry to hear you are experiencing issues with the library. I'd like to get some context, could you elaborate on the following questions bellow.

  1. How did you configure SSO for App1 and App2?

  2. Can you confirm if the flow bellow is accurate?

click signInWithRedirect App1 -> redirect to App1 -> finish authenticating -> redirect to App 2 -> log out

  1. are you able to call getCurrentUser API on App2?

  2. are the attached code snippets above for App1?

  3. how is signOut not working? are you still able to see auth tokens ?

SajithTS commented 1 month ago

Hello @israx

How did you configure SSO for App1 and App2?

Configured the SSO by using same userPoolId, userPoolClientId, cognitoAuthDomain and 
same Application domain

Can you confirm if the flow bellow is accurate? click signInWithRedirect App1 -> redirect to App1 -> finish authenticating -> redirect to App 2 -> log out

Yes. The flow is correct

are you able to call getCurrentUser API on App2?

Yes. getCurrentUser is working as expected

are the attached code snippets above for App1?

Yes. Same code is using in the App2 as well (Except "signInWithRedirect() API")

how is signOut not working? are you still able to see auth tokens ?

When I click the signout button, stay in the present page. Not redirecting to the login/landing page. 
But auth details including tokens are cleared from the cookie.
Then if I refresh the page, it will redirect to landing page. Then clicking the login button, 
take me to the App1 home page without asking the credentials

NOTE: SSO and the logout functionalities were working fine with the Amplify 5.2.4 version. After the Amplify 6 upgrade, facing this issue

SajithTS commented 1 month ago

@israx I can see that a parameter has been added to the local storage of App1. The key name is

CognitoIdentityServiceProvider.CLIENT_ID.oauthSignIn

The value for the above parameter is

true,false

But this Key-value is not present in the local storage of App2.

israx commented 1 month ago

Hey @SajithTS. Before calling the signOut API on APP2, make a call to Amplify.getConfig and see if you have a valid oauth configuration.

The library will validate this oauth config and will call the oauth endpoint that redirects back to the app. If there is not a valid oauth config, you might need to call Amplify.configure on App2.

SajithTS commented 1 month ago

Hello @israx . I am calling Amplify.configure in the main.ts file of both App1 & App2.

To make sure the configuration is correct, i have printed the configuration value while calling the logout function.

The logout function definition used in the App2 and App1 is given below:

onLogoutClick() {
    let a = Amplify.getConfig();
    console.log('Amplify config >> ', a)
    signOut({ global: true })
      .then(data => console.log(data))
      .catch(err => {
        console.log(err)
        signOut();
      });
  }

The console output for the above code snippet is given here:

{
    "Cognito": {
        "loginWith": {
            "oauth": {
                "domain": "COGNITO_DOMAIN",
                "redirectSignIn": [
                    "https://app1.dev.mydomain.com/app/home"
                ],
                "redirectSignOut": [
                    "https://app1.dev.mydomain.com/login"
                ],
                "responseType": "code",
                "scopes": [
                    "phone",
                    "email",
                    "openid",
                    "profile",
                    "aws.cognito.signin.user.admin"
                ]
            }
        },
        "signUpVerificationMethod": "code",
        "userPoolClientId": "CLIENT_ID",
        "userPoolId": "POOL_ID"
    }
}

The output is same from both App1 & App2.

israx commented 1 month ago

hello @SajithTS. Your comment led me to determine the root cause of the issue.

The oauthSignIn key is used to determine if a user logged in via an oauth flow. This key is only stored on localStorage. So the moment App2 logs out, it doesn't have access to it.

I'll mark this issue as a bug. In the meantime, a heuristic workaround is to manually set the oauthSignIn key on the localStorage of App2.

SajithTS commented 1 month ago

Hey @israx . With reference to your previous comment, I have manually set the oauthSignIn in the local storage of the App2. The code for setting the localStorage value is written in the layout class (This class is accessible only for the authenticated users). Here is the code:

localStorage.setItem(`CognitoIdentityServiceProvider.${environment.cognitoAppClientId}.oauthSignIn`,`true,false`);

Value is successfully stored in localStorage of App2 and now the localStorage of App1 & App2 are identical.

Result: If I try to logout from the App2, the following error is thrown and there is no redirection to the login page. Stay in the present page.

InvalidOriginException: redirect is coming from a different origin. 
The oauth flow needs to be initiated from the same origin
    at 38908 (https://app2.dev.mydomain.com/main.10d371c6677ed14b.js:162389:32)
    at __webpack_require__ (https://app2.dev.mydomain.com/runtime.7d29bb8b82c466c4.js:23:42)
    at 15199 (https://app2.dev.mydomain.com/main.10d371c6677ed14b.js:168598:79)
    at __webpack_require__ (https://app2.dev.mydomain.com/runtime.7d29bb8b82c466c4.js:23:42)
    at 53205 (https://app2.dev.mydomain.com/main.10d371c6677ed14b.js:165710:68)
    at __webpack_require__ (https://app2.dev.mydomain.com/runtime.7d29bb8b82c466c4.js:23:42)
    at 44920 (https://app2.dev.mydomain.com/main.10d371c6677ed14b.js:163521:86)
    at __webpack_require__ (https://app2.dev.mydomain.com/runtime.7d29bb8b82c466c4.js:23:42)
    at 32651 (https://app2.dev.mydomain.com/main.10d371c6677ed14b.js:164042:92)
    at __webpack_require__ (https://app2.dev.mydomain.com/runtime.7d29bb8b82c466c4.js:23:42)
israx commented 1 month ago

I see you have the following domains https://app1.dev.mydomain.com/app/home and https://app2.dev.mydomain.com/app/home. And you want to logout from domain2 and redirect to domain1. So this flow is not supported at the moment. However, we have a beta version that is being worked on and that you can use to overwrite the actual redirectSignOut from your oauth config.

You should be able to do it via the options of the signOut API.

SajithTS commented 1 month ago

@israx . Thank you for your prompt response and assistance. I will keep you updated on the results once I have completed the testing.

SajithTS commented 1 month ago

Hey @israx. Thank you for releasing the beta version. I have tested it in my project, and it is working fine. Could you please let me know when you anticipate releasing a stable version of this beta?

Note: Currently i'm manually setting the oauthSignIn (comment)

Do we plan on setting this key into local storage from Amplify library itself while initialising the application, so that users don't have to set it manually every time. Because this was not an issue in Amplify v5.

israx commented 1 month ago

Hello @SajithTS. We are looking into this issue, will follow up with any updates