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.
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.
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.
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)};};
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.
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];};
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)
.
const getPolicyDocument = (effect, resource) => {const policyDocument = {Version: "2012-10-17", // default versionStatement: [{Action: "execute-api:Invoke", // default actionEffect: effect,Resource: resource}]};return policyDocument;};
Perhaps less interesting, I want to call out the use of Bent in this example, which has been fantastic to use.
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}` });
Special thanks to David Wells, whose auth0 authorizer was one of the first I worked with.