GraphQL

GraphQL uses resolvers instead of controllers. The decorators are slightly different, but the concept is the same.

Installation

pnpm add @nestjs-cognito/graphql

Authentication

import { GqlAuthentication, GqlCognitoUser } from '@nestjs-cognito/graphql';
import type { CognitoJwtPayload } from '@nestjs-cognito/core';

@Resolver()
export class UserResolver {
  @Query()
  @GqlAuthentication()
  async me(@GqlCognitoUser() user: CognitoJwtPayload) {
    return {
      id: user.sub,
      username: user.username,
      email: user.email
    };
  }
}

Authorization

import { GqlAuthorization } from '@nestjs-cognito/graphql';

@Resolver()
export class AdminResolver {
  @Mutation()
  @GqlAuthorization(['admin'])
  async adminOperation() {
    return { success: true };
  }
}

Public Routes

import { GqlPublicRoute } from '@nestjs-cognito/graphql';

@Resolver()
@GqlAuthentication()
export class ProductResolver {
  @Query()
  @GqlPublicRoute()
  async products() {
    return [{ id: 1, name: 'Product 1' }];
  }
}

Access and ID tokens

import { GqlCognitoIdUser, GqlCognitoAccessUser } from '@nestjs-cognito/graphql';
import type { CognitoIdTokenPayload, CognitoAccessTokenPayload } from '@nestjs-cognito/core';

@Resolver()
export class UserResolver {
  @Query()
  @GqlAuthentication()
  async profile(@GqlCognitoIdUser() user: CognitoIdTokenPayload) {
    return {
      email: user.email,
      groups: user['cognito:groups']
    };
  }

  @Query()
  @GqlAuthentication()
  async tokenInfo(@GqlCognitoAccessUser() token: CognitoAccessTokenPayload) {
    return {
      scope: token.scope,
      clientId: token.client_id
    };
  }
}

Subscriptions

Subscriptions use WebSockets. You need to verify the token during connection:

ClientWebSocketJWT VerifierSubscriptionConnect + Bearer TokenVerify TokenValid PayloadConnection EstablishedSubscribe to EventsStream DataClientWebSocketJWT VerifierSubscription
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import { GraphQLModule } from '@nestjs/graphql';
import { CognitoJwtVerifier } from 'aws-jwt-verify';

@Module({
  imports: [
    GraphQLModule.forRootAsync<ApolloDriverConfig>({
      driver: ApolloDriver,
      useFactory: (cognitoVerifier: CognitoJwtVerifier) => ({
        autoSchemaFile: true,
        subscriptions: {
          'graphql-ws': {
            onConnect: async (context) => {
              const token = context.connectionParams?.authorization?.replace('Bearer ', '');
              if (!token) throw new Error('Missing token');

              const payload = await cognitoVerifier.verify(token);
              return { user: payload };
            },
          },
        },
      }),
      inject: [CognitoJwtVerifier],
    }),
  ],
})
export class AppModule {}

Protect subscriptions:

@Resolver()
export class NotificationResolver {
  @Subscription()
  @GqlAuthentication()
  notificationAdded(@GqlCognitoUser() user: CognitoJwtPayload) {
    return pubSub.asyncIterator(`notifications.${user.sub}`);
  }
}

Client side:

import { createClient } from 'graphql-ws';

const wsClient = createClient({
  url: 'ws://localhost:3000/graphql',
  connectionParams: {
    authorization: `Bearer ${accessToken}`,
  },
});