amplify-codegen icon indicating copy to clipboard operation
amplify-codegen copied to clipboard

Groups authorization not working as expected for GraphQL transformer v2 - Not Authorized

Open malcomm opened this issue 3 years ago • 25 comments

Before opening, please confirm:

  • [X] I have installed the latest version of the Amplify CLI (see above), and confirmed that the issue still persists.
  • [X] I have searched for duplicate or closed issues.
  • [X] I have read the guide for submitting bug reports.
  • [X] I have done my best to include a minimal, self-contained set of instructions for consistently reproducing the issue.

How did you install the Amplify CLI?

npm

If applicable, what version of Node.js are you using?

v16.13.2

Amplify CLI Version

7.6.12

What operating system are you using?

macos

Did you make any manual changes to the cloud resources managed by Amplify? Please describe the changes made.

no

Amplify Categories

auth, api

Amplify Commands

Not applicable

Describe the bug

Getting:

Not Authorized to access listFoo on type Query

When performing a list on the model

this.apiService.ListFoo();

Expected behavior

Group authentication should work as documented

Reproduction steps

  1. auth rule for groups
  2. cognito user with admin
  3. try to access model data restricting to admin group

GraphQL schema(s)

# Put schemas below this line
type Foo
  @model
  @auth(rules: [
    { allow: groups, groups: ["admin"]},
    { allow: groups, groupsField: "groupsCanAccess", operations: [read] }
  ])
{
  name: String!
  groupsCanAccess: [String]
}

Log output

# Put your logs below this line
error: Error: Uncaught (in promise): Object: {"data":{"listFoos":null},"errors":[{"path":["listFoos"],"data":null,"errorType":"Unauthorized","errorInfo":null,"locations":[{"line":2,"column":3,"sourceName":null}],"message":"Not Authorized to access listFoos on type Query"}]}
    at qe (polyfills.js:1:150135)
    at polyfills.js:1:149161
    at c (main.js:1:251763)
    at S.invoke (polyfills.js:1:140059)
    at Object.onInvoke (main.js:1:147119)
    at S.invoke (polyfills.js:1:139998)
    at S.run (polyfills.js:1:135092)
    at polyfills.js:1:150973
    at S.invokeTask (polyfills.js:1:140745)
    at Object.onInvokeTask (main.js:1:146929)

Additional information

No response

malcomm avatar Jan 25 '22 06:01 malcomm

@malcomm - can you give more specifics with how you set up your auth and user groups? Also, how are you making your mutations/create requests? This feature works as expected, so I think you are setting up auth and/or creating records with the incorrect access. Thank you.

danielleadams avatar Jan 25 '22 21:01 danielleadams

@danielleadams - note: this project was previously a transformer v1 implementation.

My setup:

  • Cognito User
  • Cognito group: admin
  • Add user to the admin group
  • Just trying to list out all the items gives me this error, I'm calling this code:

this.apiService.ListFoo();

malcomm avatar Jan 26 '22 00:01 malcomm

@danielleadams - I even deleted my environment and started over from a brand new environment. Still not working. I've tried multiple models to query and no matter what I do the groups auth is not work.

malcomm avatar Jan 26 '22 05:01 malcomm

@malcomm What client library are using and is this a mocked or deployed server?

danielleadams avatar Jan 26 '22 19:01 danielleadams

@danielleadams - I'm using Angular on the frontend and calling the generated APIService for the graphql/DynamoDB backend. I have deployed servers that I'm hitting via the standard amplify push. Here's my amplify status:

> amplify status

    Current Environment: devthree
    
┌───────────┬─────────────────────────┬───────────┬───────────────────┐
│ Category  │ Resource name           │ Operation │ Provider plugin   │
├───────────┼─────────────────────────┼───────────┼───────────────────┤
│ Auth      │ foof6ac14a6.            │ No Change │ awscloudformation │
├───────────┼─────────────────────────┼───────────┼───────────────────┤
│ Api       │ fooapi                  │ No Change │ awscloudformation │
├───────────┼─────────────────────────┼───────────┼───────────────────┤
│ Analytics │ fooui                   │ No Change │ awscloudformation │
└───────────┴─────────────────────────┴───────────┴───────────────────┘

Foo is not the name on the resources, just omitted those to keep it generic for the ticket. I'm happy to supply the specifics of my environments via other channels.

Also, here's a very simple model that I'm experiencing this error:

