AEB-labs / graphql-weaver

A tool to combine, link and transform GraphQL schemas
MIT License
240 stars 20 forks source link

Question: how to forward the origin extra http header ? #6

Closed gengjiawen closed 7 years ago

gengjiawen commented 7 years ago

I need to pass the token to the server in my situation.

Yogu commented 7 years ago

Good question; we faced this use case, too.

You can pass a custom GraphQLClient instance for endpoints to completely customize how data is fetched. In your situation, I'd suggest to extend HttpGraphQLClient:

class AuthForwardingGraphQLClient extends HttpGraphQLClient {
    protected async getHeaders(document: DocumentNode, variables?: { [name: string]: any }, context?: any, introspect?: boolean): Promise<{ [index: string]: string }> {
        const headers = await super.getHeaders(document, context, introspect);
        return {
            ...headers,
            Authentication: getAuthTokenFromGraphQLContext(context)
        };
    }
}

const schema: GraphQLSchema = await weaveSchemas({
    endpoints: [{
        namespace: 'library',
        client: new AuthForwardingGraphQLClient('http://example.com/graphql')
    }]
});

Then, you somehow need to implement getAuthTokenFromGraphQLContext. How this is done depends on your GraphQL server implementation. If you use express-graphql, the express Request object is passed as context, by default. Then, something like this would do the job:

function getAuthTokenFromGraphQLContext(context: any) {
    if (!context) {
        return undefined;
    }
    return context.header('Authentication');
}

There is one pitfall: When you call weaveSchemas, the endpoints are already called with an introspection query. At this time, there is no context. If you need an authentication token for introspection queries, you need to provide it thorugh a different manner, e.g. by configuration. Use the introspect argument to getHeaders to distinguish those initial introspection queries from normal queries by users.

gengjiawen commented 7 years ago

I followed your advice , but I still can't make it work, this is the demo code https://github.com/gengjiawen/schema-stitching-demo/tree/feature/pass_header, can you take a look when you have free time ? Thanks.

Yogu commented 7 years ago

Sorry, I confused Authentication and Authorization. Also, express seems to convert the header names to lower case for some reason. I tested it locally and it at least dumped the correct token, though I did not test it with a real auth token. See this pull request in your project for the changes.

I only now realized you wrote Origin. Do you mean the HTTP Origin header? I assumed you meant authorization because you wrote token. If you want to forward the origin, just replace the header name.

gengjiawen commented 7 years ago

thanks, I will give it a try tomorrow.

gengjiawen commented 7 years ago

It works like as expected now, thanks. But why when i first running, I got log looks like this in the sample https://github.com/gengjiawen/schema-stitching-demo/tree/feature/pass_header? looks like the context not passed when get schema.

pass undefined
pass undefined
not valid undefined
not valid undefined
gengjiawen commented 7 years ago

I see, that's because that time express-graphql is not used. Thanks for your great patience and help.

gengjiawen commented 7 years ago

For anyone want to pass all the original headers, you can do something like this: Notice to remove content-length from original header, or you will get timeout exception. This is found by @FX-HAO

class AuthForwardingGraphQLClient extends HttpGraphQLClient {
  protected async getHeaders(document: DocumentNode, variables?: { [name: string]: any }, context?: any, introspect?: boolean): Promise<{ [index: string]: string }> {
    const headers = await super.getHeaders(document, context, introspect);
    console.log('headers', headers)
    if (context) {
      console.log('context headers', context.headers)
      delete context.headers['content-length']
      return context.headers
    } else {
      return headers
    }
  }
}
adamkl commented 7 years ago

Hey Yogu, I was just working on this exact scenario last week and came up with pretty much the same solution you suggested. It was very easy to take a look at your code to see how it could be extended. You guys did a great job with the design and API!