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

Unable to send graphql accessToken to my lambda authorizer #13922

Closed
3 tasks done
domov44 opened this issue Oct 15, 2024 · 4 comments
Closed
3 tasks done

Unable to send graphql accessToken to my lambda authorizer #13922

domov44 opened this issue Oct 15, 2024 · 4 comments
Labels
GraphQL Related to GraphQL API issues

Comments

@domov44
Copy link

domov44 commented Oct 15, 2024

Before opening, please confirm:

JavaScript Framework

React

Amplify APIs

GraphQL API

Amplify Version

v6

Amplify Categories

auth, api

Backend

Amplify CLI

Environment information

# Put output below this line


Describe the bug

I have a dynamodb entity that has a custom auth directive, and I've set this custom auth to an Authorizer lambda

So far so good, my lambda executes well every time I call graphql

But I want to start this lambda with a simple condition that authorizes the Admin group to crud, and another less permissive group to read only (to then go further in the customization).

To do this, I need to send the accessToken in the authtoken in my graphql call, I get the accessToken and send it, but as soon as I send it without even doing anything with the lambda, it rejects me with an error 'Not Authorized to access listDocuments on type Query'.

I tried with the .toString(), and without, nothing to do, I said to myself that maybe the problem came from the character length but no, as soon as I send the token with its .signature (or .randomvalue after the .payload) from the jwt I'm automatically rejected.

Expected behavior

I want to be able to provide my accessToken to my lambda from my graphql calls to authenticate the user.

Reproduction steps

  1. Basic install of amplify and react
  2. Create a backend and an api graphql with entity and custom auth directive
  3. Create an index of the entity data, and try to fetch it with the basic configuration lambda and the basic authToken
  4. Try to send the accessToken in the authToken from the session.tokens.accessToken.toString() of amplify fetchAuthSession()
  5. See the error from the fetch

Code Snippet