type IssueComment
  @model
  @auth(rules: [
    { allow: groups, groups: ["admin", "practitioner"] }
  ])
  # @key(name: "byIssue", fields: ["issueID"])
{
  id: ID!
  issueID: ID! @index(name: "byIssue")
  createdAt: AWSDateTime
  updatedAt: AWSDateTime
  author: String
  comment: String
}

malcomm avatar Jan 26 '22 19:01 malcomm

@danielleadams - also of note: it looks like the permissions are setup correctly from the API perspective:

> amplify status api -acm IssueComment

userPools:staticGroup:admin
  ┌───────────┬────────┬──────┬────────┬────────┐
  │  (index)  │ create │ read │ update │ delete │
  ├───────────┼────────┼──────┼────────┼────────┤
  │    id     │  true  │ true │  true  │  true  │
  │  issueID  │  true  │ true │  true  │  true  │
  │ createdAt │  true  │ true │  true  │  true  │
  │ updatedAt │  true  │ true │  true  │  true  │
  │  author   │  true  │ true │  true  │  true  │
  │  comment  │  true  │ true │  true  │  true  │
  └───────────┴────────┴──────┴────────┴────────┘
userPools:staticGroup:practitioner
  ┌───────────┬────────┬──────┬────────┬────────┐
  │  (index)  │ create │ read │ update │ delete │
  ├───────────┼────────┼──────┼────────┼────────┤
  │    id     │  true  │ true │  true  │  true  │
  │  issueID  │  true  │ true │  true  │  true  │
  │ createdAt │  true  │ true │  true  │  true  │
  │ updatedAt │  true  │ true │  true  │  true  │
  │  author   │  true  │ true │  true  │  true  │
  │  comment  │  true  │ true │  true  │  true  │
  └───────────┴────────┴──────┴────────┴────────┘

malcomm avatar Jan 28 '22 19:01 malcomm

Hey @malcomm - I've been doing some testing on my end, and it appears the ACM is correct. I'm not able to reproduce your issue with the auth setup you've shared. Do you mind running the queries in AppSync to see if we can isolate the issue to either the front end or the backend?

danielleadams avatar Jan 28 '22 22:01 danielleadams

@danielleadams - I was able to run the query from AppSync when logged into Cognito.

I'm looking through my application now to make sure everything is configured correctly.

malcomm avatar Jan 28 '22 23:01 malcomm

@danielleadams - I looked at my config and setup and it's all pretty standard. The one thing I forgot to mention:

    "aws-amplify": "^4.3.13",

I'm also not using the @aws-amplify/ui-angular package. Instead, I'm handling authentication using my own authentication service. But in the end, it does the same thing:

import { Auth } from 'aws-amplify';

...

  async login(username: string, password: string): Promise<CognitoUser> {
    const user = await Auth.signIn(username, password);
    this.currentUser = user;
    return user;
  }

This was all working great under Transformer v1. Unless I'm doing something just silly, this seems like there's a bug?

malcomm avatar Jan 29 '22 04:01 malcomm

@malcomm I'm going to transfer this to amplify-js repo so you can chat with that team. Unfortunately, I don't have much knowledge on the library.

danielleadams avatar Jan 31 '22 17:01 danielleadams

Hi @malcomm 👋 a couple things to check:

  1. Add this line to your project, where you are calling Amplify.configure and share the logs from the console
Amplify.Logger.LOG_LEVEL = 'DEBUG';
  1. Are you certain the user you are authenticating and making the call with is assigned to one of the user groups specified in the auth rule?

  2. Do you see a difference in behavior/responses between the client and AppSync console?

chrisbonifacio avatar Feb 01 '22 15:02 chrisbonifacio

@chrisbonifacio

  1. Added the line for debug and I'm seeing the debug output. Here are the lines right before the error:
[DEBUG] 05:57.7 Credentials - credentials not changed and not expired, directly return
ConsoleLogger.js:127 [DEBUG] 05:57.8 AuthClass - getting current authenticated user
ConsoleLogger.js:127 [DEBUG] 05:57.8 AuthClass - get current authenticated userpool user
ConsoleLogger.js:127 [DEBUG] 05:57.8 Credentials - removing aws-amplify-federatedInfo from storage
  1. Yes, the user has the admin group in Cognito
  2. Yes. My angular client app is throwing the Not Authorized to access listIssueComments on type Query. AppSync console is not.

malcomm avatar Feb 01 '22 16:02 malcomm

Can you share what the code for the query looks like as well as the network request/response? Want to check if Amplify is including an auth token in the request.

Otherwise, what are the default and additional authentication types for your AppSync API?

chrisbonifacio avatar Feb 01 '22 19:02 chrisbonifacio

