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.3k stars 240 forks source link

Lack support of using both API plugin multi-auth and DataStore plugin multi-auth in the same App #1867

Open dorontal opened 2 years ago

dorontal commented 2 years ago

Description

When you have more than one Auth configured in amplifyconfiguration.dart - the function Amplify.DataStore.observeQuery() throws an exception. You can see in the console errors that are provided from the Android emulator that the failure is due to the fact that Amplify Flutter cannot yet handle multiple Auth in the same configuration file. But the Amplify Flutter documentation explains in detail that this should be available, (see https://docs.amplify.aws/lib/datastore/setup-auth-rules/q/platform/flutter/#configure-multiple-authorization-types).

Here are the log lines from the VSCode console - those come from the Amplify libraries and/or Android libraries running on Android emulated OS:

I/amplify:aws-datastore( 4037): Orchestrator lock acquired.
I/amplify:aws-datastore( 4037): Orchestrator transitioning from LOCAL_ONLY to SYNC_VIA_API
I/amplify:aws-datastore( 4037): Setting currentState to SYNC_VIA_API
I/amplify:aws-datastore( 4037): Starting API synchronization mode.
I/amplify:aws-datastore( 4037): Starting processing subscription events.
I/amplify:aws-datastore( 4037): Orchestrator lock released.
E/amplify:aws-datastore( 4037): Failure encountered while attempting to start API sync.
E/amplify:aws-datastore( 4037): DataStoreException{message=Error during subscription., cause=ApiException{message=There is more than one API configured for this plugin with matching endpoint type., cause=null, recoverySuggestion=Please specify the name of API to invoke in the API method.}, recoverySuggestion=Evaluate details.}
E/amplify:aws-datastore( 4037):     at com.amplifyframework.datastore.appsync.AppSyncClient.lambda$subscription$3(AppSyncClient.java:331)
E/amplify:aws-datastore( 4037):     at com.amplifyframework.datastore.appsync.-$$Lambda$AppSyncClient$797ziDK0io-qXODzROLOA77stS8.accept(Unknown Source:4)
E/amplify:aws-datastore( 4037):     at com.amplifyframework.api.aws.AWSApiPlugin.subscribe(AWSApiPlugin.java:285)
E/amplify:aws-datastore( 4037):     at com.amplifyframework.api.ApiCategory.subscribe(ApiCategory.java:91)
E/amplify:aws-datastore( 4037):     at com.amplifyframework.datastore.appsync.AppSyncClient.subscription(AppSyncClient.java:335)
E/amplify:aws-datastore( 4037):     at com.amplifyframework.datastore.appsync.AppSyncClient.onCreate(AppSyncClient.java:257)
E/amplify:aws-datastore( 4037):     at com.amplifyframework.datastore.syncengine.-$$Lambda$scUANamNqt4NIIofrmWYQsBqVJM.subscribe(Unknown Source:7)
E/amplify:aws-datastore( 4037):     at com.amplifyframework.datastore.syncengine.SubscriptionProcessor.lambda$subscriptionObservable$6$SubscriptionProcessor(SubscriptionProcessor.java:187)
E/amplify:aws-datastore( 4037):     at com.amplifyframework.datastore.syncengine.-$$Lambda$SubscriptionProcessor$w6tohapLGUGmW4mOmsvNOno7GVE.subscribe(Unknown Source:11)
E/amplify:aws-datastore( 4037):     at io.reactivex.rxjava3.internal.operators.observable.ObservableCreate.subscribeActual(ObservableCreate.java:40)
E/amplify:aws-datastore( 4037):     at io.reactivex.rxjava3.core.Observable.subscribe(Observable.java:13099)
E/amplify:aws-datastore( 4037):     at io.reactivex.rxjava3.internal.operators.observable.ObservableDoOnEach.subscribeActual(ObservableDoOnEach.java:42)
E/amplify:aws-datastore( 4037):     at io.reactivex.rxjava3.core.Observable.subscribe(Observable.java:13099)
E/amplify:aws-datastore( 4037):     at io.reactivex.rxjava3.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96)
E/amplify:aws-datastore( 4037):     at io.reactivex.rxjava3.core.Scheduler$DisposeTask.run(Scheduler.java:614)
E/amplify:aws-datastore( 4037):     at io.reactivex.rxjava3.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:65)
E/amplify:aws-datastore( 4037):     at io.reactivex.rxjava3.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:56)
E/amplify:aws-datastore( 4037):     at java.util.concurrent.FutureTask.run(FutureTask.java:266)
E/amplify:aws-datastore( 4037):     at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
E/amplify:aws-datastore( 4037):     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
E/amplify:aws-datastore( 4037):     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
E/amplify:aws-datastore( 4037):     at java.lang.Thread.run(Thread.java:764)
E/amplify:aws-datastore( 4037): Caused by: ApiException{message=There is more than one API configured for this plugin with matching endpoint type., cause=null, recoverySuggestion=Please specify the name of API to invoke in the API method.}
E/amplify:aws-datastore( 4037):     at com.amplifyframework.api.aws.AWSApiPlugin.selectApiName(AWSApiPlugin.java:579)
E/amplify:aws-datastore( 4037):     at com.amplifyframework.api.aws.AWSApiPlugin.getSelectedApiName(AWSApiPlugin.java:564)
E/amplify:aws-datastore( 4037):     at com.amplifyframework.api.aws.AWSApiPlugin.subscribe(AWSApiPlugin.java:283)
E/amplify:aws-datastore( 4037):     ... 19 more
W/amplify:aws-datastore( 4037): API sync failed - transitioning to LOCAL_ONLY.
W/amplify:aws-datastore( 4037): DataStoreException{message=Error during subscription., cause=ApiException{message=There is more than one API configured for this plugin with matching endpoint type., cause=null, recoverySuggestion=Please specify the name of API to invoke in the API method.}, recoverySuggestion=Evaluate details.}
W/amplify:aws-datastore( 4037):     at com.amplifyframework.datastore.appsync.AppSyncClient.lambda$subscription$3(AppSyncClient.java:331)
W/amplify:aws-datastore( 4037):     at com.amplifyframework.datastore.appsync.-$$Lambda$AppSyncClient$797ziDK0io-qXODzROLOA77stS8.accept(Unknown Source:4)
W/amplify:aws-datastore( 4037):     at com.amplifyframework.api.aws.AWSApiPlugin.subscribe(AWSApiPlugin.java:285)
W/amplify:aws-datastore( 4037):     at com.amplifyframework.api.ApiCategory.subscribe(ApiCategory.java:91)
W/amplify:aws-datastore( 4037):     at com.amplifyframework.datastore.appsync.AppSyncClient.subscription(AppSyncClient.java:335)
W/amplify:aws-datastore( 4037):     at com.amplifyframework.datastore.appsync.AppSyncClient.onCreate(AppSyncClient.java:257)
W/amplify:aws-datastore( 4037):     at com.amplifyframework.datastore.syncengine.-$$Lambda$scUANamNqt4NIIofrmWYQsBqVJM.subscribe(Unknown Source:7)
W/amplify:aws-datastore( 4037):     at com.amplifyframework.datastore.syncengine.SubscriptionProcessor.lambda$subscriptionObservable$6$SubscriptionProcessor(SubscriptionProcessor.java:187)
W/amplify:aws-datastore( 4037):     at com.amplifyframework.datastore.syncengine.-$$Lambda$SubscriptionProcessor$w6tohapLGUGmW4mOmsvNOno7GVE.subscribe(Unknown Source:11)
W/amplify:aws-datastore( 4037):     at io.reactivex.rxjava3.internal.operators.observable.ObservableCreate.subscribeActual(ObservableCreate.java:40)
W/amplify:aws-datastore( 4037):     at io.reactivex.rxjava3.core.Observable.subscribe(Observable.java:13099)
W/amplify:aws-datastore( 4037):     at io.reactivex.rxjava3.internal.operators.observable.ObservableDoOnEach.subscribeActual(ObservableDoOnEach.java:42)
W/amplify:aws-datastore( 4037):     at io.reactivex.rxjava3.core.Observable.subscribe(Observable.java:13099)
W/amplify:aws-datastore( 4037):     at io.reactivex.rxjava3.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96)
W/amplify:aws-datastore( 4037):     at io.reactivex.rxjava3.core.Scheduler$DisposeTask.run(Scheduler.java:614)
W/amplify:aws-datastore( 4037):     at io.reactivex.rxjava3.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:65)
W/amplify:aws-datastore( 4037):     at io.reactivex.rxjava3.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:56)
W/amplify:aws-datastore( 4037):     at java.util.concurrent.FutureTask.run(FutureTask.java:266)
W/amplify:aws-datastore( 4037):     at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
W/amplify:aws-datastore( 4037):     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
W/amplify:aws-datastore( 4037):     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
W/amplify:aws-datastore( 4037):     at java.lang.Thread.run(Thread.java:764)
W/amplify:aws-datastore( 4037): Caused by: ApiException{message=There is more than one API configured for this plugin with matching endpoint type., cause=null, recoverySuggestion=Please specify the name of API to invoke in the API method.}
W/amplify:aws-datastore( 4037):     at com.amplifyframework.api.aws.AWSApiPlugin.selectApiName(AWSApiPlugin.java:579)
W/amplify:aws-datastore( 4037):     at com.amplifyframework.api.aws.AWSApiPlugin.getSelectedApiName(AWSApiPlugin.java:564)
W/amplify:aws-datastore( 4037):     at com.amplifyframework.api.aws.AWSApiPlugin.subscribe(AWSApiPlugin.java:283)
W/amplify:aws-datastore( 4037):     ... 19 more
I/amplify:aws-datastore( 4037): Orchestrator transitioning from SYNC_VIA_API to LOCAL_ONLY
I/amplify:aws-datastore( 4037): Setting currentState to LOCAL_ONLY
I/amplify:aws-datastore( 4037): Stopping subscription processor.
I/amplify:aws-datastore( 4037): Stopped subscription processor.

