Apollo-server: [Federation] Feature Request: fire willSendRequest on gateway initialization

Created on 12 Jun 2019  ยท  2Comments  ยท  Source: apollographql/apollo-server

Context

In our microservice architecture, we have Authorization headers to gate access to our services. In this case, our gateway service needs to pass along expected authorization headers to our federated service.

Problem

On initialization of the gateway service, the willSendRequest callback will only fire when a GraphQL request is executed, which makes sense. But there is not configuration option available to send an initial authorization header.

Therefore, we can't use the global context object on the federated service to throw an AuthenticationError.

Source Code

From the gateway app:

const startGateway = async () => {
  const gateway = new ApolloGateway({
    debug: ENV !== 'prod',
    serviceList: [
      { name: 'federated-svc', url: `${FEDERATED_SVC}/graphql` },
    ],
    buildService: ({ name, url }) => {
      return new RemoteGraphQLDataSource({
        url,
        willSendRequest ({ request, context }) {
          // Get JWT payload, resign with same secret, and pass to federated service.
          if (context.user) {
            const jwt = jwtSign(context.user, JWT_SECRET_KEY)
            request.http.headers.set('Authorization', `JWT ${jwt}`)
          } else {
            request.http.headers.set('Authorization', ROUTE_AUTH)
          }
        },
      })
    },
  })
  let server, gatewayConfig
  try {
    gatewayConfig = await gateway.load()
    server = new ApolloServer({
      ...gatewayConfig,
      cache,
      dataSources,
      engine: { apiKey: ENGINE_API_KEY },
      tracing: true,
      context,
    })
    server.applyMiddleware({ app })
  } catch (err) {
    console.error(err)
  }
}

On the federated service, this is ideally how we would configure the server. It's important to note the business logic of the global context object here.

const schema = buildFederatedSchema([{ typeDefs, resolvers }])

const url = new URL(`redis://${REDIS_URL}:${REDIS_PORT}/${REDIS_DATABASE}`).toString()

const cache = new RedisCache({ url })

try {
  const server = new ApolloServer({
    schema,
    cache,
    debug: true,
    dataSources,
    engine: { apiKey: ENGINE_API_KEY },
    tracing: true,
    context ({ req }) {
      if (!(req.user || req.headers.authorization === ROUTE_AUTH)) {
        throw new AuthenticationError('Not authenticated')
      }
      return { user: req.user, req: req }
    },
  })
  server.applyMiddleware({ app })
} catch (err) {
  console.error(err)
  throw err
}

However, if you use this, the gateway service will receive the Not authenticated error thrown by the context object of the federated service.

The intuition is that a request to a service behind our gateway should carry an Authorization header with a known secret or a JWT in a way that each service knows the secret to validate the authenticity of the JWT that's passed. On initialization of the Gateway, there is no user context to pass along, so an expected secret should be supported.

Please let me know if you need more detail to understand this request.

Despite that, got it to work! Loving this federation pattern so far.

๐Ÿ‘ฉโ€๐Ÿš€ federation

Most helpful comment

@calebfaruki we are working on a design that lets you pull the service config with a lot more control! We want to separate willSendRequest from fetching service capabilities. This was a major source of confusion in stitching and we think not doing detch on startup is probably the better long term solution. We will have more details on this very soon!

All 2 comments

@calebfaruki we are working on a design that lets you pull the service config with a lot more control! We want to separate willSendRequest from fetching service capabilities. This was a major source of confusion in stitching and we think not doing detch on startup is probably the better long term solution. We will have more details on this very soon!

Closing via #3120.

Noting that this PR conflicts directly with what @jbaxleyiii said above - I moved forward with that PR, but was missing the context mentioned by James to decide against this. For now, the mentioned PR unlocks a viable solution for this.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

leinue picture leinue  ยท  3Comments

hiucimon picture hiucimon  ยท  3Comments

mathroc picture mathroc  ยท  3Comments

attdona picture attdona  ยท  3Comments

Magneticmagnum picture Magneticmagnum  ยท  3Comments