This is the third post of our ongoing series about using PostGraphile.

In this post, we’ll learn how to authenticate a user with AWS Cognito and use the incoming JWT with our GraphQL client against PostGraphile.

The idea behind this is to leverage PostGraphile support of PostgreSQL’s RLS.

 Cognito JWTs

The first think to understand right now is that Cognito delivers several tokens that you may use with PostGraphile. Note that the token headers are not shown here but they are important because they gives us the public key id to be used to verify the token.

  • An access_token, for which a sample payload is shown below.
{
  "sub": "44b6a4ec-1578-4975-a36c-d51a506c772c",
  "event_id": "63410683-6ae9-11e8-a1cd-9f7a63602f61",
  "token_use": "access",
  "scope": "aws.cognito.signin.user.admin",
  "auth_time": 1528441126,
  "iss": "https://cognito-idp.eu-west-1.amazonaws.com/eu-west-1_____0fOqO",
  "exp": 1528533040,
  "iat": 1528529440,
  "jti": "86fffdd5-1847-42f7-a654-30d1c8be6d92",
  "client_id": "2g77jmh5atk5e2lbjmrbh5mg3r",
  "username": "44b6a4ec-1578-4975-a36c-d51a__6c772c"
}
  • An id_token for which a sample payload is shown below.
{
  "sub": "44b6a4ec-1578-4975-a36c-d51a506c772c",
  "email_verified": true,
  "iss": "https://cognito-idp.eu-west-1.amazonaws.com/eu-west-1_____0fOqO",
  "phone_number_verified": false,
  "cognito:username": "44b6a4ec-1578-4975-a36c------06c772c",
  "aud": "2g77jmh5atk5e2lbjmrbh5mg3r",
  "event_id": "63410683-6ae9-11e8-a1cd-9f7a63602f61",
  "token_use": "id",
  "auth_time": 1528441126,
  "phone_number": "+33666666666",
  "exp": 1528533040,
  "iat": 1528529440,
  "email": "omatrot@domain.com"
}
  • A refresh_token that is useless in our case.

 PostGraphile JWT machinery

The idea is that we provide, through the graphql client (more on this below), a JWT token to be processed by PostGraphile to:

  1. Verify the audience.
  2. Check that the token has not expired
  3. Validate the signature.
  4. Extract the user id to be used as PostgreSQL role.

The id_token above seems a good fit because it contains all the information we need.

Configuration

The verification process is done through the jsonwebtoken module. We need to provide the secret, in that case a public key associated with the Cognito User Pool. It is available a the following address, as explained here.

https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json

Replace {custom} parts with values that suits you and download the Json fragment returned at this address. They may be several keys (one for the access_oken, another for the id_token). Remember, you must choose the key by matching the key id found in the token header.

The trick here is that the public key must be provided in the PEM format. So we first need a one off process of the information available at the link above.

I have found this javascript node module that suited my need. I’ll let as an exercise to the reader the task to convert a jwk to the PEM format. If you’re curious enough, you could take a look at RFC’s 1421 through 1424

So far I have my public key in PEM format, and everything I need to enhance the Postgraphile startup configuration as seen in the previous post.

The next tricky part is to set a multiline parameter value on the command line… Because I’m using a javascript ecosystem file, I could use the ‘`’ character. You’ll find a sample below. I have removed several lines from my public key but you now have the idea.

{
      name	: 'postgraphile',
      script    : '/home/ec2-user/.nvm/versions/node/v8.11.2/bin/postgraphile',
      args	: `-c postgres://my-pg-rds-instance.eu-west-1.rds.amazonaws.com/mydb --watch --host ec2-ip-v4-public-address.eu-west-1.compute.amazonaws.com --cors --default-role unauth --jwt-secret '-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk8erpQAjLqCJIHMVGbl1
