aws-amplify / amplify-flutter

A declarative library with an easy-to-use interface for building Flutter applications on AWS.
https://docs.amplify.aws
Apache License 2.0
1.33k stars 248 forks source link

NotAuthorizedException is thrown in Amplify.Auth.fetchUserAttributes() #3014

Closed motucraft closed 1 year ago

motucraft commented 1 year ago

Description

I am using a SAML provider with Flutter web. I have configured a Cognito user pool, with the IdP being Keycloak on localhost.

I have successfully signed in with SAML by running the attached sample code. Clicking on "Current user" retrieves the ID token and outputs the mapped attributes to the console.

However, when I run Amplify.Auth.fetchUserAttributes(), I encounter a NotAuthorizedException. I am unsure of the cause and wondering if I have misconfigured something.

Suspecting the scope because I am getting the output "Access Token does not have required scopes", where is the problem?

Categories

Steps to Reproduce

Start with flutter run -d chrome --web-port 8000.

import 'amplifyconfiguration.dart';

void main() { runApp(const MyApp()); }

class MyApp extends StatefulWidget { const MyApp({super.key});

@override State createState() => _MyAppState(); }

class _MyAppState extends State { @override void initState() { super.initState(); _configureAmplify(); }

Future _configureAmplify() async { try { final auth = AmplifyAuthCognito(); await Amplify.addPlugin(auth);

  await Amplify.configure(amplifyconfig);
} on Exception catch (e) {
  safePrint('An error occurred configuring Amplify: $e');
}

}

@override Widget build(BuildContext context) { return MaterialApp( home: SafeArea( child: Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( onPressed: () async { await _signInSaml(); }, child: const Text('Sign in saml'), ), const SizedBox(height: 24), ElevatedButton( onPressed: () async { final isSignedIn = await _isUserSignedIn(); if (isSignedIn) { final user = await Amplify.Auth.getCurrentUser(); } }, child: const Text('Current user'), ), const SizedBox(height: 24), ElevatedButton( onPressed: () async { Amplify.Auth.signOut(); }, child: const Text('Sign out'), ), ], ), ), ), ), ); }

Future _signInSaml() async { final result = await Amplify.Auth.signInWithWebUI( provider: const AuthProvider.saml('MySamlProvider'), ); debugPrint('result=$result'); }

Future _isUserSignedIn() async { final result = await Amplify.Auth.fetchAuthSession(); if (result.isSignedIn) { final cognitoUserPoolTokens = result.toJson()['userPoolTokens'] as CognitoUserPoolTokens; final idToken = cognitoUserPoolTokens.idToken; debugPrint('userId=${idToken.userId}'); debugPrint('name=${idToken.name}'); debugPrint('familyName=${idToken.familyName}'); debugPrint('givenName=${idToken.givenName}');

  try {
    // NotAuthorizedException thrown.
    final userAttributes = await Amplify.Auth.fetchUserAttributes();
    debugPrint('userAttributes=$userAttributes');
  } catch (e) {
    safePrint(e);
  }
}

return result.isSignedIn;

} }


- amplifyconfiguration.dart
```dart
const amplifyconfig = ''' {
    "UserAgent": "aws-amplify-cli/2.0",
    "Version": "1.0",
    "auth": {
        "plugins": {
            "awsCognitoAuthPlugin": {
                "UserAgent": "aws-amplify-cli/0.1.0",
                "Version": "0.1.0",
                "IdentityManager": {
                    "Default": {}
                },
                "CognitoUserPool": {
                    "Default": {
                        "PoolId": "xxx",
                        "AppClientId": "zzz",
                        "Region": "ap-northeast-1"
                    }
                },
                "Auth": {
                    "Default": {
                        "OAuth": {
                            "WebDomain": "xyz.auth.ap-northeast-1.amazoncognito.com",
                            "AppClientId": "zzz",
                            "SignInRedirectURI": "http://localhost:8000/",
                            "SignOutRedirectURI": "http://localhost:8000/",
                            "Scopes": [ "email", "openid", "profile" ]
                        },
                        "authenticationFlowType": "USER_SRP_AUTH"
                    }
                }
            }
        }
    }
}''';

Screenshots

https://user-images.githubusercontent.com/35750184/236658943-b118edf4-e696-4c89-acba-231e2c99cacb.mov

NotAuthorizedException is thrown in Amplify.Auth.fetchUserAttributes()

Platforms

Flutter Version

3.7.12

Amplify Flutter Version

1.0.1

Deployment Method

Amplify CLI

Schema

No response

pranavo72bex commented 1 year ago

Any update regarding this issue?

dnys1 commented 1 year ago

In your OAuth configuration, can you try setting the Scopes field to:

"Scopes": [
  "phone",
  "email",
  "openid",
  "profile",
  "aws.cognito.signin.user.admin"
]

Also, in your Cognito console, under the App integration tab, if you select your user pool client, under the Hosted UI panel, the Open ID Connect scopes should have the same list.

motucraft commented 1 year ago

@dnys1 Thanks for your help. I now understand that "aws.cognito.signin.user.admin" is required to reference attributes.

Although the following document states that "The aws.cognito.signin.user.admin scope grants access to Amazon Cognito user pool API operations that require access tokens, such as UpdateUserAttributes and VerifyUserAttribute" I did not realize that it was necessary even when only referencing attributes.

https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-app-idp-settings.html

dnys1 commented 1 year ago

Yes, I can see how that might be confusing!

dJani97 commented 8 months ago

In the current version of Amplify (^1.6.1) and Flutter (3.19.1), the lack of "profile" and "aws.cognito.signin.user.admin" caused my client to permanently hang on the Amplify.Auth.fetchUserAttributes() call. No exception is printed onto the console.

For example:

debugPrint('load user attributes');
final userAttributes = (await Amplify.Auth.fetchUserAttributes());   // <---- execution will hang here
debugPrint('load user attributes done');   // <---- this will never be printed onto the console

The only reason I even found out about NotAuthorizedException happening in the background is because I used a debugger to dig into the fetchUserAttributes() call.

I believe this is some kind of bug, the exception should be made clear instead of an infinite timeout.