Categories

Steps to Reproduce

  1. Create a GraphQL API with a simple model
  2. Modify amplifyconfiguration.dart so that under API you have one GraphQL API configuration block for with authorizationType: AMAZON_COGNITO_USER_POOLS and in a second block under the same api section add a seond GraphQL API configuration block with authorizationType: AWS_IAM (see how it's done in the tracktunes and tracktunesCognito sections in amplifyconfiguration.dart below).
  3. Add a call to Amplify.Datastore.observeQuery(<YOUR MODEL>) at the start of your program and you'll see it crashing (check log lines in VSCode of the Android emulator, giving out the Android/Java-level exception details)

Screenshots

No response

Platforms

Android Device/Emulator API Level

API 29

Environment

> flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.0.4, on Debian GNU/Linux 11 (bullseye) 5.10.0-16-amd64, locale en_US.UTF-8)
[✓] Android toolchain - develop for Android devices (Android SDK version 32.1.0-rc1)
[✓] Chrome - develop for the web
[✓] Linux toolchain - develop for Linux desktop
[✓] Android Studio (version 2021.1)
[✓] VS Code (version 1.69.0)
[✓] Connected device (3 available)
[✓] HTTP Host Availability

• No issues found!

Dependencies

> flutter pub deps --no-dev --style=compact
Dart SDK 2.17.5
Flutter SDK 3.0.4
tracktunes_app_aws 0.0.1-alpha.4

