Building a serverless authentication service with Serverless Framework and Supabase

Benjamin Iduwe
6 min readApr 10, 2023

--

AWS Lambda and Supabase

Serverless computing, also known as Function-as-a-Service (FaaS), is a cloud computing model where the cloud provider manages the infrastructure and dynamically manages the allocation of computing resources. In serverless computing, the cloud provider is responsible for the maintenance and scaling of servers, and users only pay for the actual compute time used.

Serverless computing is becoming increasingly popular due to its benefits. It allows developers to focus on writing code and business logic without worrying about the underlying infrastructure. It also provides automatic scaling to handle sudden increases in demand, which reduces the risk of downtime or performance issues. Additionally, it can be more cost-effective than traditional server-based models as users only pay for the computing time they actually use.

To use serverless computing, developers write functions in a supported language and deploy them to the cloud provider. The cloud provider then manages the execution and scaling of the functions. Popular serverless providers include AWS Lambda, Google Cloud Functions, and Azure Functions. We will be using Serverless Framework (NodeJS) to build a complete authentication service and deploy it to AWS. To proceed, you need an AWS account and your IAM credentials configured on your CLI. Please do not use your root account, instead, create an IAM user and generate an IAM credential for it. Some of the AWS services we will be using include API Gateway, and AWS Lambda. Under the hood, we would be using CloudFormation and S3.

Supabase is an open-source alternative to Firebase that provides a suite of tools for building web and mobile applications. It includes features such as authentication, a real-time database, storage, and more. Supabase leverages PostgreSQL to provide a scalable and reliable backend for your application. One of the unique features of Supabase is its focus on open-source software. All of the code for Supabase is available on GitHub, and the team encourages contributions from the community. This makes it a great choice for developers who want to avoid vendor lock-in and have more control over their infrastructure.

In this article, we would be delegating authentication to Supabase so we don’t have to worry about database design, rate limiting, and emails. Create a Supabase account if you don’t have one, create a project, and grab your API keys (Project URL and public API key). Supabase provides a simple and intuitive interface for managing your authentication service, and its SDK makes it easy to integrate with your existing codebase.

Getting started with SLS.

Install Serverless Globally.

npm intall -g serverless

After installing serverless, run.

serverless

You would get a template of serverless projects you can choose from.

? What do you want to make? (Use arrow keys)
AWS - Node.js - Starter
❯ AWS - Node.js - HTTP API
AWS - Node.js - Scheduled Task
AWS - Node.js - SQS Worker
AWS - Node.js - Express API
AWS - Node.js - Express API with DynamoDB
AWS - Python - Starter
AWS - Python - HTTP API
AWS - Python - Scheduled Task
AWS - Python - SQS Worker
AWS - Python - Flask API
AWS - Python - Flask API with DynamoDB
Other

Select AWS — Node.js — HTTP API, Enter a project name, and select NO when you are asked if you want to deploy now. Once it’s done scaffolding the HTTP API template, open the project with your favorite editor and open your editor CLI. Let’s install the serverless-offline plugin.

Installing SLS plugins

Serverless provides a wide range of plugins, we are using two plugins for this project: serverless-offline and serverless-dotenv-plugin

In your root directory ensure you have a package.json file, if you don’t run

npm init -y

Serverless offline provides a complete mock of how the API Gateway serves requests using a Lambda function. Using the serverless.yml file, we define our routes and all the configurations required to deploy our auth-service to AWS. Let's install serverless-offline.

serverless plugin install -n serverless-offline

You can learn more about serverless offline here

To add our Supabase credentials to your project, there are several ways to store environment variables in the Serverless Framework. These include using AWS Parameter Store, Secret Manager, and .env files, or storing them in your serverless.yml file. However, it is not advisable to store sensitive files like secret keys or DB credentials in the serverless.yml file. Let’s use the dotenv plugin. Install the plugin

serverless plugin install -n serverless-dotenv-plugin

Create your .env files

touch .env .env.example

Add your Supabase credentials to the environment variables and ensure you add your .env file to your gitignore.

SUPABASE_URL='xxxxx'
SUPABASE_KEY='xxxxx'

