Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GraphQL Multi-Auth Not Working #3089

Closed
dan-codes1 opened this issue Jul 14, 2023 · 19 comments
Closed

GraphQL Multi-Auth Not Working #3089

dan-codes1 opened this issue Jul 14, 2023 · 19 comments
Assignees
Labels
api Issues related to the API category auth Issues related to the Auth category question General question requires attention Follow up needed for more than 10 days

Comments

@dan-codes1
Copy link

dan-codes1 commented Jul 14, 2023

Describe the bug

My app does not work with multi-auth (API and CognitoPools) simultaneously. I have set my schema to accept multi auth but it still does not work. If I set the default auth mode to API, cognito stops working, and if I set the auth mode to cognito, Api stops working. For example:

type To-Do @model @auth(
                  rules: [
                     {allow: owner}, 
                     {allow: public, operations: [read]}, 
                     {allow: private, operations: [read]}
                 ]) {
  id: ID!
}

With the schema above, I am unable to add a new To-Do or update/delete a To-Do after authenticating with cognito.

here is my API Plugin configuration for amplifyConfiguration.json

"UserAgent": "aws-amplify-cli/2.0",
"Version": "1.0",
"api": {
    "plugins": {
        "awsAPIPlugin": {
            "Eemo": {
                "endpointType": "GraphQL",
                "endpoint": "blahblah/graphql",
                "region": "us-west-2",
                "authorizationType": "API_KEY",
                "apiKey": "blahlbah"
            }
        }
    }
},

here is my APPSync configuration for awsConfiguration.json

"AppSync": {
    "Default": {
        "ApiUrl": "blahblah/graphql",
        "Region": "us-west-2",
        "AuthMode": "API_KEY",
        "ApiKey": "blahblah",
        "ClientDatabasePrefix": "Eemo_API_KEY"
    },
    "Eemo_AWS_IAM": {
        "ApiUrl": "blahblah/graphql",
        "Region": "us-west-2",
        "AuthMode": "AWS_IAM",
        "ClientDatabasePrefix": "Eemo_AWS_IAM"
    },
    "Eemo_AMAZON_COGNITO_USER_POOLS": {
        "ApiUrl": "blahblah/graphql",
        "Region": "us-west-2",
        "AuthMode": "AMAZON_COGNITO_USER_POOLS",
        "ClientDatabasePrefix": "Eemo_AMAZON_COGNITO_USER_POOLS"
    }
},

Steps To Reproduce

try to follow the description and use the schema/configuration provided.

Expected behavior

I expect it to allow owner to create update and delete

Amplify Framework Version

2.12.0

Amplify Categories

API

Dependency manager

Swift PM

Swift version

5.8

CLI version

12.1.1

Xcode version

14

Relevant log output

<details>
<summary>Log Messages</summary>


