70 %
Chris Biscardi

Using Netlify Identity with AWS Lambda Custom Authorizers

If you prefer video, there's one here along with several other videos on working with Netlify and AWS Lambda.

Netlify Identity is an integrated authorization solution for Netlify that allows users to sign up, log in, and also provides the verified user object inside of Netlify Functions.

Sometimes we'll need to use Netlify Identity on other platforms, like AWS. This is the case if we need websocket support or want to take advantage of AWS IAM. In these cases we can still use Netlify Identity as our source of truth as our application scales up by taking advantage of custom authorizers.

What's an authorizer?

A Lambda authorizer (formerly known as a custom authorizer) is an API Gateway feature that uses a Lambda function to control access to your API. aws docs

There are two types of authorizers: token based and request parameter based. We are going to use a token authorizer in this post, but you'll need a request parameter authorizer if you're using websockets.

Let's say we have a GraphQL API running on AWS Lambda. An authorizer sits in front of our GraphQL API, checks the bearer token for us, then returns an IAM policy indicating whether or not the request should be handled by the GraphQL server, or denied. AWS then evaluates the policy and either forwards the request to the GraphQL server, or doesn't.

Implementation

If you play around with an existing Netlify identity instance and the GoTrue Playground, what you end up realizing is that to get the user data is a single HTTP call. This makes our authorizer implementation very small.

  • Get the token
  • Get the user
  • If the user isn't valid, throw
  • If the user is valid, return the policy
JS
module.exports = async params => {
const token = getToken(params);
const user = await getJSON(
"https://serverless-todo-netlify-fauna-egghead.netlify.com/.netlify/identity/user",
null,
{ Authorization: `Bearer ${token}` }
);
if (!user.id) {
throw new Error("Netlify Identity Failed");
}
return {
principalId: user.id,
policyDocument: getPolicyDocument(
"Allow",
params.methodArn
)
};
};

Getting the bearer token

As long as the token is sent in the Authorization header as a bearer token: Authorization: Bearer my-token, when getting the token string from the params is fairly mechanical.

JS
const getToken = params => {
if (!params.type || params.type !== "TOKEN") {
throw new Error(
'Expected "event.type" parameter to have value "TOKEN"'
);
}
const tokenString = params.authorizationToken;
if (!tokenString) {
throw new Error(
'Expected "event.authorizationToken" parameter to be set'
);
}
const match = tokenString.match(/^Bearer (.*)$/);
if (!match || match.length < 2) {
throw new Error(
`Invalid Authorization token - ${tokenString} does not match "Bearer .*"`
);
}
return match[1];
};

Policies

We use the 2012-10-17 IAM policy version to say that the user is allowed to invoke an effect on a specific resource. In our case above, we decided to Allow invoking the next lambda: getPolicyDocument("Allow", params.methodArn).

JS
const getPolicyDocument = (effect, resource) => {
const policyDocument = {
Version: "2012-10-17", // default version
Statement: [
{
Action: "execute-api:Invoke", // default action
Effect: effect,
Resource: resource
}
]
};
return policyDocument;
};

Bent

Perhaps less interesting, I want to call out the use of Bent in this example, which has been fantastic to use.

JS
const bent = require("bent");
const getJSON = bent("GET", "json");
const user = await getJSON(
"https://serverless-todo-netlify-fauna-egghead.netlify.com/.netlify/identity/user",
null,
{ Authorization: `Bearer ${token}` }
);

Thanks

Special thanks to David Wells, whose auth0 authorizer was one of the first I worked with.