dependencies:
- amplify_api 0.6.1 [amplify_api_android amplify_api_ios amplify_core amplify_flutter aws_common collection flutter meta plugin_platform_interface]
- amplify_auth_cognito 0.6.1 [amplify_auth_cognito_android amplify_auth_cognito_ios amplify_core aws_common collection flutter meta plugin_platform_interface]
- amplify_datastore 0.6.1 [flutter amplify_datastore_plugin_interface amplify_core plugin_platform_interface meta collection async]
- amplify_flutter 0.6.1 [amplify_core amplify_datastore_plugin_interface amplify_flutter_android amplify_flutter_ios aws_common collection flutter meta plugin_platform_interface]
- amplify_storage_s3 0.6.1 [amplify_storage_s3_android amplify_storage_s3_ios amplify_core aws_common flutter meta plugin_platform_interface]
- cupertino_icons 1.0.5
- flutter 0.0.0 [characters collection material_color_utilities meta vector_math sky_engine]
- flutter_hooks 0.18.5+1 [flutter]
- google_fonts 3.0.1 [flutter http path_provider crypto]
- hooks_riverpod 1.0.4 [collection flutter flutter_hooks flutter_riverpod riverpod state_notifier]
- image 3.2.0 [archive meta xml]
- image_picker 0.8.5+3 [flutter image_picker_android image_picker_for_web image_picker_ios image_picker_platform_interface]
- path_provider 2.0.11 [flutter path_provider_android path_provider_ios path_provider_linux path_provider_macos path_provider_platform_interface path_provider_windows]
- rflutter_alert 2.0.4 [flutter]