Before the provider section in your serverless.yml file add,

useDotenv: true

To pass the environment variables to all your serverless functions use

provider:
environment:
SUPABASE_URL: ${env:SUPABASE_URL}
SUPABASE_KEY: ${env:SUPABASE_KEY}

In your package.json, under scripts add

"start": "serverless offline start"

Run npm start to start the project. The project would be served on http://localhost:3000. Hot reload doesn’t sometimes work, to add hot reload you can use nodemon to watch for file changes.

Integrating Supabase into your Serverless project.

First, we need to install the Supabase SDK for JS. Check out Supabase docs.

npm install @supabase/supabase-js

Once it’s done, create a client or service class for Supabase. We can call it auth-service.js (src/services/auth-service.js)

Import the dependencies.

const { createClient } = require("@supabase/supabase-js")

Creating the Supabase client.

const SUPABASE_URL = process.env.SUPABASE_URL || "";
const SUPABASE_KEY = process.env.SUPABASE_KEY || "";

const supabase = () => createClient(SUPABASE_URL, SUPABASE_KEY);

Add the login and registration functionality in the service class.

/**
* Create an account.
*
* @param {{email: string, password: string, name: string}} params
* @returns {Promise<any>}
*/
module.exports.createAccount = async ({ email, password, name }) => {
return supabase().auth.signUp({
email,
password,
options: {
data: {
name,
},
},
});
};

/**
* Validate user credentials and generate JWT token.
*
* @param {{email: string, password: string}} params
* @returns {Promise<any>}
*/
module.exports.login = async ({ email, password }) => {
return supabase().auth.signInWithPassword({
email,
password,
});
};

Let’s create our functions in src/functions

login.js (src/functions/login.js)

const { login } = require("../services/auth-service");

module.exports.handler = async (event, context) => {
const { email, password } = event.body ? JSON.parse(event.body) : {};

if (!email || !password) {
return {
statusCode: 400,
body: JSON.stringify({
message: "Email and password are required.",
}),
};
}

const { data, error } = await login({ email, password });

if (error) {
return {
statusCode: 400,
body: JSON.stringify({
message: "Unable to login.",
error: error.message,
}),
};
}

return {
statusCode: 200,
body: JSON.stringify({
message: "Login successfully.",
data,
}),
};
};

The snippet above validates the input and attempts to log in using Supabase, with proper error handling. The login functionality provides the user data and a JWT token you can use on any application.

register.js (src/functions/register.js)

const { createAccount } = require("../services/auth-service");

module.exports.handler = async (event, context) => {
const { name, email, password } = event.body ? JSON.parse(event.body) : {};

if (!email || !password || !name) {
return {
statusCode: 400,
body: JSON.stringify({
message: "Name, email, and password are required.",
}),
};
}

const { data, error } = await createAccount({ name, email, password });

if (error) {
return {
statusCode: 400,
body: JSON.stringify({
message: "Unable to create an account.",
error: error.message,
}),
};
}

return {
statusCode: 200,
body: JSON.stringify({
message: "Account created successfully, please verify your email.",
data,
}),
};
};

The snippet above validates the input and creates an account on your Supabase project.

After the api function in your serverless.yml, add the next snippet.

api:
handler: index.handler
events:
- httpApi:
path: /
method: get
# Add login and register routes here.
login:
handler: src/functions/login.handler
events:
- httpApi:
path: /login
method: post
register:
handler: src/functions/register.handler
events:
- httpApi:
path: /register
method: post

Start your serverless app.

npm run start

Testing and deploying to AWS.

POST /login requires email and password.

POST /register requires name, email, and password.

Once you are done, let’s deploy to AWS.

Add the following script to your package.json

"deploy": "serverless deploy",
"remove": "serverless remove"

Run the deploy command to deploy to AWS.

npm run deploy

It may take some time, but once it’s done, you are set to go! You can remove the deployment by using npm run remove. Happy shipping 🎉

If you encounter any issues, you can share them in the comments, please note this article was written with Serverless Framework version 3. Which is the latest version as of the time this article was published, if you encounter any compatibility issues, please check the Serverless Framework docs. Here’s the sample code here.

--

--