// how i get the accessToken (console.log works well i can see him)
try {
        const session = await fetchAuthSession();
        if (session.userSub) {
          const accessToken = session.tokens.accessToken.toString();
          console.log(accessToken)
          const identityId = session.identityId;
          const currentUser = await getCurrentUser();
          const userProfile = await getUserProfile(currentUser.userId);
          const cognitoGroups = session.tokens.idToken.payload['cognito:groups'];
          setCognitoGroups(cognitoGroups && cognitoGroups.length > 0 ? cognitoGroups : null);
          setIsAdmin(cognitoGroups && cognitoGroups.includes('Admin'));

          setUser({
            age: userProfile.birthdate ? calculateAge(userProfile.birthdate) : 0,
            pseudo: currentUser ? currentUser.username : 'User',
            id: currentUser ? currentUser.userId : 0,
            profile: userProfile,
            identityId: identityId,
            accessToken: accessToken,
          });
          setProfile(userProfile);
          setLoggedIn(true);
          fetchProfilePictureURL(userProfile.profile_picture);

          checkProfileCompletion(userProfile);
        }
        
        
        // how i try to fetch all documents with an index (console.log works well to i can see my token)
          async function listAllDocuments() {
    try {
      console.log(user.accessToken)
      const documents = await client.graphql({
        query: listDocuments,
        authMode: 'lambda',
        authToken: user.accessToken,
      });
      return documents.data.listDocuments.items;
    } catch (error) {
      console.error("Erreur lors de la récupération des documents :", error);
    }
  }
  
  // my lambda
  
  /* Amplify Params - DO NOT EDIT
  API_APP_AUDITTABLE_ARN
  API_APP_AUDITTABLE_NAME
  API_APP_CLIENTSTABLE_ARN
  API_APP_CLIENTSTABLE_NAME
  API_APP_DOCUMENTSTABLE_ARN
  API_APP_DOCUMENTSTABLE_NAME
  API_APP_ENTITYDOCUMENTRELATIONTABLE_ARN
  API_APP_ENTITYDOCUMENTRELATIONTABLE_NAME
  API_APP_GRAPHQLAPIENDPOINTOUTPUT
  API_APP_GRAPHQLAPIIDOUTPUT
  API_APP_GRAPHQLAPIKEYOUTPUT
  API_APP_PROFILETABLE_ARN
  API_APP_PROFILETABLE_NAME
  API_APP_TASKSTABLE_ARN
  API_APP_TASKSTABLE_NAME
  AUTH_APPA549703A_USERPOOLID
  ENV
  REGION
Amplify Params - DO NOT EDIT */

const { CognitoIdentityProviderClient, AdminGetUserCommand, AdminListGroupsForUserCommand, GetUserCommand } = require('@aws-sdk/client-cognito-identity-provider');

/**
 * @type {import('@types/aws-lambda').APIGatewayProxyHandler}
 */
exports.handler = async (event) => {
  console.log('EVENT:', JSON.stringify(event));

  const {
    authorizationToken,
    requestContext: { apiId, accountId },
  } = event;

  // Récupérer le type d'opération et le nom du champ
  const operationType = event.requestContext?.operationType;
  const fieldName = event.requestContext?.fieldName;

  const userPoolId = process.env.AUTH_APPA549703A_USERPOOLID;
  const userName = "ronanscotet467@gmail.com";

  // Créer un client Cognito
  const client = new CognitoIdentityProviderClient({ region: process.env.REGION });

  const command2 = new AdminListGroupsForUserCommand({
    UserPoolId: userPoolId,
    Username: userName,
  });

  // const getUserCommand = new GetUserCommand({
  //   AccessToken: authorizationToken,
  // });

  try {
    // const user = await client.send(getUserCommand);
    const group = await client.send(command2);

    console.log('USERPOOL:', userPoolId);
    console.log('USERNAME:', userName);
    console.log('AccessToken:', authorizationToken);
    // console.log('USER ATTRIBUTES:', JSON.stringify(user.UserAttributes));
    console.log('USER GROUPS:', JSON.stringify(group.Groups));

  } catch (error) {
    console.error('Erreur lors de la récupération de l\'utilisateur :', error);
  }

  const response = {
    isAuthorized: !!authorizationToken,
    resolverContext: {
      userid: 'user-id',
      info: 'contextual information A',
      more_info: 'contextual information B',
    },

    // Liste des opérations explicitement refusées
    deniedFields: [
      // 'arn:aws:appsync:${process.env.AWS_REGION}:${accountId}:apis/${apiId}/types/Mutation/fields/updateDocuments',
      // 'arn:aws:appsync:${process.env.AWS_REGION}:${accountId}:apis/${apiId}/types/Mutation/fields/createDocuments',
    ],
    ttlOverride: 300,
  };

  if (operationType === 'Mutation') {
    const operationArn = `arn:aws:appsync:${process.env.AWS_REGION}:${accountId}:apis/${apiId}/types/Mutation/fields/${fieldName}`;

    if (response.deniedFields.includes(operationArn)) {
      console.log(`Operation ${fieldName} explicitly denied`);
      response.isAuthorized = false;
    }
  }

  console.log('response >', JSON.stringify(response, null, 2));
  return response;
};

Log output

Not Authorized to access listDocuments on type Query


aws-exports.js

// WARNING: DO NOT EDIT. This file is automatically generated by AWS Amplify. It will be overwritten.

const awsmobile = {
    "aws_project_region": "eu-west-3",
    "aws_appsync_graphqlEndpoint": "https://nd3f4gnskfanldzmzj4m6ta6lq.appsync-api.eu-west-3.amazonaws.com/graphql",
    "aws_appsync_region": "eu-west-3",
    "aws_appsync_authenticationType": "AMAZON_COGNITO_USER_POOLS",
    "aws_appsync_apiKey": "da2-hmvx7wqh7rbo5eaqf2mbr6ljhy",
    "aws_cognito_identity_pool_id": "eu-west-3:90cad829-23fd-4d94-932b-dc39d33f8bed",
    "aws_cognito_region": "eu-west-3",
    "aws_user_pools_id": "eu-west-3_eFrzQZh3l",
    "aws_user_pools_web_client_id": "6q0pv1llgunngd9kk7nktdqqtl",
    "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_user_files_s3_bucket": "appfiles09b13-ronantest",
    "aws_user_files_s3_bucket_region": "eu-west-3"
};


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

image
image

@github-actions github-actions bot added pending-triage Issue is pending triage pending-maintainer-response Issue is pending a response from the Amplify team. labels Oct 15, 2024
@cwomack cwomack added the GraphQL Related to GraphQL API issues label Oct 15, 2024
@chrisbonifacio
Copy link
Member

chrisbonifacio commented Oct 15, 2024

Hi! 👋 thanks for raising this issue.

I think if you're using the Cognito access token, you should be able to access it from the lambda's event as event.identity

If you console log authorizationToken, what do you see?

The isAuthorized logic in your handler is a Boolean dependent on whether it's not undefined or null.

Have you tried overriding the graphql headers with an Authorization header in the Amplify.configure call or client?

Lastly, can you share the schema just to be sure of the auth rule on the model?

@github-actions github-actions bot removed the pending-maintainer-response Issue is pending a response from the Amplify team. label Oct 15, 2024
@chrisbonifacio chrisbonifacio added pending-community-response Issue is pending a response from the author or community. and removed pending-triage Issue is pending triage labels Oct 15, 2024
@domov44
Copy link
Author

domov44 commented Oct 16, 2024

Thank you for your reply.
Your first proposal with the event.identity seems the most simple, but how to access it? I don't see any identity correpsondance in my event structure.

So I've tried all 3, but none of them worked.

console.log('IDENTITY:', JSON.stringify(event.identity));
const { requestContext: { apiId, accountId, identity } } = event;
const identity = event.identity;

If i try to send my accessToken from client to lambda as the autorizationToken i can't console log nothing, because im blocked by the lambda, but if i put a random string i can console.log the autorizationToken

this is my graphql model/entity :

type Documents @model @auth(rules: [{ allow: custom }]) {
  id: ID!
  path: String
  identityId: String
  name: String!
  description: String
  availableForclientIDs: [Clients] @hasMany
  entityRelation: [EntityDocumentRelation] @hasMany
}

more information :
image
image
image

Thanks.

@github-actions github-actions bot added pending-maintainer-response Issue is pending a response from the Amplify team. and removed pending-community-response Issue is pending a response from the author or community. labels Oct 16, 2024
@chrisbonifacio
Copy link
Member

chrisbonifacio commented Oct 18, 2024

Hi @domov44 thanks for sharing those excerpts from the docs. It seems like you might be running into a known behavior where the access token is probably being rejected by AppSync because it thinks it might be a Cognito access token.

To work around this, you can try adding a random prefix or suffix to the access token in the Authorization header like so:

const documents = await client.graphql({
        query: listDocuments,
        authMode: 'lambda',
        authToken: `random ${user.accessToken}`,
      });

This should allow it to be detected as an OIDC token instead of a Cognito access token and then your lambda's logic can remove the prefix/suffix and validate it as you normally would.

This is the relevant part of that page I'm referring to:

CleanShot 2024-10-18 at 13 42 28@2x

Note that you also can't use Bearer as a prefix as it is a reserved prefix by the service.

Let me know if that helps!

@github-actions github-actions bot removed the pending-maintainer-response Issue is pending a response from the Amplify team. label Oct 18, 2024
@domov44
Copy link
Author

domov44 commented Oct 18, 2024

Hi, yes thanks for your help, with this method i'm able to get the accessToken, now i just need to ajust my lambda to use the token by removing the random string.

@github-actions github-actions bot added the pending-maintainer-response Issue is pending a response from the Amplify team. label Oct 18, 2024
@domov44 domov44 closed this as completed Oct 18, 2024
@github-actions github-actions bot removed the pending-maintainer-response Issue is pending a response from the Amplify team. label Oct 18, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
GraphQL Related to GraphQL API issues
Projects
None yet
Development

No branches or pull requests

3 participants