@chrisbonifacio - as for auth types, I'm not sure what you mean by that exactly. I can say this: we are using only Cognito and I was able to login as the same user in AppSync and issue a query. The same query via client/angular is throwing the error.

As for the code that's causing this:

console.log('comments: ', this.apiService.ListIssueComments());

Which is the generated code for graphql:

  async ListIssueComments(
    filter?: ModelIssueCommentFilterInput,
    limit?: number,
    nextToken?: string
  ): Promise<ListIssueCommentsQuery> {
    const statement = `query ListIssueComments($filter: ModelIssueCommentFilterInput, $limit: Int, $nextToken: String) {
        listIssueComments(filter: $filter, limit: $limit, nextToken: $nextToken) {
          __typename
          items {
            __typename
            id
            issueID
            createdAt
            updatedAt
            author
            comment
          }
          nextToken
        }
      }`;
    const gqlAPIServiceArguments: any = {};
    if (filter) {
      gqlAPIServiceArguments.filter = filter;
    }
    if (limit) {
      gqlAPIServiceArguments.limit = limit;
    }
    if (nextToken) {
      gqlAPIServiceArguments.nextToken = nextToken;
    }
    const response = (await API.graphql(
      graphqlOperation(statement, gqlAPIServiceArguments)
    )) as any;
    return <ListIssueCommentsQuery>response.data.listIssueComments;
  }

malcomm avatar Feb 01 '22 20:02 malcomm

For your appsync authentication types, you can check either the aws-exports.js or the backend-config.json files generated by the CLI.

For example, this is my backend-config for my AppSync resource:

"authConfig": {
  "defaultAuthentication": {
    "authenticationType": "API_KEY",
    "apiKeyConfig": {
      "apiKeyExpirationDays": 7
    }
  },
  "additionalAuthenticationProviders": [
    {
      "authenticationType": "AMAZON_COGNITO_USER_POOLS",
      "userPoolConfig": {
        "userPoolId": "authissue9535f0ef735e"
      }
    }
  ]
}

But, if your default auth type is Cognito User Pools, then you should not be getting an Unauthorized error.

Can you try adjusting your API.graphql call to include an authMode of "AMAZON_COGNITO_USER_POOLS" to make sure there is a Cognito token being set in the authorization header?

example:

  const response = await API.graphql({
    ...graphqlOperation(statement, gqlAPIServiceArguments),
    authMode: "AMAZON_COGNITO_USER_POOLS",
  });

chrisbonifacio avatar Feb 01 '22 20:02 chrisbonifacio

@chrisbonifacio - I could not make that change because the generated code doesn't have that as an option (at least not the way you've got it - causes compiler errors). I was trying to make it work, but didn't have time. Feels like it's not the correct way of passing authMode.

malcomm avatar Feb 01 '22 21:02 malcomm

@chrisbonifacio - just to be sure, I don't have a path to move forward here.

malcomm avatar Feb 02 '22 18:02 malcomm

