Hi everyone :wave: . I'm running into a circular d...
# help
g
Hi everyone 👋 . I'm running into a circular dependency issue, I'm trying to grant unauthenticated access to an sst.AppSyncApi resource by referencing the unauth role created by sst.Auth. For example:
Copy code
const auth = new sst.Auth(this, 'Auth');
const api = new sst.AppSyncApi(this, 'Api', {
  cdk: {
    graphqlApi: {
      authorizationConfig: {
        defaultAuthorization: {
          authorizationType: appsync.AuthorizationType.IAM
        },
        additionalAuthorizationModes: [{
          authorizationType: appsync.AuthorizationType.USER_POOL,
          userPoolConfig: {
            auth.cdk.userPool,
            defaultAction: appsync.UserPoolDefaultAction.DENY,
          },
        }]
      },
    },
  },
});
api.cdk.graphqlApi.grantQuery(auth.cdk.unauthRole, 'nameOfMyQuery');
The error I'm getting is:
Copy code
Error: 'dev-my-app-auth' depends on 'dev-my-app-api' (dev-my-app-auth -> dev-my-app-api/GraphqlApi/Api/Resource.ApiId). Adding this dependency (dev-my-app-api -> dev-my-app-auth/Auth/UserPool/Resource.Ref) would create a cyclic reference.
Is there any other way to set this up? What I'm basically trying to do is to set up the AppSync API to have both authenticated access via user pool and a single query to have unauthenticated access.
d
This happens a lot with the CDK, lots of ways to solve it. Since you are talking about Auth, id recommend splitting that into its own Stack. Cognito is a bear, and you dont want to want to delete it due to wanting to reconfigure your API. Once you have, you can save various attributes from
Auth
to
ssm
, and use them how you see fit in the API stack. This is a more complex pattern to be sure, but likely one worth learning, as splitting into smaller stacks saves many headaches.
g
I actually thought it was an issue that could be solved by splitting it in to stacks but that didn't work unfortunately. I had Auth and AppSyncApi on their own stacks and then have a third stack solely for the grantQuery call. I'm wondering if instantiating the unauth role and assigning it manually to the SST Auth is the right call here although looking at the source code I don't think it'll inject itself properly with the rest of the configuration that's done behind the scenes. At the end of the day I'd have to go with creating my own cognito auth instance and not rely on the one from SST for this, unless there's another option to be considered 😅
d
I don't think I've ever come across a circular dependency using SSM, it's basically a decoupling machine...surprised you had that occur...
g
ohh wait I misread the part about SSM, so I can use that to say store references of ARNs and such? Basically the thing to be 'shared' here is the unauth role created by SST as part of the setup of the Auth construct. The circular dependency is due to the fact that the AppSyncApi construct is taking the userPool generated by the Auth construct too, if I take that out then the grantQuery call actually passes. I was also wondering if adding the additional authorization mode can happen outside of the AppSyncApi constructor too 🤔
d
Yes, that's the idea. You can then re-create them on the other side by getting the arn and using
.from
methods. I think for roles it might be
fromRoleArn
You can do the same with userPool.
g
I'll give that a try thanks!!
f
@Giorgio just checking in if u got it to work.
g
@Frank my bad for not following up! It did work in the end, except that it is a bit cumbersome since I had to deploy the auth stack first so it could write the value to SSM, and then run the api stack in the next
sst start
run.
in
AuthStack.ts
it calls:
Copy code
const unauthRoleARNParamName = `/${app.name}/${app.stage}/unauthRoleARN`;
  new ssm.StringParameter(stack, 'unauthRoleARN', {
    parameterName: unauthRoleARNParamName,
    stringValue: auth.cdk.unauthRole.roleArn,
  });
  
  return {
    unauthRoleARNParamName,
  };
and in
ApiStack.ts
it will fetch it by:
Copy code
const { unauthRoleARNParamName } = use(AuthStack);
  const unauthRoleARN = ssm.StringParameter.valueFromLookup(stack, unauthRoleARNParamName);
  const unauthRole = iam.Role.fromRoleArn(stack, 'unauthRole', unauthRoleARN);
  api.cdk.graphqlApi.grantQuery(unauthRole, 'myQuery');
I'm curious if this could be simplified further in a future version of SST, since granting permissions for unauth queries and mutations might be a common use case