aws-amplify / amplify-js

A declarative JavaScript library for application development using cloud services.
https://docs.amplify.aws/lib/q/platform/js
Apache License 2.0
9.42k stars 2.12k forks source link

Amplify Angular generated API service mutations not authenticating properly causing 401 errors -- when more than one auth type is configured #5579

Closed dorontal closed 3 years ago

dorontal commented 4 years ago

Describe the bug Getting a 401 error when using a type with @auth { allow: owner } and trying to create a new record for that type. Here is the entire schema that produced this error (the relevant type to look at is type PrivateTrack as the error is caused when calling the generated angular service function CreatePrivateTrack):

type Search
  @model(subscriptions: null)
  @auth(
    rules: [
      { allow: public, provider: iam, operations: [create, update, read] }
    ]
  )
  @key(fields: ["normalizedQuery"])
{
  normalizedQuery: String!
  titles: String!
  nTimes: Float!
  nTimesInLast24h: Int!
  updatedAt: AWSTimestamp!
  titlesUpdatedAt: AWSTimestamp!
}

type PrivateTrack
  @model
  @auth(rules: [{ allow: owner } ])
{    
  id: ID!
  path: String!
}

type PublicTrack
  @model
  @auth(
    rules: [
      { allow: owner },
      { allow: public, provider: iam, operations: [read] }
    ]
  )
{
  id: ID!
  path: String!
}

With this schema, I successfully use the Auth service to create a user, to send a code for account verification, to verify the account and finally, to log a user in (via email + password login). But then, while the user is logged-in, here is the function call of the generated Angular API service that returns a 401 (Unauthorized) error response:

   this.awsAPI.CreatePrivateTrack({ path: 'test-path' }).then((res: any) => {
       console.log('successfully created a private track!', res);
   });

To Reproduce Steps to reproduce the behavior:

  1. Use above schema
  2. amplify add api - use IAM as the default authorization mode, but add a COGNITO authorization mode as the 2nd auth mode (NOTE: I get a different error when reversing this order, but it is also an error that shows that the amplify service cannot handle more than one auth mode)
  3. Make sure, via the Cognito console, that the IAM user pool that was just created has unauthorized access (this is for the public parts of the schema): check the checkbox in Cognito, for the created Identity pool, that allows it to have UnAuthorized access.
  4. Via your client app, create a user, confirm the user account, log-in the user in your app
  5. In your client app, call the CreatePrivateTrack mutation

Expected behavior As soon as the 401 error from CreatePrivateTrack happens, I checked the request headers in the network tab of dev-tools of the browser. I expected the request headers to have the typical authorization endpoint parameters that get passed, but it does not have them. Instead, it seems to send a sigV4 signing, which is not expected. Here are those headers that show up with the request that got the 401 error response:

