aws-amplify / amplify-ui

Amplify UI is a collection of accessible, themeable, performant React (and more!) components that can connect directly to the cloud.
https://ui.docs.amplify.aws
Apache License 2.0
783 stars 256 forks source link

Social login is not properly doing redirect (Only Angular) #5048

Open yuriygavriluk opened 5 months ago

yuriygavriluk commented 5 months ago

Before opening, please confirm:

JavaScript Framework

Angular

Amplify APIs

Authentication

Amplify Version

v6

Amplify Categories

No response

Backend

Amplify CLI

Environment information

System: OS: Windows 11 10.0.22621 CPU: (8) x64 Intel(R) Core(TM) i7-10510U CPU @ 1.80GHz Memory: 4.91 GB / 15.78 GB Binaries: Node: 18.16.1 - C:\Program Files\nodejs\node.EXE npm: 9.5.1 - C:\Program Files\nodejs\npm.CMD Browsers: Edge: Chromium (120.0.2210.133) Internet Explorer: 11.0.22621.1 npmPackages: @angular-devkit/build-angular: ^16.1.0 => 16.2.11 @angular/animations: ^16.1.0 => 16.2.12 @angular/cdk: ^16.2.12 => 16.2.12 @angular/cli: ~16.1.0 => 16.1.8 @angular/common: ^16.1.0 => 16.2.12 @angular/compiler: ^16.1.0 => 16.2.12 @angular/compiler-cli: ^16.1.0 => 16.2.12 @angular/core: ^16.1.0 => 16.2.12 @angular/forms: ^16.1.0 => 16.2.12 @angular/material: ^16.2.12 => 16.2.12 @angular/platform-browser: ^16.1.0 => 16.2.12 @angular/platform-browser-dynamic: ^16.1.0 => 16.2.12 @angular/router: ^16.1.0 => 16.2.12 @aws-amplify/cli: ^12.10.1 => 12.10.1 @aws-amplify/ui-angular: ^5.0.6 => 5.0.6 @types/jasmine: ~4.3.0 => 4.3.6 aws-amplify: ^6.0.11 => 6.0.11 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/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 () jasmine-core: ~4.6.0 => 4.6.0 karma: ~6.4.0 => 6.4.2 karma-chrome-launcher: ~3.2.0 => 3.2.0 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.1.0 => 2.1.0 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.2 (2.6.1, 1.14.1) typescript: ~5.1.3 => 5.1.6 zone.js: ~0.13.0 => 0.13.3 npmGlobalPackages: @angular/cli: 16.1.0 @aws-amplify/cli: 12.10.0 aws-cdk: 2.108.1 serverless: 3.34.0

Describe the bug

Using this sample https://ui.docs.amplify.aws/angular/connected-components/authenticator/advanced Access Auth State

<!-- Render loading if authStatus is still configuring  -->
<ng-container *ngIf="authenticator.authStatus === 'configuring'">
  Loading...
</ng-container>

<!-- Only render this if there's an authenticated user -->
<ng-container *ngIf="authenticator.authStatus === 'authenticated'">
  Welcome back!
</ng-container>

<!-- Render sign-in screen otherwise with authenticator -->
<ng-container *ngIf="authenticator.authStatus !== 'authenticated'">
  <amplify-authenticator></amplify-authenticator>
</ng-container>

This code works perfectly fine when login in with users from user pool

When I run social login and login via facebook or gmail

<ng-container *ngIf="authenticator.authStatus === 'authenticated'">
  Welcome back!
</ng-container>

does not work properly

it requires page refresh to see the content if I only aprove it on FB and go further see blank screen by default

Expected behavior

f5 in browser is shuold not be required to see content in single page application after logging in via social links

Reproduction steps

  1. Create simple app https://ui.docs.amplify.aws/angular/connected-components/authenticator/advanced
    
    <!-- Render loading if authStatus is still configuring  -->
    <ng-container *ngIf="authenticator.authStatus === 'configuring'">
    Loading...
    </ng-container>

<ng-container *ngIf="authenticator.authStatus === 'authenticated'"> Welcome back!

<ng-container *ngIf="authenticator.authStatus !== 'authenticated'">


2. login via social e.g. facebook

