Search code examples
node.jsgraphqlapolloapollo-clientapollo-server

How can I pass header parameter for ando client application in apollo server?


I am having trouble while trying to use custom header parameter in apollo server. I have an apollo server as below:

import { ApolloServer } from 'apollo-server-lambda'
import { ApolloGateway, IntrospectAndCompose, GraphQLDataSourceProcessOptions, RemoteGraphQLDataSource } from '@apollo/gateway'
import { ApolloServerPluginLandingPageGraphQLPlayground } from 'apollo-server-core'
import { GraphQLRequest } from 'apollo-server-types'
import { SignatureV4 } from '@aws-sdk/signature-v4'
import { Sha256 } from '@aws-crypto/sha256-js'
import { OutgoingHttpHeader } from 'http'
import { defaultProvider } from '@aws-sdk/credential-provider-node'
import { HttpRequest } from '@aws-sdk/protocol-http'

class AuthenticatedDataSource extends RemoteGraphQLDataSource {
  /**
   * Adds the necessary IAM Authorization headers for AppSync requests
   * @param request The request to Authorize
   * @returns The headers to pass through to the request
   */
  private async getAWSCustomHeaders(request: GraphQLRequest): Promise<{
    [key: string]: OutgoingHttpHeader | undefined
  }> {
    const { http, ...requestWithoutHttp } = request

    if (!http) return {}

    const url = new URL(http.url)

    //check local env
    if(url.host.match(/localhost:20002/)) return {'x-api-key':'da2-fakeApiId123456'}

    // If the graph service is not AppSync, we should not sign these request.
    if (!url.host.match(/appsync-api/)) return {}

    const httpRequest = new HttpRequest({
      hostname: url.hostname,
      path: url.pathname,
      method: http.method,
      headers: {
        Host: url.host,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(requestWithoutHttp),
    })

    const signedRequest = await new SignatureV4({
      region: 'eu-west-1',
      credentials: defaultProvider(),
      service: 'appsync',
      sha256: Sha256,
    }).sign(httpRequest)

    return signedRequest.headers || {}
  }

  /**
   * Customize the request to AppSync
   * @param options The options to send with the request
   */
  public async willSendRequest({ request, context }: GraphQLDataSourceProcessOptions) {
    const customHeaders = await this.getAWSCustomHeaders(request)

    if (customHeaders) {
      Object.keys(customHeaders).forEach((h) => {
        request.http?.headers.set(h, customHeaders[h] as string)
      })
    }

    // context not available when introspecting
    if (context.event) 
      Object.keys(context.event.requestContext.authorizer.lambda).forEach((h) => {
        request.http?.headers.set(h, context.event.requestContext.authorizer.lambda[h] as string)
      })
  }
}

const server = new ApolloServer({
  gateway: new ApolloGateway({
    buildService({ url }) {
      return new AuthenticatedDataSource({ url })
    },
    supergraphSdl: new IntrospectAndCompose({
      subgraphs: [
        { name: 'CONFIGURATIONSERVICE', url: process.env.CONFIGURATION_SERVICE_API_URL }
      ]
    })
  }),
  debug: true,
  context: ({ event, context, express}) => ({
    headers: event.headers,
    functionName: context.functionName,
    event,
    context,
    expressRequest: express.req,
  }),
  introspection: true,
  plugins: [ApolloServerPluginLandingPageGraphQLPlayground()],
})

exports.handler = server.createHandler({
  expressGetMiddlewareOptions: {
    cors: {
      origin: '*',
    }
  }
})

When I try to execute configurationservice via playground on port 3000, I realized that I do not x-api-key header parameter and therefore I get 401 authorization. I do not understand the reason of missing header parameter that I already added in the codebase and any help would be appreciated. Thank you!


Solution

  • Ok I noticed that I can also add header parameters via playground application. Now it returns no authorization error.