I have an amplify project with a an appsync graphql api set up through amplify-cli. The project has cognito user pools integration. My goal is to have a nodeJS script on an external server perform queries on my graphql api. My question is related to correct way to set up authentication for the schema…
An excerpt of a particular model my schema I would like to access:
type Phase @model @auth(rules: [ {allow: private, provider: iam} {allow: groups, groups: ["companyAdmin"], provider: userPools}, {allow: groups, groups: ["extConnection"], operations: [create, read, update], provider: userPools}, {allow: groups, groupsField: "readGroups", operations: [create, read], provider: userPools}, {allow: groups, groupsField: "editGroups", provider: userPools}]) { id: ID! description: String editGroups: [String] readGroups: [String] }
My confusion stems from reading documentation on how to call the app-sync client endpoint via the nodejs @aws-sdk v3.
Here it seems they are referring to using the connection profiles that have an iam access key id and secrey key: https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-shared.html
If I create an IAM user and provide it the built in appsync access policies:
AWSAppSyncSchemaAuthor ::: AWS managed: Provides access to create, update, and query the schema.
AWSAppSyncPushToCloudWatchLogs ::: AWS managed: Allows AppSync to push logs to user’s CloudWatch account.
and since my model has an auth rule for iam
{allow: private, provider: iam}
I should be able to allow a particular IAM role or user to access these by adding this user to the custom-roles.json on my amplify app configuration per (since the allow private iam line above apparently only allows iam requests from auto generated scoped down rules): https://docs.amplify.aws/cli/graphql/authorization-rules/#use-iam-authorization-within-the-appsync-console ….
However, this is not ideal because I have many other models in the schema that require IAM interaction from internal lambda functions for example, but now this IAM user would have access to any model that has allow:private provider: iam so therefore question…
- Can I have granular rules for the @auth directive IAM provider that limits access to a model only for a particular IAM user?
OR
- Or should I instead use a cognito user from one of my user pools, that has the group “extConnection” which would satisfy the auth rule on this model:
{allow: groups, groups: [“extConnection”], operations: [create, read, update], provider: userPools},
But then how would I build the the request for this via something like axios npm package to make the query? Will I need to create a REST API Gateway to handle this as a middleman between the external server and the graphql endpoint?
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
I ended up finding a solution by authenticating via cognito user pools.
- The tables that needed to be accessed by this user, I had to limited by a user group, in this case “extConnection”. So I added this group to the user account. I then used the guide written by this individual here to reach my solution: https://www.fullsapps.com/2019/02/calling-aws-appsync-or-any-graphql-api_14.html .
- I then used the npm packages ‘amazon-cognito-identity-js’ and ‘graphql-request’ to build the cognito auth handshake headers and return the access token.
- I used the access token to make a gql request, and that returned the needed data.
Sample code:
(async () => { /* these will need to be updated for each environment */ const USER_NAME = 'yourusername'; const PASS_WORD = 'yourpassword'; const USER_POOL_ID = 'cognitopoolid'; const CLIENT_ID = 'cognitopoolclientid'; const ENDPOINT = 'appsyncgraphqlendpointurl'; /* ************************************************************************ */ function authUser() { // authenticates with cognito to receive the access tokens. return new Promise((resolve, reject) => { const authenticationData = { Username: USER_NAME, Password: PASS_WORD }; const authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(authenticationData); const poolData = { UserPoolId: USER_POOL_ID, ClientId: CLIENT_ID } const userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData); const userData = { Username: authenticationData.Username, Pool: userPool } const cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData); cognitoUser.authenticateUser(authenticationDetails, { onSuccess: function (result) { const idToken = result.getIdToken().getJwtToken(); const accessToken = result.getAccessToken().getJwtToken(); const refreshToken = result.getRefreshToken().getToken(); // idToken serves our purposes for now resolve(idToken); }, onFailure: function (err) { reject(err.message); } }); }); } async function makeQuery(gqlc) { const query = `mutation MyMutation { createPayout(input: { status: "Pending" } ) { id status _version } }`; return gqlc.request(query); } const token = await authUser(); /* preferably cache the token somewhere and reuse in the future until it expires, then re-auth */ console.log(token); const gqlClient = new GraphQLClient(ENDPOINT, { headers: { Authorization: token } }); const res = await makeQuery(gqlClient); console.log(JSON.stringify(res)); } )();
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0