Oct 06, 2017
At Merapar we frequently use GraphQL and for one particular project we have deployed it in AWS behind the Amazon API Gateway service. Security is managed using AWS Cognito federated identities, however in order to perform authenticated requests and take advantage of the Apollo client’s advanced features (such as caching, pre-fetching, subscriptions) we had to develop a new network interface for the Apollo GraphQL client. We have decided to open source the NPM, so that you can use it in your own projects.
The new network interface is available on Github and as an installable NPM:
- Github repository
- Installable NPM module
The issue
As we configured our AWS API Gateway to allow requests only to clients authenticated through via AWS Cognito federated identities, all requests towards the API Gateway had to be properly signed using the AWS Signature v4.
The main problem encountered was that the Apollo client did not allow the user to influence the actual request on the wire by, only to implement a middleware to tweak the HTTP request before Apollo itself performs the request. This prevented us from signing the request the AWS prescribed way: firstly, implementing this as a middleware was not sufficient, because at this stage the request’s payload was not available (the signature has to contain a checksum of the payload). Secondly, it prevented the use of the AWS generic SDK or the API Gateway custom SDK (that can be generated) to sign the request and send it directly towards the API Gateway.
The Solution
In order to perform compliant requests towards the API Gateway, we had to develop an AWS network interface for the GraphQL Apollo client.
The idea was simple: extend the BaseNetworkInterface class of the Apollo client, overwriting it so requests are called using the API Gateway generated SDK instead of the fetch library. This SDK must be initiated with AWS credentials (accessKey, secretKey and session token) and can then perform requests towards the API Gateway as the authenticated user. It handles all the signature generations and appropriate headers insertion for all requests made to the API Gateway.
This new AWS network interface also allows the client to detect when the user session expires.
Setup
- Install the API Gateway network interface with : npm install apollo-client-aws-ni save
- Generate your AWS API Gateway custom SDK and download it. To do so, go to your API Gateway console and follow the steps described in the screenshot below. You can also use the AWS CLI tool to achieve this.
Usage
You can follow these guidelines to create a graphql-client.ts for an Angular App, using the apollo-angular module, or you could easily adapt these instructions to other frameworks or directly usage in JavaScript.
First, the following imports must be added :
import { ApolloClient } from 'apollo-client';
import { AwsApiGwClient, AwsApiGwNetworkInterface } from "apollo-client-aws-ni/api-gw-connector";declare var apigClientFactory: any;
// being the AWS API GW generated SDK root object (to be imported in main html file)
The apiClientFactory has to be declared rather than imported, as the SDK does not include the types declarations. However, make sure you include all the AWS SDK javascript files within your global scope (for an angular project this can be done in the body section of your index.html).
Then, export a function that allows initialization of the AWS API GW generated SDK :
let apigClient;
let clientSet = false;
export function updateAwsApiGwClient(): void {
apigClient = apigClientFactory.newClient({
accessKey: localStorage.getItem('accessKey'),
secretKey: localStorage.getItem('secretKey'),
sessionToken: localStorage.getItem('sessionToken'),
region: 'eu-central-1'
});
clientSet = true;
};
Here, AWS credentials have been stored in the browser local storage upon successful authentication. It is also important to specify the API Gateway AWS region.
Next, a wrapper for the generated SDK must be written. This allows the use of the network interface with a wide range of API Gateway configurations and use cases. This wrapper is also the place you should define what to do when the session expires.
const awsApiGwClient: AwsApiGwClient = {
// this prevent a request to be made if the AWS SDK is not initialized isAuthenticated(){
return clientSet;
},
// this allow you to be noticed by the network interface that the session AWS user has expired
authenticationExpired(){
clientSet = false;
// you call here your logout() method to redirect to a login page
// myAuthService.logout();
},
// you just have to call here the AWS generated SDK function corresponding to your graphql endpoint graphqlPost(param, body, extra) {
return apigClient.graphqlPost(param, body, extra);
}
}
In the last graphqlPost, you may need to swap the underlying apigClient.graphqlPost function with the one declared in your AWS SDK. By default the function name is the last part of your endpoint path concatened with the method. So if you access your graphql server through the API GW end point https://<my-api-gw>/<my-api-name>/graphql using the POST method, your SDK function name will be : graphqlPost
Finally, create the AwsApiGwNetworkInterface based on this wrapper and use it to create the Apollo Client.
export function provideClient(): ApolloClient {
const networkInterface: any = new AwsApiGwNetworkInterface(awsApiGwClient);
return new ApolloClient( {networkInterface: networkInterface} );
}
Once those functions are defined and exported, you have to :
- pass the provideClient() function when you enable the (Angular) ApolloModule : ApolloModule.forRoot(provideClient);
- call the updateAwsApiGwClient() function into your authentication flow as soon as you get the AWS credentials.
You will then be able to initiate and use the Apollo client as usual.