transitive dependencies:
- amplify_api_android 0.6.1 [flutter]
- amplify_api_ios 0.6.1 [amplify_core flutter]
- amplify_auth_cognito_android 0.6.1 [flutter]
- amplify_auth_cognito_ios 0.6.1 [amplify_core flutter]
- amplify_core 0.6.1 [aws_common collection flutter intl json_annotation meta plugin_platform_interface uuid]
- amplify_datastore_plugin_interface 0.6.1 [amplify_core collection flutter meta]
- amplify_flutter_android 0.6.1 [flutter]
- amplify_flutter_ios 0.6.1 [amplify_core flutter]
- amplify_storage_s3_android 0.6.1 [flutter]
- amplify_storage_s3_ios 0.6.1 [flutter]
- archive 3.3.0 [crypto path]
- async 2.8.2 [collection meta]
- aws_common 0.1.1 [async collection http meta stream_transform uuid]
- characters 1.2.0
- charcode 1.3.1
- clock 1.1.0
- collection 1.16.0
- cross_file 0.3.3+1 [js meta]
- crypto 3.0.2 [typed_data]
- ffi 2.0.1
- file 6.1.2 [meta path]
- flutter_plugin_android_lifecycle 2.0.6 [flutter]
- flutter_riverpod 1.0.4 [collection flutter meta riverpod state_notifier]
- flutter_web_plugins 0.0.0 [flutter js characters collection material_color_utilities meta vector_math]
- http 0.13.4 [async http_parser meta path]
- http_parser 4.0.1 [collection source_span string_scanner typed_data]
- image_picker_android 0.8.5+1 [flutter flutter_plugin_android_lifecycle image_picker_platform_interface]
- image_picker_for_web 2.1.8 [flutter flutter_web_plugins image_picker_platform_interface]
- image_picker_ios 0.8.5+5 [flutter image_picker_platform_interface]
- image_picker_platform_interface 2.5.0 [cross_file flutter http plugin_platform_interface]
- intl 0.17.0 [clock path]
- js 0.6.4
- json_annotation 4.5.0 [meta]
- material_color_utilities 0.1.4
- meta 1.7.0
- path 1.8.1
- path_provider_android 2.0.16 [flutter path_provider_platform_interface]
- path_provider_ios 2.0.10 [flutter path_provider_platform_interface]
- path_provider_linux 2.1.7 [ffi flutter path path_provider_platform_interface xdg_directories]
- path_provider_macos 2.0.6 [flutter path_provider_platform_interface]
- path_provider_platform_interface 2.0.4 [flutter platform plugin_platform_interface]
- path_provider_windows 2.1.0 [ffi flutter path path_provider_platform_interface win32]
- petitparser 5.0.0 [meta]
- platform 3.1.0
- plugin_platform_interface 2.1.2 [meta]
- process 4.2.4 [file path platform]
- riverpod 1.0.3 [collection meta state_notifier]
- sky_engine 0.0.99
- source_span 1.8.2 [collection path term_glyph]
- state_notifier 0.7.2+1 [meta]
- stream_transform 2.0.0
- string_scanner 1.1.0 [charcode source_span]
- term_glyph 1.2.0
- typed_data 1.3.1 [collection]
- uuid 3.0.6 [crypto]
- vector_math 2.1.2
- win32 2.7.0 [ffi]
- xdg_directories 0.2.0+1 [meta path process]
- xml 6.1.0 [collection meta petitparser]

Device

Samsung Galaxy S7 -- emulated

OS

Android 8

CLI Version

9.1.0

Additional Context

Here's the amplifyconfiguration.dart file:

const amplifyconfig = ''' {
    "UserAgent": "aws-amplify-cli/2.0",
    "Version": "1.0",
    "api": {
        "plugins": {
            "awsAPIPlugin": {
                "tracktunes": {
                    "endpointType": "GraphQL",
                    "endpoint": "https://xxxxxxxxxxxxxxxxxx",
                    "region": "us-east-1",
                    "authorizationType": "AWS_IAM"
                },
                "tracktunesCognito": {
                    "endpointType": "GraphQL",
                    "endpoint": "https://xxxxxxxxxxxxxxxxxx",
                    "region": "us-east-1",
                    "authorizationType": "AMAZON_COGNITO_USER_POOLS"
                },
                "lambdaFunctions": {
                    "endpointType": "REST",
                    "endpoint": "https://xxxxxxxxxxxxxxxxxx",
                    "region": "us-east-1",
                    "authorizationType": "AWS_IAM"
                },
                "lambdaFunctionsCognito": {
                    "endpointType": "REST",
                    "endpoint": "https://xxxxxxxxxxxxxxxxxx",
                    "region": "us-east-1",
                    "authorizationType": "AMAZON_COGNITO_USER_POOLS"
                }
            }
        }
    },
    "auth": {
        "plugins": {
            "awsCognitoAuthPlugin": {
                "UserAgent": "aws-amplify-cli/0.1.0",
                "Version": "0.1.0",
                "IdentityManager": {
                    "Default": {}
                },
                "CredentialsProvider": {
                    "CognitoIdentity": {
                        "Default": {
                            "PoolId": "xxxxxxxxxxxxxxxxxx",
                            "Region": "us-east-1"
                        }
                    }
                },
                "CognitoUserPool": {
                    "Default": {
                        "PoolId": "xxxxxxxxxxxxxxxxxx",
                        "AppClientId": "xxxxxxxxxxxxxxxxxx",
                        "Region": "us-east-1"
                    }
                },
                "Auth": {
                    "Default": {
                        "authenticationFlowType": "USER_SRP_AUTH",
                        "socialProviders": [],
                        "usernameAttributes": [
                            "EMAIL"
                        ],
                        "signupAttributes": [
                            "EMAIL"
                        ],
                        "passwordProtectionSettings": {
                            "passwordPolicyMinLength": 8,
                            "passwordPolicyCharacters": []
                        },
                        "mfaConfiguration": "OFF",
                        "mfaTypes": [
                            "SMS"
                        ],
                        "verificationMechanisms": [
                            "EMAIL"
                        ]
                    }
                },
                "AppSync": {
                    "Default": {
                        "ApiUrl": "xxxxxxxxxxxxxxxxxx",
                        "Region": "us-east-1",
                        "AuthMode": "AWS_IAM",
                        "ClientDatabasePrefix": "tracktunes_AWS_IAM"
                    },
                    "tracktunes_AMAZON_COGNITO_USER_POOLS": {
                        "ApiUrl": "xxxxxxxxxxxxxxxxxx",
                        "Region": "us-east-1",
                        "AuthMode": "AMAZON_COGNITO_USER_POOLS",
                        "ClientDatabasePrefix": "tracktunes_AMAZON_COGNITO_USER_POOLS"
                    }
                },
                "S3TransferUtility": {
                    "Default": {
                        "Bucket": "userdata94336-dev",
                        "Region": "us-east-1"
                    }
                }
            }
        }
    },
    "storage": {
        "plugins": {
            "awsS3StoragePlugin": {
                "bucket": "userdata94336-dev",
                "region": "us-east-1",
                "defaultAccessLevel": "guest"
            }
        }
    }
}''';
HuiSF commented 2 years ago