Request URL: https://cl7elcp5znajhdxfaklaotatn4.appsync-api.us-east-1.amazonaws.com/graphql
Request Method: POST
Status Code: 401 
Remote Address: 99.84.32.33:443
Referrer Policy: no-referrer-when-downgrade
access-control-allow-origin: *
access-control-expose-headers: x-amzn-RequestId,x-amzn-ErrorType,x-amz-user-agent,x-amzn-ErrorMessage,Date,x-amz-schema-version
content-length: 105
content-type: application/json;charset=UTF-8
date: Fri, 24 Apr 2020 20:58:00 GMT
status: 401
via: 1.1 a0845df335efaa79f84feeb1d7861c1a.cloudfront.net (CloudFront)
x-amz-cf-id: s-6MeHen7rw01Qx3zlKqokp4U7Gz7mA7fGdbHvVSS5XkiF1TITSUyw==
x-amz-cf-pop: EWR52-C4
x-amzn-errortype: UnauthorizedException
x-amzn-requestid: 0c762e3e-3aa7-4ed6-9c3d-fb967aaf390c
x-cache: Error from cloudfront
:authority: cl7elcp5znajhdxfaklaotatn4.appsync-api.us-east-1.amazonaws.com
:method: POST
:path: /graphql
:scheme: https
accept: application/json, text/plain, */*
accept-encoding: gzip, deflate, br
accept-language: en-US,en;q=0.9
authorization: AWS4-HMAC-SHA256 Credential=ASIA554YJA7WFKYHME72/20200424/us-east-1/appsync/aws4_request, SignedHeaders=content-type;host;x-amz-date;x-amz-security-token;x-amz-user-agent, Signature=e59dead648fa05b654e029819829fedeaebcee88671d3a83092191aed05ef666
cache-control: no-cache
content-length: 305
content-type: application/json; charset=UTF-8
origin: http://localhost:4200
pragma: no-cache
referer: http://localhost:4200/
sec-fetch-dest: empty
sec-fetch-mode: cors
sec-fetch-site: cross-site
user-agent: Mozilla/5.0 (iPhone; CPU iPhone OS 7_1_2 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Version/7.0 Mobile/11D257 Safari/9537.53
x-amz-date: 20200424T205759Z
x-amz-security-token: IQoJb3JpZ2luX2VjEFUaCXVzLWVhc3QtMSJGMEQCIAZtWShnG18F/nsfnp0ZkB3vkjO0lISCns22/1iEeTjIAiAEKwEP68gip1w9FtPzx0shCHdWoP9cycGu+HNcQIGXHCr+Awh+EAEaDDk1NzU2MDE5NTA1MiIMPkl1bk6lfbnBZU0CKtsDwJgFX4B6gc0vV9CsEeElKhX20Dm9558uUzknzELLrpX5kZwM9aebF7hMEDYPFhGwa7fSgGv6+v94QVZc6Ya7VcwoUVC44ifPuuPCHP4LZ9JOiTXO9tzUGGkWHLdpiZWDu9HGGdTqHSPncyCMDM2s2UzrPZxWoT3YB8MZM/oVcNwYzXWQK8f7PsbQI7CHUvLFtMSMkJPBVb5RtuAvi4Y6bQXqAcKpKiu6prFrGws2qb+jipmNTyIKISE+5u9mEz8JYi/CqX/WuLc0ZtbGawBx/BSjOHB5LjoRUzs9U96CetoVyhXZm7J0MIw4jTnB0qFiarswhjvZhTsbg2fvVCAQKux23F92UUb7BLnM1bw0T+n0FjNwpyTkv7KMOBDpJKba6rLTD6FiDo1dTkrtMXePMIpa6N5m7Rzr3SOMOumJ6nbN2XRolggcmQ0PRa0XKLp5VCRCVaHgyHW2wBzPUVhKDhwrpoI9oDIXNdHtQE1zhLqWf0CH1IzmKRb6fSdBUXqHlKeAxS8yengX3Zi7zsKpLAy310/T8nSofud7lzFR0XfIXscWmRRwDBMWVXqFEguQvwunMh4/j86Do1acrD+w+O8V9OBAgBt21R15UnE7W6/tD96WL82NILAgfjDXpY31BTrMAsM3X/YfJtIteqnVw6Iv3jQ6PuQxQW6TpmZITMqLUwilIbIRRAOIF/gsLtCboXAHR4X6C3CN1uAMQIXU+/AlA3l4ApQEXuRROyxvMhxlgf894E3PiWVLo4AK/34nsKiVvc1j7UAUvR0V/TXhSP7j4xwX3MDUlDco7o26gka3mOsrNMZzY9cuPw15p8QWolPbDvhVokKS1reghKOyod7k4JOScMSc9cre4sWML9p39gKhrd/mvWI9jpRbl/d8egyNBS5NRi/YfFIk2/HS8hkCJ3TVoFxNtDVCEDjes68TDDt96eLwDyAteveDBqRkGgZNVjPe0OgiBx52A5qKymLtV3+/cbbe2U1O+nYepIVlsaXAVXWgvVYYxmDrE/7oc2Cp+YcThESMQx4Ca1v3a3izNNCRJunvc/I4fiRgb2IRz9B32aK/4K8dE051WUtR
x-amz-user-agent: aws-amplify/3.2.3 js
{,…}
query: "mutation CreatePrivateTrack($input: CreatePrivateTrackInput!, $condition: ModelPrivateTrackConditionInput) {↵  createPrivateTrack(input: $input, condition: $condition) {↵    __typename↵    id↵    path↵    owner↵  }↵}↵"
variables: {input: {path: "test-dir/mp_20190927_seg_21_64.mp3"}}

What is Configured?

[ NOTE: Yes, at some point I did try to use use AMAZON_COGNITO_USER_POOLS as the primary (first configured) auth_mode - but then I got a similar-but-reverse error: none of my public (IAM) stuff in the schema was working. Focusing here on just that one use-case of configuring IAM as the primary auth mode and adding COGNITO as the 2nd auth mode, which makes more sense anyway because most of that schema use is public (IAM). ]

(2041) dorontal@thing5: npx envinfo --system --binaries --browsers --npmPackages
npx: installed 1 in 0.983s

  System:
    OS: Linux 4.19 Debian GNU/Linux 10 (buster) 10 (buster)
    CPU: (6) x64 Intel(R) Core(TM) i5-8400 CPU @ 2.80GHz
    Memory: 4.11 GB / 11.56 GB
    Container: Yes
    Shell: 5.0.3 - /bin/bash
  Binaries:
    Node: 12.0.0 - /usr/local/bin/node
    npm: 6.14.4 - /usr/local/bin/npm
  Browsers:
    Chrome: 81.0.4044.122
    Firefox: 68.7.0esr
  npmPackages:
    @angular-devkit/build-angular: ~0.803.20 => 0.803.26 
    @angular/cli: ~8.3.23 => 8.3.26 
    @angular/common: ~8.2.14 => 8.2.14 
    @angular/compiler: ~8.2.14 => 8.2.14 
    @angular/compiler-cli: ~8.2.14 => 8.2.14 
    @angular/core: ~8.2.14 => 8.2.14 
    @angular/forms: ~8.2.14 => 8.2.14 
    @angular/language-service: ~8.2.14 => 8.2.14 
    @angular/platform-browser: ~8.2.14 => 8.2.14 
    @angular/platform-browser-dynamic: ~8.2.14 => 8.2.14 
    @angular/router: ~8.2.14 => 8.2.14 
    @aws-amplify/api: ^3.1.7 => 3.1.7 
    @aws-amplify/pubsub: ^3.0.8 => 3.0.8 
    @ionic-native/core: ^5.0.7 => 5.24.0 
    @ionic-native/splash-screen: ^5.0.0 => 5.24.0 
    @ionic-native/status-bar: ^5.0.0 => 5.24.0 
    @ionic/angular: ^5.0.0 => 5.0.7 
    @ionic/angular-toolkit: ^2.1.1 => 2.2.0 
    @types/jasmine: ^3.5.6 => 3.5.10 
    @types/jasminewd2: ^2.0.8 => 2.0.8 
    @types/node: ^13.7.4 => 13.13.2 
    aws-amplify: ^3.0.8 => 3.0.8 
    aws-amplify-angular: ^5.0.8 => 5.0.8 
    codelyzer: ^5.0.0 => 5.2.2 
    core-js: ^2.5.4 => 2.6.11 
    eslint: ^6.8.0 => 6.8.0 
    jasmine-core: ~3.4.0 => 3.4.0 
    jasmine-spec-reporter: ~4.2.1 => 4.2.1 
    karma: ^4.4.1 => 4.4.1 
    karma-chrome-launcher: ~2.2.0 => 2.2.0 
    karma-coverage-istanbul-reporter: ~2.0.1 => 2.0.6 
    karma-jasmine: ~2.0.1 => 2.0.1 
    karma-jasmine-html-reporter: ^1.4.0 => 1.5.3 
    protractor: ~5.4.0 => 5.4.4 
    rxjs: ~6.5.1 => 6.5.5 
    ts-node: ~7.0.0 => 7.0.1 
    tslib: ^1.9.0 => 1.11.1 
    tslint: ~5.15.0 => 5.15.0 
    typescript: ~3.4.3 => 3.4.5 
    zone.js: ~0.9.1 => 0.9.1 
  npmGlobalPackages:
    @aws-amplify/cli: 4.18.1
    @ionic/cli: 6.6.0
    npm: 6.14.4

Smartphone (please complete the following information):

Not a smartphone - running in Chromium browser on desktop with the following version information Version 80.0.3987.162 (Developer Build) built on Debian 10.3, running on Debian 10.3 (64-bit)

dorontal commented 4 years ago

I was made aware that there is a work-around for this issue, which is to not use the automatically generated Angular API service, but use GraphQL / AppSync directly, and do something like this:

// call the mutation directly via graphql
const createPrivateTrack = await API.graphql({
  ...
});

but this does not solve the issue posted here, which is with the Angular API service, not with AppSync. Also I have not tried this. Maybe it also produces a 401 error? I am adding this comment, because it's a workaround that may help others who have had the same issue, so that they can try to proceed with this proposed temporary solution. Instead, I am interested in using the automatically generated Angular API service, interested in calling the mutation via the provided service, which seems to have a bug in it - the one described above.

Johnniexson commented 4 years ago

@dorontal were you able to fix this?

dorontal commented 4 years ago

@Johnniexson we did not try to fix this because it seems like a serious and deep bug that the experts at Amazon would be able to fix much more quickly than us, also because there is a workaround (using API.graphql directly, see comment above). Instead, we've shifted attention to work on other parts of the project until this is fixed.

Johnniexson commented 4 years ago

@dorontal Okay, but how do u import queries.createTodo in your component?

Because when i run amplify codegen the files created in the graphql folder are mutations.graphql, queries.graphql, subscriptions.graphql and schema.json

From these files there are no exported data that i can import in my components and i can't be writings the statements for each queries from scratch if I'm to use API.graphql directly

Any help on this? You might want to look at this for what i actually meant. https://github.com/aws-amplify/amplify-js/discussions/5805#discussion-3656

dorontal commented 4 years ago

@Johnniexson I do not understand your last question very well. The GraphQL code I post in a comment above which comes from the graphql folder is just the workaround solution, not the one we are after. In this issue, I am not referring to the code that gets created in the graphql folder at all, it is shown only to point other people who have that issue that there is a workaround (we haven't tried the workaround, by the way, so we can't say for sure that it works). Instead, in this issue I am referring to the Angular service code that gets generated in a Typescript code file. By the way, today we upgraded to Angular 9 with the Ivy renderer and all new aws-amplify packages in package.json (that's "@aws-amplify/api": "^3.1.10" and "aws-amplify": "^3.0.11", which are the only two AWS/amplify packages we use -- and the exact same bug is still exactly the same. When you use the mutation from the generated service, the mutation returns a 401 error, even though the user authorized to make that mutation is 100% logged in. That's because the wrong Authorization header token is sent with the request. That's probably related to the fact that there are multiple authorizations set-up here: Cognito and IAM together. Any help on this would be greatly appreciated.

Johnniexson commented 4 years ago

@dorontal what i meant is that i can't import the generated statements for mutation from the graphql folder(the file generated have .graphql file extension and not .ts like the API.service.ts) to my component in other to use API.graphql directly (the part where one will specify in query: mutation.**).

Are u on the amplify discord channel? I will be glad if you can reach me via johnniexson#3220 so i could explain the issue /what i mean better.. 🙏

dorontal commented 4 years ago

Woohoo!! I found a super simple workaround that fixes the issue reported here, even better than using API.graphql (the API.graphql workaround would have required ignoring the auto-generated angular service code and rewriting that service with calls to API.graphql each of which specifies the auth mode - it would have worked but it would have taken numerous new lines of code. The following workaround is just one line of code).

The workaround is based on this issue and comment https://github.com/aws-amplify/amplify-cli/issues/1576#issuecomment-665338424 -- thank you @kwhitejr and @dabit3 !!

To work around this issue, I just added one line of code above the line that caused trouble. Added:

    Amplify.configure({
        aws_appsync_authenticationType: 'AMAZON_COGNITO_USER_POOLS'
    });

before the call to

    this.awsAPI.CreatePrivateTrack({ ...

Done! All works as expected now.

wei commented 3 years ago

I can reproduce. After some investigations:

https://github.com/aws-amplify/amplify-js/blob/8b3183f4d2ec7289044e2b6700e3ff4df3f98ce4/packages/api-graphql/src/GraphQLAPI.ts#L116-L122

Looks like aws_appsync_authenticationType is being used on line 122 here to determine the type of auth headers to set.

@dorontal Since AWS_IAM is used as the default aws_appsync_authenticationType, and PrivateTrack doesn't have iam @auth rules, isn't 401 working as intended?

As you commented, setting aws_appsync_authenticationType to 'AMAZON_COGNITO_USER_POOLS' works as designed.

Adding the iam @auth as follows (as an example):

type PrivateTrack
  @model
  @auth(rules: [
    { allow: owner },
    { allow: private, provider: iam, operations: [create, read, update] }
  ])
{
  id: ID!
  path: String!
}

allows both auth types to call this.awsAPI.CreatePrivateTrack successfully.

mauerbac commented 3 years ago

Thank you @wei ! As Wei mentions, that is the expected behavior. If you have further questions, please open a discussion or join our Discord (discord.gg/amplify) !

github-actions[bot] commented 2 years ago

This issue has been automatically locked since there hasn't been any recent activity after it was closed. Please open a new issue for related bugs.

Looking for a help forum? We recommend joining the Amplify Community Discord server *-help channels or Discussions for those types of questions.