Cognito user identity is undefined in context when making GraphQL requests
haruki-m opened this issue · comments
I'm trying to protect my GraphQL endpoints with a Cognito user pool, but when I make requests from the frontend using AWS Amplify, the identity
field in the context is undefined (and in turn cognitoIdentityId
is undefined), making it impossible/difficult to identify the user who made the request. This setup used to work in the past and now it doesn't. Any help would be appreciated!
Relevant dependency versions:
- node: 20.2.0
- sst: 2.40.3
- constructs: 10.3.0
- aws-cdk-lib: 2.124.0
- @pothos/core: ^3.38.0
- aws-amplify / @aws-amplify: ^6.0.12
sst.config.ts
:
import type { SSTConfig } from 'sst';
import { Api } from './stacks/ApiStack';
import { Auth } from './stacks/AuthStack';
import { Site } from './stacks/SiteStack';
export default {
config(_input) {
return { name: 'sample', region: 'us-east-1' };
},
stacks(app) {
app.stack(Auth).stack(Api).stack(Site);
},
} satisfies SSTConfig;
stacks/AuthStack.ts
:
import { Cognito, type StackContext } from 'sst/constructs';
export function Auth({ stack, app }: StackContext) {
const auth = new Cognito(stack, 'auth', { login: ['email'] });
return { auth };
}
stacks/ApiStack.ts
:
import { Api as ApiGateway, use, type StackContext } from 'sst/constructs';
import { Auth } from './AuthStack';
export function Api({ stack }: StackContext) {
const { auth } = use(Auth);
const api = new ApiGateway(stack, 'api', {
authorizers: {
cognito: {
type: 'user_pool',
userPool: { id: auth.userPoolId, clientIds: [auth.userPoolClientId] },
},
},
routes: {
'POST /graphql': {
type: 'graphql',
authorizer: 'cognito',
function: { handler: 'packages/functions/src/graphql/graphql.handler' },
pothos: {
schema: 'packages/functions/src/graphql/schema.ts',
output: 'packages/graphql/schema.graphql',
commands: [
'cd packages/graphql && npx @genql/cli --output ./genql --schema ./schema.graphql --esm',
],
},
},
},
});
auth.attachPermissionsForAuthUsers(stack, [api]);
return { api };
}
stacks/SiteStack.ts
import { StackContext, use, type StaticSite } from 'sst/constructs';
import { Api } from './ApiStack';
import { Auth } from './AuthStack';
export function Site({ stack, app }: StackContext) {
const { api } = use(Api);
const { auth } = use(Auth);
const site = new StaticSite(stack, 'site', {
path: 'packages/site',
buildCommand: 'pnpm run build',
buildOutput: 'dist',
environment: {
VITE_API_URL: api.url,
VITE_USER_POOL_ID: auth.userPoolId,
VITE_USER_POOL_CLIENT_ID: auth.userPoolClientId,
},
});
return { site };
}
packages/functions/src/graphql/graphql.ts
:
import { GraphQLHandler } from 'sst/node/graphql';
import { schema } from './schema';
export const handler = GraphQLHandler({
schema,
context: async (serverContext) => {
const cognitoId = serverContext.context.identity?.cognitoIdentityId;
console.log(serverContext); // serverContext.context.identity is undefined here
return { cognitoId };
},
});
packages/functions/src/graphql/schema.ts
:
import SchemaBuilder from '@pothos/core';
const builder = new SchemaBuilder<{ Context: { cognitoId?: string } }>({});
builder.queryType({ description: 'Queries' })
builder.queryFields((t) => ({
sampleQuery: t.field({
type: 'String',
resolve: async (obj, args, context) => 'sampleQuery',
}),
}));
export const schema = builder.toSchema({})
packages/site/src/main.tsx
:
import ReactDOM from 'react-dom/client';
import { Amplify } from 'aws-amplify';
import { generateClient } from "aws-amplify/api";
import { signIn } from 'aws-amplify/auth';
Amplify.configure({
Auth: {
Cognito: {
userPoolId: import.meta.env.VITE_USER_POOL_ID,
userPoolClientId: import.meta.env.VITE_USER_POOL_CLIENT_ID,
},
},
API: {
GraphQL: {
endpoint: `${import.meta.env.VITE_API_URL}/graphql`,
defaultAuthMode: 'userPool',
},
},
});
const client = generateClient();
const App = () => {
const onClick = async () => {
await signIn({ username: 'sample@email.com', password: 'password' });
const response = await client.graphql({ query: 'query { sampleQuery }' });
console.log(response);
}
return <button onClick={onClick}>call</button>;
};
ReactDOM.createRoot(document.getElementById('root')!).render(<App />);
Clicking on the button will sign in and make a request. Here's the serverContext
output on the sst dev
logs:
{
event: { ... },
context: {
awsRequestId: 'some request id',
invokedFunctionArn: 'some arn',
getRemainingTimeInMillis: [Function: getRemainingTimeInMillis],
identity: undefined, // this field is supposed to have { cognitoIdentityId: string }
clientContext: undefined,
functionName: 'some function name',
functionVersion: '$LATEST',
memoryLimitInMB: undefined,
logGroupName: 'some log group name',
logStreamName: 'some log stream name',
callbackWaitsForEmptyEventLoop: true,
done: [Function: done],
fail: [Function: fail],
succeed: [Function: succeed]
},
waitUntil: [Function: waitUntil],
request: { ... },
}
I found the article that I based my code off of: here
Ok, never mind. After re-reading the article, the thing that I was looking for was actually stored in serverContext.event.requestContext.authorizer.jwt.claims.sub