Hello @dorontal thanks for opening this issue.

The exception is self explanatory - There is more than one API configured for this plugin with matching endpoint type.. This exception gets thrown when you have multiple keys under awsAPIPlugin and using API plugin along with DataStore. DataStore requires only 1 key to be presented under awsAPIPlugin.

Why do you need to modify the config file? I think multi-auth in Amplify library doesn't require manual modification.

dorontal commented 2 years ago

Hi @HuiSF -

Why do you need to modify the config file? I think multi-auth in Amplify library doesn't require manual modification.

I need to modify the config file because - as the documentation tells us - I need to have multi-Auth for Cognito work, see: https://docs.amplify.aws/lib/restapi/authz/q/platform/flutter/#cognito-user-pool-authorization

HuiSF commented 2 years ago

Hi @dorontal thanks for the follow up and more details.

The document listed multi-auth modes configuration for GraphQL and REST APIs is not applicable for DataStore use case at this moment. DataStore relies on single GraphQL API to back the sync engine, and resolves valid auth method by priority as multi-auth resolution. I will do the following

  1. Update the documents state that REST and GraphQL multi-auth modes configurations are not applicable to DataStore
  2. Create a feature request to support choose which API to be used as DataStore sync engine while configuring DataStore
dorontal commented 2 years ago

@HuiSF - thanks very much for your response.

I am unable to turn off multi-auth in my app. This is a feature request to enable multi-Auth for GraphQL queries. It seems to me that this is an essential feature.

Regarding step 2 above - I'm not sure I understand - there is no need to specify which API to use for the Datastore Sync because as this link describes - multi-auth in Amplify library - Datastore's library does this selection (of authorization type) automatically -- see table after following the link to the documentation -- so the library already probably has code for that automatic selection of the proper API configuration to use (Datastore derives which auth to use, from the schema, as the doc points out) which would imply that this feature is almost fully implemented already.

HuiSF commented 2 years ago

Hey @dorontal sorry for the confusion. those two steps are meant for me to take actions :D

For point 2, taking your configuration, we should be able to do

final datastore = AmplifyDataStore(
  modelProvider: ModelProvider.instance,
  apiName: 'tracktunesCognito',
);

So when you have multiple GraphQL endpoints listed in the configuration, DataStore can still work.

dorontal commented 2 years ago

@HuiSF

BTW, there is another named parameter (mentioned in the documentation) that must be added for using multiAuth and that is authModeStrategy, e.g:

        AmplifyDataStore(
          modelProvider: ModelProvider.instance,
          authModeStrategy: AuthModeStrategy.multiAuth,
        ),

it was my understanding that the Datastore would try to automatically deduce which auth to use from the schema, when this flag authModeStrategy is set to AuthModeStrategy.multiAuth.

Not sure why you want to add the apiName or how it would help as the DataStore will need both apiNames not just one of them - one for IAM, one for Cognito.

HuiSF commented 2 years ago

authModeStrategy: AuthModeStrategy.multiAuth this parameter doesn't resolve valid auth between GraphQL API configurations. It still requires 1 GraphQL endpoint to be listed in the configuration file, (so it won't fix the exception you encountered).

