LinkedIn tracking pixel
CIPHERSTASH / BLOG

Adding CipherStash Encryption to a Next.js app that uses the Supabase SDK

CJ Brewer
CJ Brewer

Let's look at how to add CipherStash Encryption to a Next.js app that uses the Supabase SDK and server actions to read and insert encrypted data into a Supabase Postgres instance.

This is the first installment of the CipherStash Encryption tutorial blog series. In these posts I’ll be covering how you can integrate CipherStash Encryption into popular frameworks and tools, rather than just focusing on business value. I want to show how convenient it is to add CipherStash Encryption to your tech stack and gain all the security benefits of CipherStash Encryption!

This post will cover how to add CipherStash Encryption to a Next.js app that uses the Supabase SDK and server actions to read and insert encrypted data into a Supabase Postgres instance.

For the security-minded folks, it’s worth calling out some points about CipherStash encryption before continuing:

  • CipherStash encryption is backed by ZeroKMS which will use a unique encryption key for every single record, also referred to as field-level encryption

    This enables cryptographically proven audit trails of decryption events. In simpler terms, we can provide you an audit log of every data access event to help meet compliance and stringent customer requirements.

  • CipherStash Encryption encrypts data using AES-256-GCM-SIV with a unique key per value

  • ZeroKMS supports bulk operations, and is up to 14x faster than AWS KMS

The following tutorial assumes a few things:

  • You have an existing Next.js app
  • You have an existing Supabase instance

Step 1: Add CipherStash Encryption

Install the CipherStash Encryption npm package:

npm install @cipherstash/stack

Under the hood, CipherStash Encryption uses the Neon framework to create JavaScript Rust bindings to the CipherStash SDK which handles the crypto functions and communication with ZeroKMS. Given this, you'll need to use the native Node.js require which is achieved by opting out of server bundling.

In your next.config.json file add the following directive:

// Referenced in the Next.js docs: https://nextjs.org/docs/app/api-reference/config/next-config-js/serverExternalPackagesconst nextConfig = {  serverExternalPackages: ['@cipherstash/stack'],}module.exports = nextConfig

Step 2: Install the CipherStash CLI

The CLI is used to bootstrap your CipherStash environment so go ahead and install it:

brew install cipherstash/tap/stash

If you're using Linux, follow the instructions in the docs.

Step 3: Bootstrap your environment

Now that the CLI is installed, run the setup command and follow the prompts to either login/signup, create a new keyset, and store the settings in the TOML files.

stash setup

When it's time to go to production, you can run this process again and output the settings as environment variables which you can then store securely 🚀.

Before continuing to the next step, make sure that you have the following files in the root of your project directory.

  • cipherstash.toml
  • cipherstash.secret.toml (the CLI tool will automatically append this file name to your .gitignore file to ensure it is not checked into source)

Step 4: Init the Encryption client

CipherStash Encryption uses a domain-specific schema definition to configure the state of CipherStash Encryption. This state will determine how to encrypt the data, whether you need encrypted indexes or not. Encrypted indexes is how CipherStash encryption enables the use of Searchable Encryption, which you can read more about it in the docs.

Create a protect directory in either the root of your app, or in the src directory if you have that configured for your project. Also create the following files inside the protect directory:

  • index.ts
  • schema.ts

Open the schema.ts file and define a schema using the exported helper functions:

import { encryptedTable, encryptedColumn } from ‘@cipherstash/stack/schema’// The encryptedTable function takes two arguments:// - the corresponding table name in your Supabase database// - An object, where the keys are the columns that store encrypted dataexport const users = encryptedTable(‘users’, {	// The encryptedColumn function takes a single argument:	// - the corresponding column name in your Supabase table	email: encryptedColumn(‘email’),})

Now that a schema is defined, open the protect/index.ts file and initialize the Encryption client:

import { Encryption } from ‘@cipherstash/stack’import { users } from ‘./schema’// The Encryption function takes N number of arguments,// where each argument is a CipherStash Encryption schema.export const encryptionClient = await Encryption(users)

These patterns might look familiar and that’s because when we were creating the CipherStash Encryption interface we didn’t want to re-invent the wheel, but rather use existing patterns leveraged in other libraries (e.g. Drizzle ORM was a major influence).

Step 5: Update your Supabase schema

Encrypted data is stored using the data type jsonb so update the column in the Supabase instance.

jsonb-encrypted-data

In most cases, this tutorial is meant to be run through with a playground environment where the environment can be modified easily and without side effects. If you're looking to modify a production instance, you'll need to consider a migration plan. If you’d like to chat about what that might look like, our solutions team would love to help out. Multiple contact options are available here.

Step 6: Encrypt data

You can use the encrypt and bulkEncrypt functions in any server side component (which includes Vercel Edge Functions or Cloudflare workers). Each operation in the CipherStash Encryption library leverages a Result pattern which is outlined in the code example below:

import { encryptionClient } from '@/encryption'import { users } from '@/encryption/schema'// The encrypt function takes two arguments:// - the plaintext data// - an object defining the table and column that the data will be stored intoconst result = encryptionClient.encrypt('plaintext', {	table: users,	column: users.email,})// The result will alway return either a failure OR data key but never bothif (result.failure) {	// handle the failure explicitly}const encryptedPayload = result.data

Step 7: Insert the data using the Supabase SDK

The Supabase SDK for JavaScript makes it really easy to insert data directly into your Supabase instance. Given you’ve already configured the SDK, you would insert the data like this:

const { data, error } = await supabase    .from('users')    .insert([{ email: encryptedPayload }])    .single()

If you want to learn more about the types for the encrypted payload data, read more about Encrypted Query Language format in the docs.

Typically you’d handle the inserting of data in a Next.js server action or through your API!

Conclusion

Now you’ve seen how easy it is to integrate CipherStash encryption into your Next.js project, using CipherStash Encryption and storing the encrypted data in a Supabase Postgres instance! As you try it out, keep in mind all of the security enhancements you’re getting with field-level encryption, plus those handy audit logs of every data access event.

That’s it for this first installment of the CipherStash Encryption tutorial series! Stay tuned for more tutorials where we’ll dive into advanced features, like searchable encryption, and continue to provide examples for other popular databases and frameworks. Cheers!

If you’re interested in giving CipherStash Encryption a try, install the npm package and create a CipherStash account!

Start securing your data

Create a free workspace, integrate your stack, or book a demo.