INSERT LOG MESSAGES HERE
```

Is this a regression?

Yes

Regression additional context

No response

Platforms

No response

OS Version

MacOs Ventura

Device

iphone 14

Specific to simulators

No response

Additional context

No response

@harsh62 harsh62 added api Issues related to the API category auth Issues related to the Auth category labels Jul 14, 2023
@harsh62
Copy link
Member

harsh62 commented Jul 14, 2023

@dan-codes1 Thanks for opening the issue. Would you be able to share verbose logs and error that you are seeing?

You can enable verbose logging to the console by doing this before calling Amplify.configure:

Amplify.Logging.logLevel = .verbose

This will immensely help us narrow down our investigation.

@harsh62 harsh62 added the pending-community-response Issue is pending response from the issue requestor label Jul 14, 2023
@dan-codes1
Copy link
Author

@harsh62 this is the log:

<<<< FigByteStream >>>> FigByteStreamStatsLogOneRead: ByteStream read of 137,230 bytes @ 63,591 took 0.7807 sec. to complete, 2 reads >= 0.5 sec.
Starting query 9B81E8D8-F289-4B0D-BEB3-6E1EF2F03DDC
{
"variables" : {
"limit" : 2,
"filter" : {
"and" : [
{
"favouriteUserId" : {
"eq" : "b551c6ad-c91a-41d0-bd1a-e06a75f4726f"
}
},
{
"favouriteEemoId" : {
"eq" : "8549F477-85BF-4633-8D07-BE7A2146E33C"
}
}
]
}
},
"query" : "query ListFavourites($filter: ModelFavouriteFilterInput, $limit: Int) {\n listFavourites(filter: $filter, limit: $limit) {\n items {\n id\n createdAt\n favouriteEemoId\n favouriteUserId\n updatedAt\n eemo {\n id\n __typename\n }\n user {\n id\n __typename\n }\n __typename\n }\n nextToken\n }\n}"
}
Starting network task for query 9B81E8D8-F289-4B0D-BEB3-6E1EF2F03DDC

successful amplify operation graphQLApi

Creating loaded list of Favourite
Starting mutation 1C507575-787A-4A12-96F9-E179DE42414C
{
"query" : "mutation CreateFavourite($input: CreateFavouriteInput!) {\n createFavourite(input: $input) {\n id\n createdAt\n favouriteEemoId\n favouriteUserId\n updatedAt\n eemo {\n id\n __typename\n }\n user {\n id\n __typename\n }\n __typename\n }\n}",
"variables" : {
"input" : {
"favouriteEemoId" : "8549F477-85BF-4633-8D07-BE7A2146E33C",
"id" : "98A2A709-43F1-4E51-AE2F-684572033BD2",
"favouriteUserId" : "b551c6ad-c91a-41d0-bd1a-e06a75f4726f"
}
}
}
Starting network task for mutation 1C507575-787A-4A12-96F9-E179DE42414C
GraphQLResponseError: GraphQL service returned a successful response containing errors: [Amplify.GraphQLError(message: "Not Authorized to access createFavourite on type Mutation", locations: Optional([Amplify.GraphQLError.Location(line: 2, column: 3)]), path: Optional([Amplify.JSONValue.string("createFavourite")]), extensions: Optional(["data": Amplify.JSONValue.null, "errorInfo": Amplify.JSONValue.null, "errorType": Amplify.JSONValue.string("Unauthorized")]))]
Recovery suggestion: The list of GraphQLError contains service-specific messages

@harsh62 harsh62 added bug Something isn't working and removed pending-community-response Issue is pending response from the issue requestor labels Jul 14, 2023
@dan-codes1
Copy link
Author

@harsh62 just to clarify, the Favourite model has the following auth rules:

type Favourite @model @auth(rules: [{allow: owner}, {allow: public, operations: [read]}, {allow: private, operations: [read]}]) {
id: ID!
}

@royjit
Copy link
Contributor

royjit commented Jul 19, 2023

You amplifyconfiguration.json does not contain multiple authorizations, please follow the steps here https://docs.amplify.aws/lib/graphqlapi/authz/q/platform/ios/#configure-multiple-authorization-modes to configure the endpoints and invoke the api appropriately.

@royjit royjit added pending-community-response Issue is pending response from the issue requestor question General question and removed bug Something isn't working labels Jul 19, 2023
@dan-codes1
Copy link
Author

dan-codes1 commented Jul 19, 2023

@royjit I actually always added that but it was always overridden even after calling amplify push

"api": {
"plugins": {
"awsAPIPlugin": {
"Eemo_API_KEY": {
"endpointType": "GraphQL",
"endpoint": "blah blah",
"region": "us-west-2",
"authorizationType": "API_KEY",
"apiKey": " blah blah "
},
"Eemo_AMAZON_COGNITO_USER_POOLS": {
"endpointType": "GraphQL",
"endpoint": " blah blah",
"region": "us-west-2",
"authorizationType": "AMAZON_COGNITO_USER_POOLS",
"apiKey": "blah blah"
}
}
}
},

however even when i run amplify push --force the changes do not seem to be pushed because when i run amplify pull after running amplify push --force, the amplifyconfiguration.json file doesnt have the change added above.

Also, I get a new error:
APIError: More than one graphQL API configured. Could not infer which API to call
Recovery suggestion: Use the apiName to specify which API to call

failed amplify operation graphQLApi with error:
The operation couldn’t be completed. (Amplify.APIError error 1.)

@dan-codes1
Copy link
Author

@royjit @harsh62 any updates on this? Almost been 2 weeks.

@harsh62
Copy link
Member

harsh62 commented Jul 27, 2023

@dan-codes1 If you are changing the configuration file by hand, it will always get overridden during amplify pull/push. From the latest error it seems the graphQL API is not configured correctly. I would suggest to follow the guide to set up the category again and document each steps. If you still face an issue, please comment on the issue with all the steps you have documented and we will try to recreate the issue at our end.
ATM, I am not able to repro this issue in my local project.

@dan-codes1
Copy link
Author

dan-codes1 commented Jul 29, 2023

i have created a new api and even user pool and added multi auth to the API via cli. Multi auth is still not working and it still doesn't add the multi auth under plugins API section of the amplifyconfiguration.json file.

This is the documented cli step:

? Choose the default authorization type for the API API key
✔ Enter a description for the API key: · public api
✔ After how many days from now the API key should expire (1-365): · 30
? Configure additional auth types? Yes
? Choose the additional authorization types you want to configure for the API Am
azon Cognito User Pool
Cognito UserPool configuration
Use a Cognito user pool configured as a part of this project.

⚠️ WARNING: owners may reassign ownership for the following model(s) and role(s): Message: [owner], Favourite: [owner] If this is not intentional, you may want to apply field-level authorization rules to these fields. To read more: https://docs.amplify.aws/cli/graphql/authorization-rules/#per-user--owner-based-data-access.
✅ GraphQL schema compiled successfully.

Edit your schema at /Users/danieleze/Documents/eemo/amplify/backend/api/eemo/schema.graphql or place .graphql files in a directory at /Users/danieleze/Documents/eemo/amplify/backend/api/eemo/schema
✅ Successfully updated resource

@harsh62 harsh62 added requires attention Follow up needed for more than 10 days and removed pending-community-response Issue is pending response from the issue requestor labels Aug 4, 2023
@tobias-feldmann
Copy link

Have the same problem since upgrading from Amplify v1 to v2.
Previously, Multi-Auth worked without any problems. I only use the DataStore and not the API directly.
Everything configured as described. And it still worked before the upgrade.

@dan-codes1
Copy link
Author

@tobias-feldmann oh so your multi with worked in v1 but stopped in v2?

I started with v2 and mine has never worked.

@tobias-feldmann
Copy link

Yes exactly, with the same setup it worked with V1 @dan-codes1.

I also deleted the Amplify project and created it again.
When creating the API, i added Amazon Cognito User Pool as the default authorization type and additional IAM. Allow unauthenticated logins is active for Auth.
Only users who are logged in via the user pool can save data.

The DatastorePlugin is configured like this:

Amplify.add( plugin: AWSDataStorePlugin( modelRegistration: models, configuration: .custom(authModeStrategy: .multiAuth) ) )

@lawmicha
Copy link
Contributor

lawmicha commented Aug 9, 2023

This is a bit verbose, so i will try to summarize this in comment below. Here's my attempt to reproduce this problem, I'm currently using Amplify CLI version 12.2.3

? Select from one of the below mentioned services: GraphQL
? Here is the GraphQL API that we will create. Select a setting to edit or continue Authorization modes: API key (default, expi
ration time: 7 days from now)
? Choose the default authorization type for the API API key
✔ Enter a description for the API key: · 
✔ After how many days from now the API key should expire (1-365): · 365
? Configure additional auth types? No
? Here is the GraphQL API that we will create. Select a setting to edit or continue Conflict detection (required for DataStore)
: Disabled
? Enable conflict detection? Yes
? Select the default resolution strategy Auto Merge
? Here is the GraphQL API that we will create. Select a setting to edit or continue Continue
? Choose a schema template: Blank Schema

The schema i used

# This "input" configures a global authorization rule to enable public access to
# all models in this schema. Learn more about authorization rules here: https://docs.amplify.aws/cli/graphql/authorization-rules
type Todo @model @auth(
                  rules: [
                     {allow: owner}, 
                     {allow: public, operations: [read]}, 
                     {allow: private, operations: [read]}
                 ]) {
  id: ID!
  content: String
}

When i run amplify push, it also prompted me to create the Cognito User Pool. I selected Default Configuration and Username. When asked to generated code (legacy file for AppSync SDK), selected No (we'll generate the model files from amplify codegen models)

After this finished, the amplifyconfiguration.json file looks like this:

{
    "UserAgent": "aws-amplify-cli/2.0",
    "Version": "1.0",
    "api": {
        "plugins": {
            "awsAPIPlugin": {
                "amplify3089": {
                    "endpointType": "GraphQL",
                    "endpoint": "https://xxxxxxxxxxx.appsync-api.us-east-2.amazonaws.com/graphql",
                    "region": "us-east-2",
                    "authorizationType": "API_KEY",
                    "apiKey": "da2-xxxxxxxxxxx"
                }
            }
        }
    },
    "auth": {
        "plugins": {
            "awsCognitoAuthPlugin": {
                "UserAgent": "aws-amplify/cli",
                "Version": "0.1.0",
                "IdentityManager": {
                    "Default": {}
                },
                "AppSync": {
                    "Default": {
                        "ApiUrl": "https://xxxxxxxxxxx.appsync-api.us-east-2.amazonaws.com/graphql",
                        "Region": "us-east-2",
                        "AuthMode": "API_KEY",
                        "ApiKey": "da2-xxxxxxxxxxx",
                        "ClientDatabasePrefix": "amplify3089_API_KEY"
                    },
                    "amplify3089_AMAZON_COGNITO_USER_POOLS": {
                        "ApiUrl": "https://xxxxxxxxxxx.appsync-api.us-east-2.amazonaws.com/graphql",
                        "Region": "us-east-2",
                        "AuthMode": "AMAZON_COGNITO_USER_POOLS",
                        "ClientDatabasePrefix": "amplify3089_AMAZON_COGNITO_USER_POOLS"
                    }
                },
                "CredentialsProvider": {
                    "CognitoIdentity": {
                        "Default": {
                            "PoolId": "us-east-2:xxxxxxxxxxx",
                            "Region": "us-east-2"
                        }
                    }
                },
                "CognitoUserPool": {
                    "Default": {
                        "PoolId": "us-east-xxxxxx",
                        "AppClientId": "xxxxxx",
                        "Region": "us-east-2"
                    }
                },
                "Auth": {
                    "Default": {
                        "authenticationFlowType": "USER_SRP_AUTH",
                        "socialProviders": [],
                        "usernameAttributes": [],
                        "signupAttributes": [
                            "EMAIL"
                        ],
                        "passwordProtectionSettings": {
                            "passwordPolicyMinLength": 8,
                            "passwordPolicyCharacters": []
                        },
                        "mfaConfiguration": "OFF",
                        "mfaTypes": [
                            "SMS"
                        ],
                        "verificationMechanisms": [
                            "EMAIL"
                        ]
                    }
                }
            }
        }
    }
}

I ran amplify codegen models to generate the Swift Model files.

Using SPM, I added https://github.com/aws-amplify/amplify-swift.git, up to next major 2.0.0

  • Amplify
  • AWSAPIPlugin
  • AWSCognitoAuthPlugin
  • AWSDataStorePlugin

In the App's main file, ensure .multiAuth is configured on DataStore.

import SwiftUI
import Amplify
import AWSAPIPlugin
import AWSCognitoAuthPlugin
import AWSDataStorePlugin

@main
struct amplify3089App: App {
    
    init() {
        do {
            Amplify.Logging.logLevel = .verbose
            
            try Amplify.add(plugin: AWSCognitoAuthPlugin())
            try Amplify.add(plugin: AWSAPIPlugin(modelRegistration: AmplifyModels()))
            try Amplify.add(plugin: AWSDataStorePlugin(modelRegistration: AmplifyModels(),
                                                       configuration: .custom(authModeStrategy: .multiAuth)))
            try Amplify.configure()
            print("Amplify configured with DataStore, API, and Auth plugins!")
        } catch {
            print("Failed to initialize Amplify with \(error)")
        }
    }
    
    var body: some Scene {
        WindowGroup {
            AppView()
        }
    }
}

I created a user through the CLI:

aws cognito-idp admin-create-user --user-pool-id [POOL_ID] --username [USERNAME]
aws cognito-idp admin-set-user-password --user-pool-id [POOL_ID] --username [USERNAME] --password [PASSWORD] --permanent

The [POOL_ID] can be found in amplifyconfiguration.json under auth.plugin.awsCognitoAuthPlugin.CognitoUserPool.Default.PoolId.

Back in the App, I established the DataStore subscriptions by calling Amplify.DataStore.start(), through a button

func start() {
    Task {
        try? await Amplify.DataStore.start()
    }
}

Taking a look at the logs, I can see onCreate subscriptions that is created:

  onCreateTodo(owner: $owner) {
    id
    content
    createdAt
    updatedAt
    __typename
    _version
    _deleted
    _lastChangedAt
    owner
  }
}","variables":{"owner":""}}"

The subscription is established with owner field value of empty string. I believe this is the problem. It's establishing the subscription with the correct auth mode while the user is signed out (API Key), but the mutations are not being received by the client, because it's subscribed to a specific owner. For example, in the AppSync console, sending a mutation does not trigger a subscription event:

On one window, using API Key, subscribe to the data with the empty owner field:

subscription MySubscription {
  onCreateTodo(owner: "") {
    _deleted
    _lastChangedAt
    _version
    content
    createdAt
    id
    owner
    updatedAt
  }
}

On a second window, using API Key, subscribe without owner field:

subscription MySubscription {
  onCreateTodo {
    _deleted
    _lastChangedAt
    _version
    content
    createdAt
    id
    owner
    updatedAt
  }
}

On a third window, using UserPool auth, logged in user, send a mutation:

mutation MyMutation {
  createTodo(input: {content: "From AppSync Console"}) {
    _deleted
    _lastChangedAt
    _version
    content
    createdAt
    id
    owner
    updatedAt
  }
}

Only the third (the mutation response of course) and second window (subscription without owner input specified) receives the mutation event:

{
  "data": {
    "onCreateTodo": {
      "_deleted": null,
      "_lastChangedAt": 1691589812327,
      "_version": 1,
      "content": "From AppSync Console",
      "createdAt": "2023-08-09T14:03:32.298Z",
      "id": "0958e3d3-1539-42bd-818b-5819646992d4",
      "owner": "username",
      "updatedAt": "2023-08-09T14:03:32.298Z"
    }
  }
}

From here, I'm curious how we are establishing the when the user is logged in from the App:

{\"variables\":{\"owner\":\"username\"},\"query\":\"subscription OnCreateTodo($owner: String!) {
  onCreateTodo(owner: $owner) {
    id
    content
    createdAt
    updatedAt
    __typename
    _version
    _deleted
    _lastChangedAt
    owner
  }
}\"}}

The subscription is established with an owner field of value "username". "username" is the username of the logged in user in the app.

When i send a mutaton from the Appsync console with the logged in user (replay of the 3rd window above), i can see the mutation event come through on the app.

Now, I logged in with another user and their mutations are not trigger subscription events. This seems to be expected since the subscription established is for a particular owner, the logged in user themselves.

@lawmicha
Copy link
Contributor

lawmicha commented Aug 9, 2023

Let's take a look at the use case:

type Todo @model @auth(
                  rules: [
                     {allow: owner}, 
                     {allow: public, operations: [read]}, 
                     {allow: private, operations: [read]}
                 ]) {
  id: ID!
  content: String
}

What is the expected behavior?

  1. All users signed in can create, update, delete, and read todos. A signed in user subscribes to Todo mutations with owner input specified, receives only todos created/updated/deleted by the owner specified.
  2. All users not signed in, can read todos (when not passing in owner input in the subscription), and cannot create/update/delete
  3. Signed in user can read all todos, and cannot create/update/delete. A signed in user subscribes to Todo mutations without owner input specified, receives all todos.

There seems to be 2 issues with DataStore. The first one is for a user that is not signed in, the subscriptions that are established is passing in owner input of empty string. This means that the read is specified for an empty owner, so no data is ever received on the API Key read-only use case.

For APIKey auth, we should not be passing in "owner" input.

The second issue is when the user is signed in, it's subscribing with an owner input of the logged in user, filtering down to only data created/updated/deleted by the user themselves (use case 1). Is this the expected behavior? Seems like DataStore should not pass in owner input in the subscription to achieve use case 3.

@lawmicha
Copy link
Contributor

lawmicha commented Aug 9, 2023

Everything seems to be working well with

@auth(
  rules: [
    {allow: owner, operations: [create, update, delete]}, 
    {allow: public, operations: [read]}, 
    {allow: private, operations: [read]}
]) 

Make sure if you update the auth rules, amplify push and re-run amplify codegen models to pick up the schema changes in the model swift files.

Not signed in users, using API Key subscriptions, doesn't have owner field, which receives all mutations:

subscription OnCreateTodo {
  onCreateTodo {
    id
    content
    createdAt
    updatedAt
    __typename
    _version
    _deleted
    _lastChangedAt
    owner
  }
}\"

Signed In user, also using the above operation, and with the Authorization payload to be the token, is receiving all mutations. By not setting read access for owner rule, the priority of auth types used in the DataStore multiauth takes private over public

@dan-codes1
Copy link
Author

@lawmicha Does this only work for datastore category? because I'm using the API category and its still not working. Im getting this error:
GraphQLResponseError<Address>: GraphQL service returned a successful response containing errors: [Amplify.GraphQLError(message: "Not Authorized to access createAddress on type Mutation", locations: Optional([Amplify.GraphQLError.Location(line: 2, column: 3)]), path: Optional([Amplify.JSONValue.string("createAddress")]), extensions: Optional(["errorType": Amplify.JSONValue.string("Unauthorized"), "data": Amplify.JSONValue.null, "errorInfo": Amplify.JSONValue.null]))] Recovery suggestion: The list of GraphQLErrorcontains service-specific messages

Address Schema:
type Address @model @searchable @auth(rules: [{allow: public, operations: [read]}, {allow: private, operations: [read]}, {allow: owner, operations: [create, update, delete]}]) { id: ID!

Default auth = API Key. Secondary auth mode is cognito.

@lawmicha
Copy link
Contributor

lawmicha commented Aug 9, 2023

Hey @dan-codes1, I can see in the response, the mutation failed because it's trying to send the mutation request using the API key. if you're making API calls using a different auth type from the primary auth mode, you'll have to modify the configuration as per #3089 (comment) to use the correct interceptors. The amplifyconfiguration.json currently only generates the primary auth mode, and is expected I believe. I understand you mentioned that the file gets overwriten on pull which is expected since local modifications to the file do not get stored in your amplify project.

Ideally the solution we provide here a way to specify the auth type in code instead of the configuration file, where you can do something like this:

Amplify.API.mutate(request: request, authtype: .cognitoUserPool)

@lawmicha lawmicha self-assigned this Aug 9, 2023
@dan-codes1
Copy link
Author

dan-codes1 commented Aug 9, 2023

@lawmicha thanks it worked. However, though I rarely run amplify pull, this means I'd have to keep updating my amplifyconfiguration.json each time I call amplify pull.

Your suggestion Amplify.API.mutate(request: request, authtype: .cognitoUserPool) makes sense to me.

Hope to see these improvements in future releases.

@lawmicha
Copy link
Contributor

@dan-codes1 please track/thumbs up this #3133 for the feature request of adding auth type. Since the original issue here is solved, please open an new issue if you face any other problems. @tobias-feldmann If you are still experienceing issues, please open a new issue with the details of your use case and schema.

@tobias-feldmann
Copy link

@lawmicha I described my use case with more details in #3136

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api Issues related to the API category auth Issues related to the Auth category question General question requires attention Follow up needed for more than 10 days
Projects
None yet
Development

No branches or pull requests

5 participants