apiName: 'tracktunesCognito' will be an upcoming improvement (I renamed your issue and marked it as a feature request), please stay tuned.

dorontal commented 2 years ago

@HuiSF

The statement authmodeStragegy: AuthModeStrategy.multiAuth "doesn't resolve valid auth between GraphQL API" - does not make sense to me - I thought that DataStore does resolve the valid auth, automatically, as the documentation says, when it sees that flag.

Thinking about the apiName feature request a bit more, it makes less sense now, because an addition of apiName to AmplifyDataStore() would let you use GraphQL with only one Auth, not multiple Auths.

In the case of the apiName flag to AmplifyDataStore() the API name would be specified at the configuration level - at the call to Amplify.addPlugins() - which means it would be set in stone once your app starts, which means multi-Auth will not be possible for GraphQL APIs with that flag.

[ I know Amplify Datastore it's still early code - so very grateful for this library overall, the benefits are immense! Thanks so much! ]

HuiSF commented 2 years ago

I think there is misunderstanding here...

The multi-auth modes for GraphQL and REST using Amplify API plugins is conceptually different from multi-auth in DataStore.

The former - configure multiple APIs, each API is with a different auth mode, you choose which API to use in order to choose a specific auth mode. The latter - DataStore relies on a single GraphQL API, it resolve a usable auth method (providing valid token for this GraphQL PI) from an array of choosable auth methods (listed by @auth directive in model schema).

Configuring multi-auth modes in API doesn't do anything for DataStore multi-auth. Hope that makes sense.

We need to introduce apiName for DataStore in order to support this particular configuration of yours, which has multiple GraphQL endpoints listed.

But if you don't need multiple GraphQL API endpoints (the configuration file has only 1 GraphQL endpoint), DataStore multi-auth is already supported.

dorontal commented 2 years ago

Hi @HuiSF, the issue is that when you configure the same GraphQL API endpoint with a Cognito user pool authorization type by adding a block in the amlifyconfiguration.dart - for some reason this causes the exception to be thrown by DataStore init. DataStore cannot handle the fact that the amplifyconfiguration.dart has changed in that way at the API section - but that change was the one that's documented by Amplify for us to do in order to get the app to use Cognito authorization with the GraphQL API. That part works in my app with the API but not with DataStore: as soon as I introduced it - it caused the DataStore to throw the exception and fail.

Moreover, when I remove the Cognito Authorization section from the amplifyconfiguration.dart then DataStore no longer throws that exception.

That is what I call a bug, not a feature request.

I do not see how adding apiName to DataStore initialization at the call to Amplify.addPlugins(..., AmplifyDatastore(..., apiName: .... would help. The DataStore cannot be tied to a single API at that level - it needs to handle more than one API but apiName ties it to just one API which is exactly what you are trying to avoid.

The only options I have right now are: 1) Do not use DataStore until it can handle the two authorizationType blocks in the GraphQL API section of amplifyconfiguration.dart - i.e until it no longer throws an exception 2) Stop using GraphQL with multi-Auth, only use it with single Auth (I cannot afford to do that in my app, it needs multi-Auth)

Why is this not a bug? DataStore is throwing an exception with valid code.

dorontal commented 2 years ago

As this DataStore Amplify documentation mentions, when a GraphQL model has multiple @auth rules,

the rules will be ranked by priority (see below), and DataStore will attempt the synchronization with each authorization type until one succeeds (or they all fail).

This implies that 1) DataStore uses GraphQL API 2) when there is a GraphQL API with one endpoint but two authorizations (one being IAM, the other being Cognito, as in my setup above) then DataStore, when it tries to sync with the public @auth rules it will need to use the GraphQL API that was configured for IAM and when DataStore tries to sync with private or allow: owner @auth rules it will need to use the GraphQL API that was configured for Cognito. It is my understanding that DataStore is supposed to figure out automatically not only which @auth rule to use but also which API configuration in amplifyconfiguration.dart goes with which @auth rule that it is trying to sync with - as it mentions, it will try to sync with all the @auth rules in your schema until one succeeds or all fail. This implies that DataStore must consult both the Cognito API section and the IAM API section, depending on which @auth rule it is currently trying. This is why adding an apiName does not seem to help - DataStore will need to try all configured GraphQL APIs that have the same endpoint in the amplifyconfiguration.dart in order to go through all the @auth rules - it cannot just rely on one of the APIs - that is not enough for it to go through all the Auth rules.

I do not understand how adding apiName to the DataStore at configuration can help or what it can help with, with respect to this bug.

HuiSF commented 2 years ago