b/NBMHsNHmb0518WBp9oH4CTGwa/xxblNy0P9EMLSqXSZD0gk/ewO6l4NCSofcXL
9BY94TVDaiDOyh7CAvUzDFTjUcNGeiQbdF7bvbTElqh35M3Dxujn7sWBMq7GKqsW
LQIDAQAB
-----END PUBLIC KEY-----' --jwt-verify-algorithms RS256 --jwt-role username`,
}

The jwt related command arguments are:

  1. –jwt-secret
  2. –jwt-verify-algorithms
  3. –jwt-role

As a side note, I encountered a problem with the public key associated to the id_token that I would have used to check the audience as I originally intended. It troubled the pgSettings code somehow:

0|postgrap | Error: Error converting pgSetting: boolean needs to be of type string or number.
0|postgrap |     at isPgSettingValid (/home/ec2-user/.nvm/versions/node/v8.11.2/lib/node_modules/postgraphile/build/postgraphile/withPostGraphileContext.js:252:11)
0|postgrap |     at getSettingsForPgClientTransaction (/home/ec2-user/.nvm/versions/node/v8.11.2/lib/node_modules/postgraphile/build/postgraphile/withPostGraphileContext.js:182:17)
0|postgrap |     at withDefaultPostGraphileContext (/home/ec2-user/.nvm/versions/node/v8.11.2/lib/node_modules/postgraphile/build/postgraphile/withPostGraphileContext.js:28:51)
0|postgrap |     at Object.withPostGraphileContext [as default] (/home/ec2-user/.nvm/versions/node/v8.11.2/lib/node_modules/postgraphile/build/postgraphile/withPostGraphileContext.js:98:12)
0|postgrap |     at /home/ec2-user/.nvm/versions/node/v8.11.2/lib/node_modules/postgraphile/build/postgraphile/http/createPostGraphileHttpRequestHandler.js:75:49
0|postgrap |     at Promise.all.paramsList.map (/home/ec2-user/.nvm/versions/node/v8.11.2/lib/node_modules/postgraphile/build/postgraphile/http/createPostGraphileHttpRequestHandler.js:499:40)
0|postgrap |     at Array.map ()
0|postgrap |     at requestHandler (/home/ec2-user/.nvm/versions/node/v8.11.2/lib/node_modules/postgraphile/build/postgraphile/http/createPostGraphileHttpRequestHandler.js:435:52)
0|postgrap |     at
0|postgrap |     at process._tickDomainCallback (internal/process/next_tick.js:228:7)

So I temporarily moved to the access_token because the public key is doing fine.

Using Apollo client

The last thing to do is to actually send a query. Here is one way to set up the Apollo Client (apollo-boost) with the access token JWT coming from the AWS Amplify client:

import ApolloClient from "apollo-boost";
import { Auth } from "aws-amplify";
import gql from "graphql-tag";

private onPress = async () => {
        try {
// Get the logged in user
const user = await Auth.currentSession();

const client = new ApolloClient({
                request: async (operation) => {
                    // setContext function can't be made async.
                    // This is why the user is retreived above
                    operation.setContext(() => {
                        // get the authentication token from local storage if it exists
                        const token = user.accessToken.jwtToken;
                        // return the headers to the context so httpLink can read them
                        return {
                            headers: {
                                authorization: token ? `Bearer ${token}` : "",
                            },
                        };
                    });
                },
                uri: "http://ec2-my-ip-v4-public.eu-west-1.compute.amazonaws.com:5000/graphql",
            });

            const result = await client.query({
                query: gql`{
                    allPasswds(first: 1) {
                      totalCount
                    }
                  }`,
            });
            console.log(result);
        } catch (e) {
            console.log(e);
        }
    }

And bingo it works, Postgraphile tries to use the username as the current role, which does not exist yet in my database:

0|postgrap | error: role "44b6a4ec-1578-4975-a36c------6c772c" does not exist

In my opinion, Postgraphile could be improved to download the secret itself because the issuer URL is available in the tokens. I’ll talk about that with Benjie Gillam that helped me a lot on this journey. Kudos man.

2 thoughts on “The ultimate DAL with PostGraphile part 3: using a JWT coming from AWS Cognito

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s