Problem:
after confirming in fb you are redirected back and only see blank screen
if you press browser refload (f5) it fixes the issues

issues also is not reproduced if working with users that are managed by user pool directly

### Code Snippet

```javascript
// Put your code below this line.

Log output

<ng-container *ngIf="authenticator.authStatus === 'configuring'"> Loading...

<ng-container *ngIf="authenticator.authStatus === 'authenticated'"> Welcome back!

<ng-container *ngIf="authenticator.authStatus !== 'authenticated'">

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": "*", "aws_cognito_identity_pool_id": "**", "aws_cognito_region": "*", "aws_user_pools_id": "", "aws_user_pools_web_client_id": "", "oauth": { "domain": "***", "scope": [ "phone", "email", "openid", "profile", "aws.cognito.signin.user.admin" ], "redirectSignIn": "https://showcase.grorain.com", "redirectSignOut": "https://showcase.grorain.com", "responseType": "code" }, "federationTarget": "COGNITO_USER_POOLS", "aws_cognito_username_attributes": [], "aws_cognito_social_providers": [ "FACEBOOK", "GOOGLE" ], "aws_cognito_signup_attributes": [ "EMAIL", "GIVEN_NAME" ], "aws_cognito_mfa_configuration": "OFF", "aws_cognito_mfa_types": [ "SMS" ], "aws_cognito_password_protection_settings": { "passwordPolicyMinLength": 6, "passwordPolicyCharacters": [ "REQUIRES_LOWERCASE" ] }, "aws_cognito_verification_mechanisms": [ "EMAIL" ] };

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

No response

israx commented 5 months ago

Hello @yuriygavriluk . Can you attach the / at the end of your sign-in/out redirect e.g

{ 
 signInRedirect: 'https://showcase.grorain.com/' ,
 signOutRedirect: 'https://showcase.grorain.com/'
}
yuriygavriluk commented 5 months ago

Hello @israx Changed to "redirectSignIn": "https://showcase.grorain.com/", "redirectSignOut": "https://showcase.grorain.com/",

in both amplifyconfiguration.json and aws-exports.js

did not make any difference currently hosted here https://showcase.grorain.com/

if you have any other ideas to try please let me know

yuriygavriluk commented 5 months ago

image Added log of the state

<div>
  <div>
    authStatus: {{authenticator.authStatus}}
  </div>
  <!-- Render loading if authStatus is still configuring  -->
  <ng-container *ngIf="authenticator.authStatus === 'configuring'">
    Loading...
  </ng-container>

  <!-- Only render this if there's an authenticated user -->
  <ng-container *ngIf="authenticator.authStatus === 'authenticated'">
    Welcome back!
  </ng-container>

  <!-- Render sign-in screen otherwise with authenticator -->
  <ng-container *ngIf="authenticator.authStatus !== 'authenticated'">
    <amplify-authenticator></amplify-authenticator>
  </ng-container>
</div>

it seems like state is not being updated

import { Component } from '@angular/core'; import { AuthenticatorService } from '@aws-amplify/ui-angular';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  title = 'ang-showcase-app';
  constructor(public authenticator: AuthenticatorService) {
  }
}
cwomack commented 5 months ago

@yuriygavriluk, after looking into this further... we've confirmed that this looks to be working as expected on the amplify-js side of things. This appears to be an issue associated with the Authenticator, so i'll send this over to the amplify-ui repo to get better assistance! Appreciate your patience on this.

calebpollman commented 5 months ago

Hi @yuriygavriluk. Have been unable to repro the issue based off the code example you provided. Where are you calling Amplify.configure?

yuriygavriluk commented 5 months ago

Hello @calebpollman Thanks for your reply main.ts

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';

import { Amplify } from 'aws-amplify';
import amplifyconfig from './amplifyconfiguration.json';
Amplify.configure(amplifyconfig);

platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(err => console.error(err));

Curretnly simple hosted solution can be checked here https://showcase.grorain.com/

And and just does not work smooth So once you have empy page you can to reload (f5 in browser) and you are being logged in properly And also please note that this does not work only with Facebook or Gmail in case you work with user from user pool it works as expected

Please do let me know if any other useful info I can provide

calebpollman commented 5 months ago

To isolate whether the issue may be with the application configuration can you verify that the post sign in redirect is working as expected without the authenticator element?

Here is some example code with the signWithRedirect function used internally by the authenticator

// sign-in-federated.component.ts
import { Component } from '@angular/core';
import {
  fetchAuthSession,
  signInWithRedirect,
  signOut,
} from 'aws-amplify/auth';

@Component({
  selector: 'sign-in-federated',
  templateUrl: 'sign-in-federated.component.html',
})
export class SignInFederatedComponent {
  public isSignedIn = false;
  constructor() {
    this.fetchAuthSession();
  }

  async fetchAuthSession() {
    try {
      await fetchAuthSession();
      this.isSignedIn = true;
    } catch {
      // `fetchAuthSession` throws if no current session 
    }
  }

  public signInWithRedirect() {
    // 'Google` can be swapped out with other configured providers (example: 'Facebook')
    signInWithRedirect({ provider: 'Google' });
  }

  public signOut() {
    signOut();
    this.isSignedIn = false;
  }
}
<!-- sign-in-federated.component.html -->
<div>
  <p>Signed In? {{ isSignedIn }}</p>
  <ng-container *ngIf="!isSignedIn">
    <button (click)="signInWithRedirect()">Sign In!</button>
  </ng-container>
  <ng-container *ngIf="isSignedIn">
    <button (click)="signOut()">Sign Out!</button>
  </ng-container>
