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

Unauthorized errors: Cognito user pool as generic OIDC provider #13016

Closed mikejw closed 6 months ago

mikejw commented 7 months ago

Before opening, please confirm:

JavaScript Framework

React

Amplify APIs

Authentication, GraphQL API, DataStore

Amplify Version

v6

Amplify Categories

auth, api

Backend

Amplify CLI

Environment information

``` # Put output below this line System: OS: macOS 14.2.1 CPU: (8) arm64 Apple M1 Memory: 53.77 MB / 8.00 GB Shell: 5.9 - /bin/zsh Binaries: Node: 20.4.0 - ~/.local/share/nvm/v20.4.0/bin/node npm: 9.7.2 - ~/.local/share/nvm/v20.4.0/bin/npm Browsers: Brave Browser: 114.1.52.130 Chrome: 121.0.6167.160 Safari: 17.2.1 npmPackages: @aws-amplify/ui-react: ^6.1.0 => 6.1.0 @aws-amplify/ui-react-internal: undefined () @capacitor/app: 5.0.6 => 5.0.6 @capacitor/cli: 5.6.0 => 5.6.0 @capacitor/core: 5.6.0 => 5.6.0 @capacitor/haptics: 5.0.6 => 5.0.6 @capacitor/keyboard: 5.0.7 => 5.0.7 @capacitor/status-bar: 5.0.6 => 5.0.6 @cypress/angular: 0.0.0-development @cypress/mount-utils: 0.0.0-development @cypress/react: 0.0.0-development @cypress/react18: 0.0.0-development @cypress/svelte: 0.0.0-development @cypress/vue: 0.0.0-development @cypress/vue2: 0.0.0-development @ionic-enterprise/auth: ^5.1.3 => 5.1.3 @ionic-enterprise/identity-vault: ^5.12.2 => 5.12.2 @ionic/react: ^7.0.0 => 7.6.4 @ionic/react-router: ^7.0.0 => 7.6.4 @testing-library/dom: >=7.21.4 => 9.3.4 @testing-library/jest-dom: ^5.16.5 => 5.17.0 @testing-library/react: ^14.0.0 => 14.1.2 @testing-library/user-event: ^14.4.3 => 14.5.2 @types/react: ^18.0.27 => 18.2.47 @types/react-dom: ^18.0.10 => 18.2.18 @types/react-router: ^5.1.20 => 5.1.20 @types/react-router-dom: ^5.3.3 => 5.3.3 @vitejs/plugin-legacy: ^5.0.0 => 5.2.0 @vitejs/plugin-react: ^4.0.1 => 4.2.1 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 () bin: 1.0.0 cypress: ^13.5.0 => 13.6.2 dist: 1.0.0 eslint: ^8.35.0 => 8.56.0 eslint-plugin-react: ^7.32.2 => 7.33.2 ionicons: ^7.0.0 => 7.2.2 ionicons-loader: undefined () ionicons/components: undefined () ionicons/icons: 7.2.2 jsdom: ^22.1.0 => 22.1.0 react: ^18.2.0 => 18.2.0 react-dom: ^18.2.0 => 18.2.0 react-router: ^5.3.4 => 5.3.4 react-router-dom: ^5.3.4 => 5.3.4 terser: ^5.4.0 => 5.26.0 typescript: ^5.1.6 => 5.3.3 vite: ^5.0.0 => 5.0.11 vitest: ^0.34.6 => 0.34.6 npmGlobalPackages: @aws-amplify/cli: 12.10.1 corepack: 0.19.0 ionic: 5.4.16 npm: 9.7.2 prettier: 3.0.0 ```

Describe the bug

I have followed the docs here for creating an OIDC owner auth rule against my model: https://docs.amplify.aws/react/build-a-backend/more-features/datastore/authz-rules-setup/#owner-based-authorization-with-oidc-provider. We are using Cognito (user pool) as a generic OIDC provider and I have entered relevant details by running "amplify api update".

The full schema is here:

type User
  @model
  @auth(rules: [
      { allow: owner, provider: oidc, identityClaim: "sub" }
    ]) {
    username: String!
    favourites: [Int]
    owner: String
}

I have ruled out there being any issues in how data is created/read/updated by also trying this auth rule in addition to the owner one:

{ allow: private, operations: [create, read, update] }

When this rule is in place everything works fine except that any user can potentially gain access to another user's data.

To highlight that issue I'm having is to do with the owner (oidc) auth rule I have been advised by someone on the Amplify discord to try running queries in ApSync with the appropriate JWT token and this does not result in any 401 errors. However any query I try to run results in unauthorized errors.

e.g.

query MyQuery {
  getUser(id: "ce6710aa-f9e2-46a7-bf7b-03b8780b1568") {
    id
    username
  }
}

Returns:

{
  "data": {
    "getUser": null
  },
  "errors": [
    {
      "path": [
        "getUser"
      ],
      "data": null,
      "errorType": "Unauthorized",
      "errorInfo": null,
      "locations": [
        {
          "line": 2,
          "column": 3,
          "sourceName": null
        }
      ],
      "message": "Not Authorized to access getUser on type Query"
    }
  ]
}

