WHAT YOU’LL LEARN
  • what are the initial steps we need to perform, before implementing authentication and authorization checks in our GraphQL API
  • how to perform all of the initial steps
Can I use this?

In order to follow this tutorial, you must use Webiny version 5.18.0 or greater.

The code that we cover in this section can also be found in our GitHub examples repository . Also, if you’d like to see the complete and final code of the application we’re building, check out the full-example folder.

Overview
anchor

With the Hosted UI authentication flow successfully integrated into our React application, we’re now finally ready to start implementing authentication and authorization checks in our GraphQL API. But, before we do that, we need to perform a couple of initial setup steps.

First of all, we’ll add two new libraries that will make it a bit easier to work with the current user within our GraphQL API code (resolver functions). Once that’s in place, we will perform the following:

  • perform a minor update to our cloud infrastructure code
  • set up the added libraries (plugins)
  • perform a minor but important update to the Context interface
  • add new createdBy field to our main Pin GraphQL type

Let’s see how we can achieve all of these.

1. Adding the Needed Libraries
anchor

In order to implement the necessary authentication and authorization checks within our GraphQL API, like in the previous section, let’s again start by adding a couple of new libraries into the mix.

Placeholder
anchor

@webiny/api-authentication
anchor

A collection of plugins that will set up a thin authentication layer in our GraphQL API handler function.

@webiny/api-authentication-cognito
anchor

By itself, the above library doesn’t do much. It’s simply a framework, which is always paired with an authentication provider. In our case, we want to pair it up with the Amazon Cognito authentication, which is what this library does.

The following command will add both libraries to our React application:

yarn workspace pinterest-clone-api add @webiny/api-authentication @webiny/api-authentication-cognito

2. Updating Cloud Infrastructure Code
anchor

In order to start setting up the newly added libraries, we’ll first need to update our cloud infrastructure code.

More specifically, we’ll need to pass two new environment variables to our GraphQL API handler function: COGNITO_REGION and COGNITO_USER_POOL_ID.