</div>
yuriygavriluk commented 5 months ago

https://showcase.grorain.com/

this is deployed example

For me it is hard to say whether it works or not as there's no state difference between logged in use and not logged in as in original request Feel free to explore on my site it is for testing purposes

yuriygavriluk commented 5 months ago

image

yuriygavriluk commented 5 months ago

if we can add some state on ui e.g. label user is logged in or not and his name that would be very cool to check

calebpollman commented 5 months ago

@yuriygavriluk Totally valid request. Updated the example code here to conditionally render the sign in/out buttons and provide whether there is a signed in user

yuriygavriluk commented 5 months ago

image

Same issue After log in state is not updated if I do page reload it starts working properly

calebpollman commented 5 months ago

@yuriygavriluk Got it. As you were able to repro the issue without using the authenticator it seems likely that the issue is in your application setup. Going to route this issue back to Amplify JS for further triage

yuriygavriluk commented 5 months ago

I will now try to use react To see if it makes difference or not This will naswer if the issue in JS or not

cwomack commented 5 months ago

@yuriygavriluk, let's see if we can get you unblocked here then if it doesn't seem to be a bug in the JS library or the UI side with the authenticator. If you still experience the issue with React, definitely comment back and let us know.

yuriygavriluk commented 5 months ago

react sample works just fine https://showcase.grorain.com/

simple code

<Authenticator>
        {({ signOut, user }) => (
          <main>
            <h1>Hello {user.username}</h1>
            <button onClick={signOut}>Sign out</button>
          </main>
        )}
      </Authenticator>

After being rediected see proper state image

For me sounds like something wrong with Angular packages and state is not managed properly for some reason

yuriygavriluk commented 5 months ago

Just wanted to point that still not clear how to use it in Anguar project

Using react is an option but can be too big change in some cases

israx commented 5 months ago

hello @yuriygavriluk . Just wanted to confirm, this seems to be specific to the Angular authenticator. Did you use the same amplify config on the React Authenticator ?

Could you call the signInWithRedirect API in your Angular App, without the Angular Authenticator, and see if you are able authenticate ?

yuriygavriluk commented 5 months ago

Hello @israx thanks for your reply config is the same

so only angular vs react was changed

signInWithRedirect we did this experiment and it behaves the same as Angular Authenticator so the same problem

is this only with Angular Authenticator -- no

signInWithRedirect this method is also used by react behind the scenes?

israx commented 5 months ago

I think some Angular versions don't re-render components on window redirect, thus not calling any APIs that allow to verify the user is authenticated, is it possible for you to listen to the signInWithRedirect hub event and then update state as desired ?

yuriygavriluk commented 5 months ago

Not too sure what this means but all the code is very simple and was provided here on the comments

I can run the solution again if you want to check

But to sum this method signinwithredirect works for some anguar versions and facebook or gmail correct?

israx commented 5 months ago