Here's a summary of my findings:

  1. API plugin (use without DataStore plugin) and DataStore plugin both support multi-auth but they are working in different ways
  2. API plugin multi-auth modes is not configurable by Amplify CLI, it requires manual configuration following this doc. Auth modes info is listed under the api key in configuration file. (configuration files looks like the one pasted in this issue)
  3. To enable multi-auth in DataStore, a developer needs to configure multi-auth using Amplify CLI, follow the steps shown in the following screenshot, auth modes info is listed under the auth key

image

Expand to see the configuration file of DataStore multi-auth ```dart const amplifyconfig = ''' { "UserAgent": "aws-amplify-cli/2.0", "Version": "1.0", "api": { "plugins": { "awsAPIPlugin": { "oncalltriagingca": { "endpointType": "GraphQL", "endpoint": "https://xxx.appsync-api.us-west-2.amazonaws.com/graphql", "region": "us-west-2", "authorizationType": "AMAZON_COGNITO_USER_POOLS", "apiKey": "xxx" } } } }, "auth": { "plugins": { "awsCognitoAuthPlugin": { "UserAgent": "aws-amplify-cli/0.1.0", "Version": "0.1.0", "IdentityManager": { "Default": {} }, "CredentialsProvider": { "CognitoIdentity": { "Default": { "PoolId": "us-west-2:25e89d87-1f92-4b64-9b55-xxx", "Region": "us-west-2" } } }, "CognitoUserPool": { "Default": { "PoolId": "us-west-xxxx", "AppClientId": "xxx", "Region": "us-west-2" } }, "Auth": { "Default": { "authenticationFlowType": "CUSTOM_AUTH", "socialProviders": [], "usernameAttributes": [], "signupAttributes": [ "EMAIL" ], "passwordProtectionSettings": { "passwordPolicyMinLength": 8, "passwordPolicyCharacters": [] }, "mfaConfiguration": "OFF", "mfaTypes": [ "SMS" ], "verificationMechanisms": [ "EMAIL" ] } }, "AppSync": { "Default": { "ApiUrl": "https://xxx.appsync-api.us-west-2.amazonaws.com/graphql", "Region": "us-west-2", "AuthMode": "AMAZON_COGNITO_USER_POOLS", "ClientDatabasePrefix": "oncalltriagingca_AMAZON_COGNITO_USER_POOLS" }, "oncalltriagingca_API_KEY": { "ApiUrl": "https://xxx.appsync-api.us-west-2.amazonaws.com/graphql", "Region": "us-west-2", "AuthMode": "API_KEY", "ApiKey": "da2-xb2eywexpndhdobpzhlimp2mli", "ClientDatabasePrefix": "oncalltriagingca_API_KEY" }, "oncalltriagingca_AWS_IAM": { "ApiUrl": "https://xxx.appsync-api.us-west-2.amazonaws.com/graphql", "Region": "us-west-2", "AuthMode": "AWS_IAM", "ClientDatabasePrefix": "oncalltriagingca_AWS_IAM" } } } } } }'''; ```
  1. If a developer enables multi-auth modes for API plugin following steps 2, it breaks DataStore functionality when DataStore plugin is used in the same App, as DataStore currently doesn’t support multiple GraphQL endpoints to be listed in the configuration file under the api key in the configuration file.

Hi @dorontal for the original issue you posted, it was due to point 4.

Do you want to use multi-auth with API plugin, and also need DataStore in the same App? If so, unfortunately it's not working due to the reason explained above, we would need to provide the feature from DataStore allowing specifying which GraphQL endpoints to let DataStore use.

Or do you want to use multi-auth for only DataStore? If so, please follow the Amplify CLI multi-auth configuration mentioned in point 3, and specify authModeStrategy: AuthModeStrategy.multiAuth when initiate DataStore.

dorontal commented 2 years ago

Hi @HuiSF - and thank you for your response and for the informative explanation. Nothing in this explanation is new to me, however, and this explanation is exactly my reason for opening this issue, specifically, your step 4 is this issue.

Not using multi-auth in API is not an option for us. App is already using multi-auth with API plugin. It has to use multi-Auth, because it provides a services to people who don't even have an account and have never logged in (guests) like the ability to see a limited view of any user's profile or the ability to view publicly posted documents. But the GraphQL API also needs to provide many services to signed-in users, such as seeing private documents. So both API configurations are set up - one to use IAM and the other to use Cognito, as prescribed by the documentation and as you can see in amplifyconfiguration.dart and it works just fine.

One minor clarification of point #4 above: it's not that GraphQL uses multiple endpoints - there is always only one endpoint for GraphQL: if you look in my amplifyconfiguration.dart above, for example, you'll see that the URL for both tracktunes and tracktunesCognito is exactly the same endpoint. I believe you meant "DataStore currently doesn't support multiple GraphQL configuurations or "API configurations" but it's not an endpoint, which is a URL, which is the same for both. One of the main features of GraphQL is that it always uses only one endpoint. I think it may be clearer to use "configuration" instead of endpoint.