pinterest-clone/api/pulumi/dev/index.ts
import * as pulumi from "@pulumi/pulumi";import DynamoDB from "./dynamoDb";import Graphql from "./graphql";import ApiGateway from "./apiGateway";import Cloudfront from "./cloudfront";import Cognito from "./cognito";import S3 from "./s3";
// Among other things, this determines the amount of information we reveal on runtime errors.// https://www.webiny.com/docs/how-to-guides/environment-variables/#debug-environment-variableconst DEBUG = String(process.env.DEBUG);
// Enables logs forwarding.// https://www.webiny.com../../core-development-concepts/basics/watch-command#enabling-logs-forwardingconst WEBINY_LOGS_FORWARD_URL = String(process.env.WEBINY_LOGS_FORWARD_URL);
export default () => {  const cognito = new Cognito();  const dynamoDb = new DynamoDB();  const s3 = new S3();
  const api = new Graphql({      dbTable: dynamoDb.table,      cognitoUserPool: cognito.userPool,      s3Bucket: s3.bucket,      env: {          // The single DynamoDB table in which data can be stored and queried.          DB_TABLE: dynamoDb.table.name,          COGNITO_REGION: String(process.env.AWS_REGION),          COGNITO_USER_POOL_ID: cognito.userPool.id,          DEBUG,          WEBINY_LOGS_FORWARD_URL      }  });
  (...)};

As we’ll soon see, in our application code, these will be passed to our GraphQL API handler function, upon configuring the relevant security plugins.

In order for the new environment variables to be available in our GraphQL API handler function, we’ll need to redeploy it. If you had a watch session already up and running, this should’ve happened automatically. Otherwise, you’ll need to either start a new session via the webiny watch, or simply deploy the change via the webiny deploy command.

3. Setting Up the Libraries (Plugins)
anchor

Essentially, both @webiny/api-authentication and @webiny/api-authentication-cognito packages are exporting a collection of plugins, which are meant to be registered within our GraphQL API handler function:

pinterest-clone/api/code/graphql/src/index.ts
import { createHandler } from "@webiny/handler-aws";import graphqlPlugins from "@webiny/handler-graphql";import logsPlugins from "@webiny/handler-logs";import authenticationPlugins from "@webiny/api-authentication";import { authenticateUsingHttpHeader } from "@webiny/api-authentication/authenticateUsingHttpHeader";import cognitoAuthenticationPlugins from "@webiny/api-authentication-cognito";
// Imports plugins created via scaffolding utilities.import scaffoldsPlugins from "./plugins/scaffolds";
const debug = process.env.DEBUG === "true";
export const handler = createHandler({  plugins: [      /**       * Setup authentication plugins.       */      authenticationPlugins(),      authenticateUsingHttpHeader(),
      /**       * Setup Amazon Cognito authentication plugins.       */      cognitoAuthenticationPlugins({          region: process.env.COGNITO_REGION,          userPoolId: process.env.COGNITO_USER_POOL_ID,          identityType: "user"      }),
      logsPlugins(),      graphqlPlugins({ debug }),      scaffoldsPlugins()  ],  http: { debug }});

As we can see, via the authenticationPlugins and authenticateUsingHttpHeader function calls, we’re first registering the plugins that set up the authentication layer. No additional configuration parameters need to be passed here.

Then, via the cognitoAuthenticationPlugins function call, we’re registering another set of plugins that will essentially enable us to perform authentication against Amazon Cognito and the User Pool we’ve defined in a previous section.

Configuration Parameters
anchor

Except assigning the newly added environment variables to region and userPoolId properties, in the cognitoAuthenticationPlugins function call, we’re also assigning "user" to identityType

identityType is a simple string that can, down the road, tell us what type of an identity we’re working with and which authentication provider managed to authenticate it. For us, this is not that important, as we only have a single type of a user and only a single @webiny/api-authentication-cognito authentication provider. But, since the identityType is a required property, we had to set it to something, so we’ve simply set it to "user".

4. Updating Context Interface
anchor

The following step is not required, but it’s certainly recommended because we’ll benefit from improved type-safety and better developer experience.

If we were to open the pinterest-clone/api/code/graphql/src/types.ts file, in it, we’d find the Context interface, which we can utilize any time we’re actually interacting with the handler’s central context object. In our case, this would be within our GraphQL resolver functions, but it could also happen in different ContextPlugin , GraphQLSchemaPlugin , and other plugins.

So, since the newly added libraries (sets of plugins) are actually modifying the context object by adding new properties to it, let’s make sure those changes are reflected in the Context interface as well.

pinterest-clone/api/code/graphql/src/types.ts {5,18}
import { HandlerContext } from "@webiny/handler/types";import { HttpContext } from "@webiny/handler-http/types";import { ArgsContext } from "@webiny/handler-args/types";import { ClientContext } from "@webiny/handler-client/types";import { AuthenticationContext } from "@webiny/api-authentication/types";
// When working with the `context` object (for example while defining a new GraphQL resolver function),// you can import this interface and assign it to it. This will give you full autocomplete functionality// and type safety. The easiest way to import it would be via the following import statement:// import { Context } from "~/types";// Feel free to extend it with additional context interfaces, if needed. Also, please do not change the// name of the interface, as existing scaffolding utilities may rely on it during the scaffolding process.export interface Context  extends HandlerContext,      HttpContext,      ArgsContext,      ClientContext,       AuthenticationContext {}

As we can see, at the top of the file, we’ve simply imported the AuthenticationContext interface, and then, below, added it to our Context interface. Which means that, from now on, every time we interact with the context object, we’ll be able to safely access all of the additional properties that the newly added security-related plugins defined.

5. Adding createdBy Field
anchor

With all of the above steps correctly performed, we should be able to retrieve the signed in user and perform essential authentication and authorization checks within our GraphQL resolver functions. Which means that, upon creating a new pin (via the createPin(data: PinCreateInput!): Pin! mutation), we’ll also be able to tie the signed in user to it.

In order to do this, we’ll need to expand the existing Pin entity, and the PinEntity type with the new createdBy field, which will be responsible for storing signed in user information. As we’ll be able to see, the field will accept an object that consists of three properties: id, displayName, and type.

Apart from that, we’ll also want to perform the same update on the GraphQL schema side, so that this information can be retrieved from our React application.

So, let’s start by updating the Pin entity and PinEntity type first:

pinterest-clone/api/code/graphql/src/plugins/scaffolds/pins/entities/Pin.ts
// https://github.com/jeremydaly/dynamodb-toolboximport { Entity } from "dynamodb-toolbox";import table from "./table";import { PinEntity } from "../types";
/** * Once we have the table, we define the PinEntity entity. * If needed, additional entities can be defined using the same approach. */export default new Entity<PinEntity>({  table,  name: "Pin",  timestamps: false,  attributes: {      PK: { partitionKey: true },      SK: { sortKey: true },      id: { type: "string" },      title: { type: "string" },      description: { type: "string" },      coverImage: { type: "string" },      createdOn: { type: "string" },      savedOn: { type: "string" },
      // For the `Pin` entity, we can use a simple "map" type as the attribute type.      createdBy: { type: "map" },
      // Will store current version of Webiny, for example "5.9.1".      // Might be useful in the future or while performing upgrades.      webinyVersion: { type: "string" }  }});
pinterest-clone/api/code/graphql/src/plugins/scaffolds/pins/types.ts
import { Identity } from "@webiny/api-authentication/types";
export interface PinEntity {  PK: string;  SK: string;  id: string;  title: string;  description?: string;  coverImage?: string;  createdOn: string;  savedOn: string;
  // For the `PinEntity` type, let's be specific and specify which fields are accepted.  createdBy: Pick<Identity, "id" | "type" | "displayName">;
  webinyVersion: string;}
In Case You Forgot

In the GraphQL API section of this tutorial, we’ve used the Extend GraphQL API scaffold to extend our GraphQL API and introduce essential CRUD GraphQL queries and mutations.

By default, the application code relies on DynamoDB Toolbox , which is a library that makes it a bit easier to interact with the Amazon DynamoDB database. The Entity class we’ve seen in the above code sample is part of it.

Once that’s in place, all that is left to do is update our GraphQL schema:

  1. define the new PinCreatedBy type
  2. extend the existing Pin type with the the new createdBy: PinCreatedBy field
pinterest-clone/api/code/graphql/src/plugins/scaffolds/pins/typeDefs.ts
export default /* GraphQL */ `  type Pin {      id: ID!      title: String!      description: String      coverImage: String      createdOn: DateTime!      savedOn: DateTime!      createdBy: PinCreatedBy  }
  type PinCreatedBy {      id: String      type: String      displayName: String  }
  (...)`;

Final Result
anchor

With all of the above-shown changes in place, the initial setup is complete, which means we’re finally ready to start implementing authentication and authorization checks in our GraphQL resolver function.

Note that the setup is only performed once. After that, the majority of security-related tasks will revolve around implementing proper authentication and authorization checks.