I'm not sure about Angular versions but I noticed some Angular Apps don't re-render components after being redirected from signInWithRedirect API, my suggestion is to try listening for the signInWithRedirect Hub event and then updating your state as desired.

yuriygavriluk commented 5 months ago

Thanks a lot for explanation Will try And message

israx commented 5 months ago

please do and let's us know

yuriygavriluk commented 5 months ago

Added this hub listener In this configuration with component does not write any message current code is hosted here https://showcase.grorain.com/

import { Component } from '@angular/core';
import {
  fetchAuthSession,
  signInWithRedirect,
  signOut,
} from 'aws-amplify/auth';
import { Hub } from 'aws-amplify/utils';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  title = 'ang-showcase-app';
  public isSignedIn = false;
  constructor() {
    Hub.listen('auth', this.listener);
    this.fetchAuthSession();
  }

  listener = (data: any) => {
    console.log('Data---');
    console.log(data);
  };

  async fetchAuthSession() {
    try {
      await fetchAuthSession();
      this.isSignedIn = true;
    } catch {
      // `fetchAuthSession` throws if no current session
    }
  }

  public signInWithRedirect() {
    // 'Google` can be swapped out with other configured providers (example: 'Facebook')
    signInWithRedirect({ provider: 'Google' });
  }

  public signOut() {
    signOut();
    this.isSignedIn = false;
  }
}
israx commented 5 months ago

Thank you for the reproducible code, is it possible to listen for the signInWithRedirect hub event and then call this.isSignedIn = true . E.g

 listener = (data: any) => {
    console.log('Data---');
    console.log(data);
   switch(data.payload.event){
    'signInWithRedirect':
      this.isSignedIn = true
      break
  }
  };
yuriygavriluk commented 5 months ago

this code is not hit //console.log('Data---'); this is never logged

israx commented 5 months ago

I'm able to log the actual data based on your app. I'm running it on the latest version of Chrome.

Screenshot 2024-01-29 at 5 08 15 PM
yuriygavriluk commented 5 months ago

Yes my mistake Thanks

Added code above still have issues image

cannot updated state in that call back from some reason

it may seem that rendering does not work but if I press new button 'Test rendering' value is being changed

Adding code

import { Component } from '@angular/core';
import {
  fetchAuthSession,
  signInWithRedirect,
  signOut,
} from 'aws-amplify/auth';
import { Hub } from 'aws-amplify/utils';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  title = 'ang-showcase-app';
  public isSignedIn = false;
  public anotherMessage = '';

  constructor() {
    Hub.listen('auth', this.listener);
    this.fetchAuthSession();
  }

  listener = (data: any) => {
    console.log('Data---');
    console.log(data);
    switch (data.payload.event) {
      case 'signInWithRedirect':
        this.isSignedIn = true;
        this.anotherMessage = 'new message';
        console.log('setting values ' +
          JSON.stringify({
            isSignedIn: this.isSignedIn,
            anotherMessage: this.anotherMessage
          })
        );
        break
    }
  };

  async fetchAuthSession() {
    try {
      await fetchAuthSession();
      this.isSignedIn = true;
    } catch {
      // `fetchAuthSession` throws if no current session
    }
  }

  setTestValueTestAngularRendering() {
    this.anotherMessage = 'testing rendeting';
  }

  public signInWithRedirect() {
    // 'Google` can be swapped out with other configured providers (example: 'Facebook')
    signInWithRedirect({ provider: 'Google' });
  }

  public signOut() {
    signOut();
    this.isSignedIn = false;
  }
}
cwomack commented 4 months ago

@yuriygavriluk, wanted to check in and see if you've figured out what's causing this yet. If not, have you tried using the ChangeDetectorRef class that @angular/core provides?

To @israx's comment above, I'm wondering if the state of the app after the Auth redirect flow doesn't have the proper context/data being returned after signing in. Can you see if refactoring your code to include this class and the detectChanges() method it provides help resolve the issue?

Note - I've also added a couple of async/awaits because signOut() and signInWithRedirect() both return a promise.