It works in my angular app, had to change it in the API.service.ts file. But, I think you're right, even if you got it to work, this isn't an ideal way to deal with this. The file would be overwritten every time you push and you'd have to adjust the authMode for each mutation function. The auto-generated functions don't accept an argument for you to set the authMode so the only way around it (that I'm aware of) would be to change your codegen configuration to generate typescript files instead of graphql files (from choosing angular) and running the mutations similar to this:

// app.component.ts

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { API } from 'aws-amplify';
import { IssueComment } from './API.service';
import { createIssueComment } from 'src/graphql/mutations';
import { listIssueComments } from 'src/graphql/queries';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnInit {
  title = 'amplify-angular-app';
  public createForm: FormGroup;

  /* declare IssueComments variable */
  public IssueComments: Array<IssueComment> = [];

  constructor(private fb: FormBuilder) {
    this.createForm = this.fb.group({
      author: ['', Validators.required],
      comment: ['', Validators.required],
      issueID: ['1234', Validators.required],
    });
  }

  async ngOnInit() {
    /* fetch IssueComments when app loads */
    const event = (await API.graphql({
      query: listIssueComments,
      authMode: 'AMAZON_COGNITO_USER_POOLS',
    })) as any;

    this.IssueComments = event.data.listIssueComments.items as IssueComment[];
  }

  public async onCreate(IssueComment: IssueComment) {
    try {
      await API.graphql({
        query: createIssueComment,
        variables: {
          input: {
            ...IssueComment,
          },
        },
        authMode: 'AMAZON_COGNITO_USER_POOLS',
      });
      console.log('item created!');
      this.createForm.reset();
    } catch (e) {
      console.log('error creating IssueComment...', e);
    }
  }
}

chrisbonifacio avatar Feb 02 '22 22:02 chrisbonifacio

@chrisbonifacio - right, this is not how things are supposed to work, so we are agreed. This is a bug.

malcomm avatar Feb 03 '22 01:02 malcomm

I don't think it's a bug because the codegen is working as intended but it should be updated to add an argument for setting the authMode. I'm going to transfer this over to the CLI repo since this is more of a codegen issue.

chrisbonifacio avatar Feb 03 '22 15:02 chrisbonifacio

Hi @malcomm, can we try one more thing to see if this solves the problem. In the aws-exports.js file, what is the value of the aws_appsync_authenticationType field? I was able to reproduce the same 401 error, and that was caused by aws_appsync_authenticationType being set to other auth types than AMAZON_COGNITO_USER_POOLS. Once I changed the value to AMAZON_COGNITO_USER_POOLS, it starts to work for me. Can you try and see if it works for you?

Doc reference for aws_appsync_authenticationType: https://docs.amplify.aws/lib/graphqlapi/create-or-re-use-existing-backend/q/platform/js/#re-use-existing-appsync-graphql-api

yeung-wah avatar Apr 07 '22 20:04 yeung-wah

I was facing a similar situation locally when groups wasn't working when I was using the amplify mock command. For what it's worth, I did this:

npm i -g @aws-amplify/cli  // This performed an upgrade from 7.6.23 → 8.0.3  
npm ci  // deleted and re-installed my node_modules

groups started working again with amplify mock

aokoli avatar Apr 27 '22 14:04 aokoli

I also met this problem, after I added "authMode: 'AMAZON_COGNITO_USER_POOLS'", it can query successfully.

Mavlarn avatar Jun 24 '22 03:06 Mavlarn

As per today I experienced this bug using latest Amplify cli v10.8.1 with React.js v18 I solved it by modifying the aws-export.js file like this post mentioned: https://github.com/aws-amplify/amplify-codegen/issues/387#issuecomment-1092177790 but every time I run amplify push api, the bug comes back

In the AppSync console settings, i tried switching the AMAZON_COGNITO_USER_POOLS as Default authorization mode and put the other method I was using as default in the Additional authorization providers sections. But that did not fix it. Still need to change aws-export.js after each push

Changing the default auth mode with the cli works!

nomadev21 avatar Mar 03 '23 15:03 nomadev21

It works in my angular app, had to change it in the API.service.ts file. But, I think you're right, even if you got it to work, this isn't an ideal way to deal with this. The file would be overwritten every time you push and you'd have to adjust the authMode for each mutation function. The auto-generated functions don't accept an argument for you to set the authMode so the only way around it (that I'm aware of) would be to change your codegen configuration to generate typescript files instead of graphql files (from choosing angular) and running the mutations similar to this:

// app.component.ts

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { API } from 'aws-amplify';
import { IssueComment } from './API.service';
import { createIssueComment } from 'src/graphql/mutations';
import { listIssueComments } from 'src/graphql/queries';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnInit {
  title = 'amplify-angular-app';
  public createForm: FormGroup;

  /* declare IssueComments variable */
  public IssueComments: Array<IssueComment> = [];

  constructor(private fb: FormBuilder) {
    this.createForm = this.fb.group({
      author: ['', Validators.required],
      comment: ['', Validators.required],
      issueID: ['1234', Validators.required],
    });
  }

  async ngOnInit() {
    /* fetch IssueComments when app loads */
    const event = (await API.graphql({
      query: listIssueComments,
      authMode: 'AMAZON_COGNITO_USER_POOLS',
    })) as any;

    this.IssueComments = event.data.listIssueComments.items as IssueComment[];
  }

  public async onCreate(IssueComment: IssueComment) {
    try {
      await API.graphql({
        query: createIssueComment,
        variables: {
          input: {
            ...IssueComment,
          },
        },
        authMode: 'AMAZON_COGNITO_USER_POOLS',
      });
      console.log('item created!');
      this.createForm.reset();
    } catch (e) {
      console.log('error creating IssueComment...', e);
    }
  }
}

Thanks for the clarification. This is correct because aws amplify is deny first by default, so you have to call cognito pool for the authorization, then you can perform group specific CRUD stuff. It is safe though because they cannot change their cognito status because its deny first

DomGarza avatar Sep 01 '24 12:09 DomGarza