Learn how to use Unkey with an auth provider, to associate your keys with your users.
Written by
James Perkins
Published on
When working with your external facing API and having a client application, you need to identify which user owns an API Key. Having a way to identify the user allows you to understand their usage of your product better. This blog is going to cover how you can use your authentication provider to add a way to identify the user.
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:
We will use my favorite authentication provider, Clerk, in this example. The concepts described below are agnostic, so feel free to use whatever provider works for you. We will also use Next.js for demo purposes, but it isn't a requirement.
The first thing we want to do is create our Next.js application and install our dependencies.
1
npx create-next-app@latest unkey-with-auth
We need Unkey's typescript library and Clerk's next.js package for dependencies.
1
npm install @unkey/api @clerk/nextjs
Then, finally, we are going to use Shadcn to speed up the styling. If you copy the two commands below, it will install everything you need.
1
2
3
npx shadcn-ui@latest init
npx shadcn-ui@latest add button input label card
Make sure you include your Clerk secret key and publishable key in your .env.local
file. You can find these in your Clerk dashboard. You will also need an Unkey root key and API ID. You can find these in your Unkey dashboard.
1
2
3
4
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_xxxxxxxxxxxxxx
CLERK_SECRET_KEY=sk_test_xxxxxxxxxxxxxx
UNKEY_API_ID=api_xxxxxxxxxxxxxx
UNKEY_ROOT_KEY=unkey_xxxxxxxxxxxxxx
You can skip this step if you aren't using Clerk as an auth provider. We only need to update our root layout file and add middleware.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import "./globals.css";
import { Inter } from "next/font/google";
import { ClerkProvider } from "@clerk/nextjs";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<ClerkProvider>
<html lang="en">
<body>{children}</body>
</html>
</ClerkProvider>
);
}
Then, you need to add middleware.ts to the root of the project. This protects all pages and routes except /api/secret
. More on that later.
1
2
3
4
5
6
7
8
9
import { authMiddleware } from "@clerk/nextjs";
export default authMiddleware({
publicRoutes: "/api/secret",
});
export const config = {
matcher: ["/((?!.+\\.[\\w]+$|_next).*)", "/", "/(api|trpc)(.*)"],
};
Unkey accepts an owner_id when creating a key that we can use to associate our auth provider's unique identifier, such as user_id
. In our example application, we are going to use a server action to create the key.
We will create a client component that takes a name for an API. This API key name can make it easier to identify the critical self and not the owner in our demo, but it gives you an idea of how the flow would work. Create a folder called keys
and a client.tsx
file. Inside that file, add the following imports from our components from shadcn.
1
2
3
4
5
6
7
8
9
10
11
12
"use client";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
Now, we can create our component, which we will name UnkeyElements
and use the card component to create an easy-to-use form.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const UnkeyElements = () => {
return (
<div className="mt-8">
<Card className="w-[350px]">
<CardHeader>
<CardTitle>Create API Key</CardTitle>
<CardDescription>
Create your API key so you can interact with our API.
</CardDescription>
</CardHeader>
<form>
<CardContent>
<div className="grid w-full items-center gap-4">
<div className="flex flex-col space-y-1.5">
<Label htmlFor="name">Give your key a name</Label>
<Input name="name" placeholder="Key for next big thing" />
</div>
</div>
</CardContent>
<CardFooter className="flex justify-between">
<Button type="submit">Create Key</Button>
</CardFooter>
</form>
</Card>
</div>
);
};
export { UnkeyElements };
Make sure to import this into your page.tsx
file and add it to the page.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { UnkeyElements } from "./keys/client";
export default function Home() {
return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<div className="flex flex-col items-center justify-center">
<h1 className="text-4xl font-bold">
Welcome to the Unkey + Auth Provider
</h1>
<p className="text-xl mt-4">
This is a demo of how you can use Unkey to secure your API with an
Auth Provider.
</p>
<UnkeyElements />
</div>
</main>
);
}
Our server action will allow us to create a key in our application in the keys
folder, add a create.ts
file. Then, in this file, we will use our auth provider and Unkey together.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
"use server";
import { auth } from "@clerk/nextjs";
import { Unkey } from "@unkey/api";
export async function create(formData: FormData) {
"use server";
const { userId } = auth();
if (!userId) {
return null;
}
const token = process.env.UNKEY_ROOT_KEY;
const apiId = process.env.UNKEY_API_ID;
if (!token || !apiId) {
return null;
}
const name = (formData.get("name") as string) ?? "My Awesome API";
const unkey = new Unkey({ token });
const key = await unkey.keys.create({
name: name,
ownerId: userId,
apiId,
});
return { key: key.result };
}
The key creation server action will check if the user is authenticated. If they are, it will create a key with the name provided and the user's ID. If the user isn't authenticated, it will return null. We now have a way to track which user owns which key.
Now, we have a way to create a key to add to our client component. In our client.tsx
file, we will add our server action import and useState
to handle the returned key.
1
2
import { create } from "./create";
import { useState } from "react";
Now we have the imports, we can add an onCreate
function to our component that will call our server action. We can now call that function when the form is submitted.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const UnkeyElements = () => {
const [key, setKey] = useState<string>('')
async function onCreate(formData: FormData) {
const res = await create(formData)
if(res) setKey(res.key?.key);
}
...
<form action={onCreate}>
<CardContent>
<div className="grid w-full items-center gap-4">
<div className="flex flex-col space-y-1.5">
<Label htmlFor="name">Give your key a name</Label>
<Input name="name" placeholder="Key for next big thing" />
</div>
</div>
</CardContent>
<CardFooter className="flex justify-between">
<Button type="submit">Create Key</Button>
</CardFooter>
</form>
...
When we submit the form, we will get a key back that we can use to make requests to our API. If you give this a test, you can log out the key in the console to see it.
In this demo, we can display the key to our user and then show a button to request our API. Let's update our component to do this. Underneath our original card, we will add a new card that will display the key to the user. So we can copy the card component and update the content.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{key && key.length > 0 && (
<>
<Card className="w-[350px] mt-8">
<CardHeader>
<CardTitle>API Key</CardTitle>
<CardDescription>Here is your API key. Keep it safe!</CardDescription>
</CardHeader>
<CardContent>
<div className="grid w-full items-center gap-4">
<div className="flex flex-col space-y-1.5">
<Label htmlFor="name">API Key</Label>
<Input name="name" value={key} />
</div>
</div>
</CardContent>
</Card>
The final step is to request our API. We are going to use the key we created to request our API. We are going to use Next.js router handler to make the request. We will create a new folder called api
and inside that a folder called secret
and a file called route.ts
. Inside this file, we are going to add the following code.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { verifyKey } from "@unkey/api";
import { NextResponse } from "next/server";
export async function GET(request: Request) {
const header = request.headers.get("Authorization");
if (!header) {
return new Response("No Authorization header", { status: 401 });
}
const token = header.replace("Bearer ", "");
const { result, error } = await verifyKey(token);
if (error) {
console.error(error.message);
return new Response("Internal Server Error", { status: 500 });
}
if (!result.valid) {
// do not grant access
return new Response("Unauthorized", { status: 401 });
}
// process request
return NextResponse.json({ result });
}
Unkey makes it easy to make business decisions. We can verify the key and then return a response based on the result. In this example, we will return a 401 if the key is invalid. If the key is valid, we are going to return the results.
Technically speaking, you could make a request from your favorite API client using http://localhost:3000/api/secret
and add the Authorization header with the key. But we will add a button to our client component to make the request and display the response to keep everything in one place.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<Card className="w-[350px] mt-8">
<CardHeader>
<CardTitle>Get Secret Data </CardTitle>
<CardDescription>Retrieve secret data from API </CardDescription>
</CardHeader>
<CardContent>
<Button onClick={getData} variant="outline">
Get Data
</Button>
<div className="grid w-full items-center gap-4">
<div className="flex flex-col space-y-1.5">
<Label htmlFor="name">Secret Data</Label>
<Input name="name" value={JSON.stringify(secret)} />
</div>
</div>
</CardContent>
</Card>
We need a function called getData
to request our API. We will use the fetch and add a state to hold the returned data.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const [key, setKey] = useState<string>("");
const [secret, setSecret] = useState<string>("");
async function onCreate(formData: FormData) {
const res = await create(formData);
if (res) setKey(res.key?.key);
}
const getData = async () => {
const res = await fetch(`/api/secret`, {
headers: {
Authorization: `Bearer ${key}`,
},
});
const data = await res.json();
setSecret(data.result);
};
When we click the button, we will request our API and display the response. The entire component file should look like this.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
"use client";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { create } from "./create";
import { useState } from "react";
const UnkeyElements = () => {
const [key, setKey] = useState<string>("");
const [secret, setSecret] = useState<string>("");
async function onCreate(formData: FormData) {
const res = await create(formData);
if (res) setKey(res.key?.key);
}
const getData = async () => {
const res = await fetch(`/api/secret`, {
headers: {
Authorization: `Bearer ${key}`,
},
});
const data = await res.json();
setSecret(data.result);
};
return (
<div className="mt-8">
<Card className="w-[350px]">
<CardHeader>
<CardTitle>Create API Key</CardTitle>
<CardDescription>
Create your API key so you can interact with our API.
</CardDescription>
</CardHeader>
<form action={onCreate}>
<CardContent>
<div className="grid w-full items-center gap-4">
<div className="flex flex-col space-y-1.5">
<Label htmlFor="name">API Key Name</Label>
<Input name="name" placeholder="My Awesome API " />
</div>
</div>
</CardContent>
<CardFooter className="flex justify-between">
<Button type="submit">Create Key</Button>
</CardFooter>
</form>
</Card>
{key && key.length > 0 && (
<>
<Card className="w-[350px] mt-8">
<CardHeader>
<CardTitle>API Key</CardTitle>
<CardDescription>
Here is your API key. Keep it safe!
</CardDescription>
</CardHeader>
<CardContent>
<div className="grid w-full items-center gap-4">
<div className="flex flex-col space-y-1.5">
<Label htmlFor="name">API Key</Label>
<Input name="name" value={key} />
</div>
</div>
</CardContent>
</Card>
<Card className="w-[350px] mt-8">
<CardHeader>
<CardTitle>Get Secret Data </CardTitle>
<CardDescription>Retrieve secret data from API </CardDescription>
</CardHeader>
<CardContent>
<Button onClick={getData} variant="outline">
Get Data
</Button>
<div className="grid w-full items-center gap-4">
<div className="flex flex-col space-y-1.5">
<Label htmlFor="name">Secret Data</Label>
<Input name="name" value={JSON.stringify(secret)} />
</div>
</div>
</CardContent>
</Card>
</>
)}
</div>
);
};
export { UnkeyElements };
The response from the API will look like this, and as you can see, we now associate the API key to a user.
1
2
3
4
{
"valid": true,
"ownerId": "user_2Vi5Z5c9tcZd6dfbgV6tEWDQYVf"
}
In the dashboard for Unkey, you can see the key and the owner ID and the name associated with it
In this post, we have covered how to use Unkey with an auth provider to secure your API. We have covered how to associate a user with a key and then use that key to request our API. You can check out the code for this project here: Example
2500 verifications and 100K successful rate‑limited requests per month. No CC required.