AWS CDK: AppSync GraphQL API with Cognito and Apollo Client in React

January 19th, 2025 878 Words

The Amazon Cognito User Pool with Managed Login is a great baseline to start a new project. This guide adds an AWS AppSync GraphQL Data API to the project and shows you how to use the Amazon Cognito Access Token to authenticate against the GraphQL API using the Apollo Client in React.

The Amazon Cognito UI in the AWS Management Console has everything you need to get started; this guide uses React with TypeScript.

Cognito Client Configuration

Based on the outputs of your cdk deploy command, you can configure React and the react-oidc-context package with the needed references to the AWS resources.

import { AuthProvider } from "react-oidc-context";

const cognitoAuthConfig = {
  authority: "https://cognito-idp.eu-central-1.amazonaws.com/eu-cent…",
  client_id: "6n1hf59di3lb65pr3…",
  redirect_uri: "http://localhost:3000",
  response_type: "code",
  scope: "aws.cognito.signin.user.admin email openid phone profile",
};

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <AuthProvider {...cognitoAuthConfig}>
      <App />
    </AuthProvider>
  </React.StrictMode>
);

Together with the react-oidc-context package, this creates everything you need. Within your <App /> component, you can use the useAuth() hook (Introduction to React Hooks) to get the current user - and most importantly - the Access Token:

import { useAuth } from "react-oidc-context";

export default function App() {
  const auth = useAuth();

  if (auth.isAuthenticated) {
    return (
      <div>
        <pre> Access Token: {auth.user?.access_token} </pre>
      </div>
    );
  } else {
    return (
      <div>
        <button onClick={() => auth.signinRedirect()}>Sign in</button>
      </div>
    );
  }
}

With the baseline, we can now send authenticated requests to AWS AppSync. Relevant for this guide is the AWS AppSync GraphQL Data API.

The recently introduced AWS AppSync Events API is an alternative to the AWS AppSync GraphQL Subscription API and is covered in the second part of this guide.

Configure AWS AppSync with Amazon Cognito

Using the Amazon Cognito User Pool created in the previous guide, you can configure the AWS AppSync API to use it for authentication. As mentioned before, AWS AppSync supports two types of API: GraphQL and Events.

AppSync GraphQL Data API

To have an example GraphQL API, create a Session type with the current user’s username and ID as fields. The GraphQL Schema looks like this:

type Session @model {
  sub: String!
  username: String!
}

type Query {
  session: Session
}

schema {
  query: Query
}

Instead of creating the AppSync GraphQL API with a manually created schema file, use the awscdk-appsync-utils package to create the schema with code (more about schema-first GraphQL APIs). First, start with an empty schema and the AppSync GraphQL API:

import * as aws_appsync from "aws-cdk-lib/aws-appsync";
import * as aws_logs from "aws-cdk-lib/aws-logs";
import { CodeFirstSchema } from "awscdk-appsync-utils";

const schema = new CodeFirstSchema();

const api = new aws_appsync.GraphqlApi(stack, "Api", {
  name: `serverless-graphql-api`,
  schema,
  authorizationConfig: {
    defaultAuthorization: {
      authorizationType: aws_appsync.AuthorizationType.USER_POOL,
      userPoolConfig: {
        userPool: users,
      },
    },
    additionalAuthorizationModes: [
      {
        authorizationType: aws_appsync.AuthorizationType.IAM,
      },
    ],
  },
  logConfig: {
    fieldLogLevel: aws_appsync.FieldLogLevel.ALL,
    retention: aws_logs.RetentionDays.ONE_WEEK,
    excludeVerboseContent: false,
  },
  introspectionConfig: aws_appsync.IntrospectionConfig.ENABLED,
  xrayEnabled: true,
});

With an empty schema definition, the deployment of this will fail. Next, create a Session type with the basic properties username and sub we need; both are strings and required in the response.

const typeSession = new ObjectType("Session", {
  definition: {
    sub: GraphqlType.string({ isRequired: true }),
    username: GraphqlType.string({ isRequired: true }),
  },
});

Next, define the AppSync Resolver for the session field.

const fieldSession = new ResolvableField({
  returnType: typeSession.attribute(),
  dataSource: api.addNoneDataSource("SessionDataSource"),
  runtime: aws_appsync.FunctionRuntime.JS_1_0_0,
  code: aws_appsync.Code.fromInline(`
    export function request(ctx) {
      return {}
    }

    export function response(ctx) {
      return ctx.identity
    }
  `),
});

Using serverless AWS AppSync JavaScript Resolvers this will return the current user’s Cognito session object. As a last step, you need to add the Session type to the schema and the session field to the Query type:

schema.addQuery("session", fieldSession);
schema.addType(typeSession);

With this, you can now deploy the GraphQL API and use it with React. I prefer to use Apollo Client for React when working with GraphQL in the browser.

Apollo Client for AppSync GraphQL

Thanks to the react-oidc-context package, you can easily retrieve the Access Token from the useAuth() hook and use it to create a new Apollo Client object:

export default function App() {
  const auth = useAuth();

  const link = ApolloLink.from([
    setContext(async (_, { headers }) => {
      return {
        headers: {
          ...headers,
          authorization: `Bearer ${auth.user!.access_token}`,
        },
      };
    }),
    createHttpLink({
      uri: "https://<api-id>.appsync-api.eu-central-1.amazonaws.com/graphql",
    }),
  ]);

  const client = new ApolloClient({ link, cache: new InMemoryCache() });

  if (auth.isAuthenticated) {
    return (
      <ApolloProvider client={client}>
        <div>Authenticated</div>
      </ApolloProvider>
    );
  } else {
    return <div>Not authenticated</div>;
  }
}

That’s all you need to have the Apollo GraphQL Client working with AWS AppSync GraphQL Data API using Amazon Cognito for authentication; using TypeScript. Next, you can create a React component to query for the user’s session.

interface SessionData {
  session: {
    sub: string;
    username: string;
  };
}

export const Session: React.FC = () => {
  const auth = useAuth();
  const { loading, error, data } = useQuery<SessionData>(gql`
    query session {
      session {
        sub
        username
      }
    }
  `);

  if (loading) return <Text type="secondary">Loading...</Text>;
  if (error) return <Text type="danger">Error: {error.message}</Text>;

  return <div>{data?.session.sub}</div>;
};

Thank’s to the useAuth() and useQuery() hooks, this is all it takes to get data from the AWS AppSync GraphQL Data API via React in TypeScript.

What’s next?

Recently, AWS AppSync introduced the AWS AppSync Events API as an alternative to the AWS AppSync GraphQL Subscription API. The GraphQL Data API in this guide is for fetching data; the Events API allows you to publish data to clients using WebSockets.