Learn how to use Unkey to secure your Supabase functions
Written by
James Perkins
Published on
Supabase offers edge functions built upon Deno. They have a variety of uses for applications like OpenAI or working with their storage product. In this post, we will show you how to use Unkey to secure your function in just a few lines of code.
Unkey is an open source API management platform that helps developers secure, manage, and scale their APIs. Unkey has built-in features that can make it easier than ever to provide an API to your end users, including:
First, we need to create a folder. Let's call that unkey-supabase
. This will be where our supabase functions exist going forward.
1
mkdir unkey-supabase && cd unkey-supabase
Now, we have a folder for our project. We can initialize and start Supabase for local development.
1
supabase init
Make sure Docker is running. The start
command uses Docker to start the Supabase services.
This command may take a while to run if this is the first time using the CLI.
1
supabase start
Now that Supabase is setup, we can create a Supabase function. This function will be where we secure it using Unkey.
1
supabase functions new hello-world
This command creates a function stub in your Supabase folder at ./functions/hello-world/index.ts
. This stub will have a function that returns the name passed in as data for the request.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
console.log("Hello from Functions!");
serve(async (req) => {
const { name } = await req.json();
const data = {
message: `Hello ${name}!`,
};
return new Response(JSON.stringify(data), {
headers: { "Content-Type": "application/json" },
});
});
Before making any changes, let's ensure your Supabase function runs. Inside the function, you should see a cURL command similar to the following:
1
2
3
4
curl -i --location --request POST 'http://localhost:54321/functions/v1/' \
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0' \
--header 'Content-Type: application/json' \
--data '{"name":"hello-world"}'
After invoking your Edge Function, you should see the response { "message":"Hello Functions!" }
.
If you receive an error Invalid JWT, find the
ANON_KEY
of your project in the Dashboard under Settings > API.
verifyKey
to our functionNow that we have a function, we must add Unkey to secure the endpoint. Supabase uses Deno, so instead of installing our npm package, we will use ESM CDN to provide the verifyKey
function we need.
1
2
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
import { verifyKey } from "https://esm.sh/@unkey/api";
verifyKey
do?Unkey's verifykey
lets you verify a key from your end users. We will return a result and you can decide whether to give the user access to a resource or not based upon that result. For example, a response could be:
1
2
3
4
5
6
7
8
9
{
"result": {
"valid": true,
"ownerId": "james",
"meta": {
"hello": "world"
}
}
}
First, let's remove the boilerplate code from the function so we can work on adding Unkey.
1
2
3
4
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
import { verifyKey } from "https://esm.sh/@unkey/api";
serve(async (req) => {});
Next, we will wrap the serve
function inside a try-catch. Just in case something goes wrong, we can handle that.
1
2
3
4
5
6
7
8
9
10
11
12
13
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
import { verifyKey } from "https://esm.sh/@unkey/api";
serve(async (req) => {
try {
// handle our functions here.
} catch (error) {
// return a 500 error if there is an error with a message.
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
});
}
});
Inside our try, we can look for a header containing the user's API Key. In this example we will use x-unkey-api-key
but you could call the header whatever you want. If there is no header will immediately return 401.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
import { verifyKey } from "https://esm.sh/@unkey/api";
serve(async (req) => {
try {
const token = req.headers.get("x-unkey-api-key");
if (!token) {
return new Response("Unauthorized", { status: 401 });
}
} catch (error) {
// return a 500 error if there is an error with a message.
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
});
}
});
The verifyKey
function returns a result
and error
, making the logic easy to handle. Below is a simplified example of the verification flow.
1
2
3
4
5
6
7
8
9
10
11
12
13
const { result, error } = await verifyKey("key_123");
if (error) {
// handle potential network or bad request error
// a link to our docs will be in the `error.docs` field
console.error(error.message);
return;
}
if (!result.valid) {
// do not grant access
return;
}
// process request
console.log(result);
Now you have a basic understanding of verification, let's add this to our Supabase function.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
serve(async (req) => {
try {
const token = req.headers.get("x-unkey-api-key");
if (!token) {
return new Response("No API Key provided", { status: 401 });
}
const { result, error } = await verifyKey(token);
if (error) {
// handle potential network or bad request error
// a link to our docs will be in the `error.docs` field
console.error(error.message);
return new Response(JSON.stringify({ error: error.message }), {
status: 400,
});
}
if (!result.valid) {
// do not grant access
return new Response(JSON.stringify({ error: "API Key is not valid for this request" }), {
status: 401,
});
}
return new Response(JSON.stringify({ result }), { status: 200 });
}
We can send a curl request to our endpoint to test this functionality. Below is an example of the curl to send. Remember, we now need to include our API key.
1
2
3
curl -XPOST -H 'Authorization: Bearer <SUPBASE_BEARER_TOKEN>' \
-H 'x-unkey-api-key: <UNKEY_API_KEY>' \
-H "Content-type: application/json" 'http://localhost:54321/functions/v1/hello-world'
Adding CORS allows us to call our function from the frontend and decide what headers can be passed to our function. Inside your functions
folder, add a file called cors.ts
. Inside this cors file, we will tell the Supabase function which headers and origins are allowed.
1
2
3
4
5
export const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers":
"authorization, x-client-info, x-unkey-api-key, content-type",
};
In this post, we have covered how to use Unkey with Supabase functions to secure them. You can check out the code for this project in our Examples folder
2500 verifications and 100K successful rate‑limited requests per month. No CC required.