import { Component, ChangeDetectorRef } from '@angular/core';
import {
  fetchAuthSession,
  signInWithRedirect,
  signOut,
} from 'aws-amplify/auth';
import { Hub } from 'aws-amplify/utils';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  title = 'ang-showcase-app';
  public isSignedIn = false;
  public anotherMessage = '';

  constructor(private changeDetectorRef: ChangeDetectorRef) {
    Hub.listen('auth', this.listener);
    this.fetchAuthSession();
  }

  listener = (data: any) => {
    console.log('Data---');
    console.log(data);
    switch (data.payload.event) {
      case 'signInWithRedirect':
        this.isSignedIn = true;
        this.anotherMessage = 'new message';
        console.log('setting values ' +
          JSON.stringify({
            isSignedIn: this.isSignedIn,
            anotherMessage: this.anotherMessage
          })
        );

        this.changeDetectorRef.detectChanges();
        break;
    }
  };

  async fetchAuthSession() {
    try {
      await fetchAuthSession();
      this.isSignedIn = true;
      this.changeDetectorRef.detectChanges();
    } catch {
      // `fetchAuthSession` throws if no current session
    }
  }

  setTestValueTestAngularRendering() {
    this.anotherMessage = 'testing rendering';
    this.changeDetectorRef.detectChanges();
  }

  public async signInWithRedirect() {
    // 'Google` can be swapped out with other configured providers 
    await signInWithRedirect({ provider: 'Google' });
  }

  public async signOut() {
    await signOut();
    this.isSignedIn = false;
    // Manually trigger change detection to update the view
    this.changeDetectorRef.detectChanges();
  }
}
yuriygavriluk commented 4 months ago

@cwomack I confirm the code you provided works as expected State login\logout changed without any neccessety of extra reloading page Thanks a lot

Will check with the original code

I will check the original code if this works or not perhapse it will help to localyze the issue

yuriygavriluk commented 4 months ago

and coming back to the original sample from the doc app.component.html `

version 6
authStatus: {{authenticator.authStatus}}

<ng-container *ngIf="authenticator.authStatus === 'configuring'"> Loading...

<ng-container *ngIf="authenticator.authStatus === 'authenticated'"> Welcome back!

<ng-container *ngIf="authenticator.authStatus !== 'authenticated'">

`

app.component.ts `import { Component } from '@angular/core'; import { AuthenticatorService } from '@aws-amplify/ui-angular';

@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) export class AppComponent { title = 'ang-showcase-app'; constructor(public authenticator: AuthenticatorService) { } } `

app.module.ts `import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser';

import { AppComponent } from './app.component'; import { AmplifyAuthenticatorModule } from '@aws-amplify/ui-angular'; import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import {MatToolbarModule} from '@angular/material/toolbar'; import {MatButtonModule} from '@angular/material/button';

@NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, AmplifyAuthenticatorModule, MatSlideToggleModule, BrowserAnimationsModule, MatToolbarModule, MatButtonModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { } `

main.ts `import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';

import { Amplify } from 'aws-amplify'; import amplifyconfig from './amplifyconfiguration.json'; Amplify.configure(amplifyconfig);

platformBrowserDynamic().bootstrapModule(AppModule) .catch(err => console.error(err)); `

In this sample I can login using cognito user (the one that was created manually) all works as expected Neither facebook nor gmail does not work properly requires page reload to get proper state

image And page reload will fix the issue

yuriygavriluk commented 4 months ago

If you just have any ideas how I can patch the code so that it works properly that would be nice or I can use workaround we discussed before just a bit more code

Thanks for help

israx commented 4 months ago

Hello @yuriygavriluk . Can you confirm that the sample code suggested by @cwomack ended up working ? And that you are still attempting to use the UI Authenticator without having any results yet? If that is the case, there might be a bug in the Authenticator, and we would need to transfer this issue to the UI team for better assistance.

yuriygavriluk commented 4 months ago

HI @israx I confirm this code works as expected https://github.com/aws-amplify/amplify-ui/issues/5048

Original code does not work https://github.com/aws-amplify/amplify-ui/issues/5048

so @cwomack suggestion can be used as workaround for sure

cwomack commented 4 months ago

@yuriygavriluk, glad you've got a workaround now at least! It sounds like this may need some further investigation from the amplify-ui team to determine if there's a feature request related to the change detection with Angular in particular.

Going to cc: @calebpollman and transfer the issue back to that repo for next/final steps on this issue.