Introduction - Building & Testing an API for... Serverless Sessions?

Using DynamoDB & API Gateway to build a combined Web & API Authentication Session Store, and why exactly should I want to do such a thing?

Sessions, Dear Reader, are an integral part of how the web functions. Sessions allow us to overcome the Stateless nature of HTTP and implement niceties such as "knowing who our users are" and "remembering what our users are doing".

I'm currently building a tool to help developers track and share what they're working on, provisionally called Maruki. Because it's a tool for developers, I want it to have both a full-featured API and a front end. I also want to avoid as much infrastructure management as I can, and as such, I'm building a Serverless architecture, using the lovely SST toolkit.

Maruki uses Lambda to access a DynamoDB powered data-layer (with what I hope is a good first attempt at Single Table Design), along with a Sveltekit powered front-end. Working in Sveltekit has been lovely, with one slight caveat.

Session Management in Sveltekit has been the very dickens.

Because it's relatively new, Sveltekit is undergoing rapid changes. I had just got a naïve implementation of session management working when this update was released, necessitating major changes throughout the app, including session management. This, coupled with the paucity of documentation (owing to the new changes) has meant that my experience of Session Management in Sveltekit has been the very dickens.

Still, I have the basic "Send Login Get Cookie Nom Nom Nom" flow working, and now need to actually authenticate users & construct unique sessions.

The problem we're solving

I need to support both API and website access, so I might as well have a unified mechanism. I want it to be serverless, scalable, and as hands-off as possible.

For now, users will have a username and password, but in the future I'd like to allow alternative access methods. Users are already identified in my system with a unique ID that's separate from their email and username. I'd also like to have a non-password authentication system for my API.

Sessions should expire at some point, and I'd like to be able to force-expire them myself.

I'd also like to have things be well tested and monitored, because Knowing is Half the Battle, or so I hear.

How we're solving it

Design

Let's implement traditional, server-side sessions, using API Gateway Lambda Authorizers to enforce access requirements and DynamoDB for session storage.

Website users will log in with their username and password, and receive an associated cookie for access. Sessions will expire every week or so. Reasons for the "Or So" are detailed below.

API users will generate a request token using their API Key. That token will expire every hour (or so). This helps prevent replay attacks, and also saves us a bit of compute (by virtue of only running our access-key computation once per generation).

Components

API Gateway

API Gateway is Amazon's solutions for scalable, controllable API proxying. We're making use of it's ability to dispatch HTTP requests to an underlying Lambda function.

czNmcy1wcml2YXRlL3Jhd3BpeGVsX2ltYWdlcy93ZWJzaXRlX2NvbnRlbnQvbHIvcGQ0My00OS1jaGltLmpwZw.webp

Jeff Bezos and Investors tour API Gateway

API Gateway Lambda Authorizers

Built into API Gateway is the ability to add a Lambda function as an Authorizer to any route. This function receives either a bearer token or some configurable subset of the request being made, and needs to return an IAM Policy in return.

Using an authorizer function gives me a few things. By denying access before even hitting my functions, it provides me with a level of abuse protection. I can implement complicated access patterns because I'm returning an IAM Policy, and have the flexibility to use whatever authentication mechanism I want. Plus, the result is automatically cached.

Lambda

AWS Lambda is "a compute service that lets you run code without provisioning or managing servers". Basically, we upload stand-alone pieces of code which, when needed, are executed in a controlled environment, then terminated. There is no additional management required. It's wonderful.

DynamoDB

Yes, I could use Redis as an in-memory Session Store, but the pricing doesn't make sense; at the lowest cost I'd be paying nearly 40c/hr to keep Amazon MemoryDB online, which is enough to pay for 1.5 million DynamoDB reads or a Gb/Month of storage. Even using Elasticache would cost us 1.6¢/hr. It's not a fair comparison because you have to consider all usage costs.... But even a back of envelope calculation puts DynamoDB far ahead of the alternatives.

But what about the speed I hear you ask! In practical terms, I've observed DynamoDB response times for my authentication logic are consistently below 20ms. Additionally, I feel that tiny delays in areas like security and "calculation" activities make users feel more reassured.

Typescript

I profess no special TypeScript knowledge, nor do I claim to be demonstrating the "right" way to achieve any particular goal. I am merely a dilettante! However, I do find type checking helps keep some of Javascript's more egregious peccadillos in check.

Next up

I think our plan is sufficient reading for now! Should you wish to follow the rest of this series, please be so kind as to Follow me here, or over on Twitter.