So yes, the conclusion remains the same: that DataStore is not yet usable when the GraphQL API is configured with more than one authorization type. I cannot use it.

What's interesting is that the DataStore documentation on multi Auth seems to suggest that DataStore is supposed to work out of the box in this case when the GraphQL API is configured with more than one authorization type. The documentation even goes as far as to say that DataStore can pick the proper @auth rule automatically - and this means it knows how to pick either the IAM version or the Cognito version of the GraphQL API automatically. That's what the documentation claims. But when I run it, it throws an exception that contradicts the documentation - I think that exception should not be thrown and instead it would be nice to see DataStore automatically pick the right GraphQL API auth configuration, as the documentation claims it can.

HuiSF commented 2 years ago

it's not that GraphQL uses multiple endpoints - there is always only one endpoint for GraphQL

I was thinking GraphQL + different Auth = different endpoints as they are requiring different tokens.

What's interesting is that the DataStore documentation on multi Auth seems to suggest that DataStore is supposed to work out of the box

This is based on the assumption configuring DataStore multi-auth using Amplify CLI. and -

DataStore can pick the proper @auth rule automatically

DataStore picks up the auth info from the information under the auth key, not from the api key.

The documentation is confusing, the API multi-auth is not related to DataStore multi-auth at all. :/ Will fix this. All this is due to a lack of support to use API plugin and DataStore plugin in the same App. The improvements are on their way.

dorontal commented 2 years ago

FYI, just noticed that there is another similar issue to this one - #1651

sumchans commented 1 year ago

Does anybody have any idea when this issue will be fixed?

HuiSF commented 1 year ago

Hi @dorontal amplify-flutter DataStore supports multi-auth.

This issue is that the multi-auth for API plugin and multi-auth for DataStore are not compatible with each other as I explained in previous discussion.

We've updated the documentation of multi-auth in API plugin to state this issue. This issue is also recorded in https://github.com/aws-amplify/amplify-flutter/issues/1945 (point 5) to track the solution of this issue that is currently being investigated, thank you for your patience.

dorontal commented 1 year ago

Hi @HuiSF - thank you for the clarification that this is being dealt with, also for the clarification of the incompatibility issue, which I think I understand better now. Definitely will wait patiently but it's good to know that we are being listened to, thanks so much!

seanhamstra commented 1 year ago

Hey @dorontal sorry for the confusion. those two steps are meant for me to take actions :D

For point 2, taking your configuration, we should be able to do

final datastore = AmplifyDataStore(
  modelProvider: ModelProvider.instance,
  apiName: 'tracktunesCognito',
);

So when you have multiple GraphQL endpoints listed in the configuration, DataStore can still work.

Has this part been addressed? We have multiple GraphQL endpoints and need a way to specify for DataStore.

dorontal commented 1 year ago

Hi @HuiSF - what is your current ETA for resolving this issue? It has been around for almost six months - still unable to use DataStore because of it. Thanks.

HuiSF commented 1 year ago

Hi @dorontal sorry for the delayed response and thanks for your patience, we are communicating with amplify-swift and amplify-android maintainers as well as relevant internal teams regarding this issue to determine a suitable long term solution. I'll update the progress.

dorontal commented 1 year ago

@HuiSF -- In case you were wondering why I believe multi-mode authorization is common / needed, here's our use case that demands it and which I believe is shared by many apps:

I'm putting this use-case here also because maybe somebody can suggest a way to implement this use-case without using multi-auth, which would also allow us to move forward.

Our use-case that needs multi-auth is this;

1) DynamoDB saves a user-profile model for each user of our app, accessed via GraphQL 2) Some fields in the user model (e.g. how much money they've made) are private and can only be seen by the owner user 3) We still want to be able to show the non-private fields of any user's profile to anyone -- even people without an account at all, or users who are not signed in, should be able to see limited user profile information about other users (e.g, if you go to your Github home page while signed out, you still can see some stats about your username there)

If we only needed parts 1 and 2 above, we'd be able to get by with Cognito authorization alone, no multi-auth needed.

But if we want to share info about users with anybody, as in part 3, we must use IAM for that, hence we must use multi-auth in such an app.

This use case is why I think this issue is very important not just to me, but to many others. It seems to me like it would be a common use case.

dorontal commented 1 year ago

BTW, for those of you who need a workaround, I am using this workaround: created a lambda function that performs the needed DB operation (just a GET, in my case); the lambda function is accessed by guest users via IAM and a REST API.