So what am I missing from my schema to make queries, mutations and subscriptions work?

I have also tried using "private" instead of "owner" like so:

{ allow: private, provider: oidc, identityClaim: "sub", operations: [create, read, update] }

Thanks

Expected behavior

AppSync does not produce unauthorized errors as if I had used the private auth rule with all operations granted.

Reproduction steps

Code Snippet

// Put your code below this line.

Log output

``` // Put your logs below this line ```

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": "eu-west-1",
    "aws_cognito_identity_pool_id": "eu-west-1:03223797-8f4a-4667-bc1f-b77a463b3c19",
    "aws_cognito_region": "eu-west-1",
    "aws_user_pools_id": "eu-west-1_X6oCcM1Bw",
    "aws_user_pools_web_client_id": "49t856q7emfn8av69883e0cuo4",
    "oauth": {},
    "aws_cognito_username_attributes": [
        "EMAIL"
    ],
    "aws_cognito_social_providers": [],
    "aws_cognito_signup_attributes": [
        "EMAIL"
    ],
    "aws_cognito_mfa_configuration": "OFF",
    "aws_cognito_mfa_types": [
        "SMS"
    ],
    "aws_cognito_password_protection_settings": {
        "passwordPolicyMinLength": 8,
        "passwordPolicyCharacters": []
    },
    "aws_cognito_verification_mechanisms": [
        "EMAIL"
    ],
    "aws_appsync_graphqlEndpoint": "https://tq74h7r43zaxdk2q5al4nczopu.appsync-api.eu-west-1.amazonaws.com/graphql",
    "aws_appsync_region": "eu-west-1",
    "aws_appsync_authenticationType": "OPENID_CONNECT"
};

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

mikejw commented 7 months ago

By the way we are using a custom token provider as outlined here (even though the token comes from Cognito): https://docs.amplify.aws/javascript/build-a-backend/auth/advanced-workflows/#custom-token-providers

cwomack commented 6 months ago

@mikejw, can you try to update your auth rules to be the following and see if there is any change in behavior:

type User
  @model
  @auth(rules: [
      { allow: owner, provider: oidc, identityClaim: "sub::username" }
    ]) {
    username: String!
    favourites: [Int]
    owner: String
}
mikejw commented 6 months ago

I'm still getting this kind of response from the graphql endpoint. NB: This is for path syncUsers, createUser and updateUser.

{
    "data": {
        "syncUsers": null
    },
    "errors": [
        {
            "path": [
                "syncUsers"
            ],
            "data": null,
            "errorType": "Unauthorized",
            "errorInfo": null,
            "locations": [
                {
                    "line": 2,
                    "column": 3,
                    "sourceName": null
                }
            ],
            "message": "Not Authorized to access syncUsers on type Query"
        }
    ]
}

BTW, this is whether or not a explicitly define the "owner" field or not in the schema.

chrisbonifacio commented 6 months ago

@mikejw sorry, the owner field is inconsequential. The important bit of the change we suggested was the format of the identity claim.

Did you change it to this in the auth rule?

identityClaim: "sub::username"

Oh, you did mention that the same token before the change worked in the AppSync console though... so that's strange.

Can you check the network activity in the browser and see if the syncUser request has an Authorization header with the token you'd expect?

mikejw commented 6 months ago

Hey @chrisbonifacio, yep I tried using that new identityClaim value and it didn't seem to help.

Yep, I'm using a custom token provider to make sure the authorization can happen. In AppSync I don't see any 401 unless I modify the token value with arbitrary text.

chrisbonifacio commented 6 months ago

Hmm, I'm having a hard time reproducing this myself. Can you check the owner field of a record that was created using the token and identityClaim? How is it formatted? The value of owner just has to match the value of the claim on the token.

Amplify GraphQL API's @auth directive and owner rule supports Cognito out of the box. Is there a reason why you have to use Cognito as an OIDC provider instead of a user pool?

mikejw commented 6 months ago

Hey @chrisbonifacio I'm trying to look for the data stored in DynamoDB but it shows as empty for some reason. I'm pretty sure I have seen data stored in the owner field as just the user ids that should correspond to user::sub. e.g. '562d4a8f-0a58-45fb-a873-6014759b6745'

We want to use Ionic Auth Connect and Vault components to secure the token on device with biometrics, therefore we need to be able to handle generated tokens manually.

cwomack commented 6 months ago

@mikejw, can you provide some examples of your implementation of Amplify.configure() and the token provider possibly? I think we may need some sample code to make sure we're not missing something. It would also be helpful if we could see the structure of the token payload.

mikejw commented 6 months ago

Hi @cwomack thanks but another developer on our team got it working in the end with the following auth rule:

@auth(rules: [{ allow: owner }])

I apologise as it seems I wrongly presumed that I needed to use the oidc provider due to authenticating with Cognito outside of using the Amplify authentication components.

We're all good now.