Using Unkey with your Authentication Provider

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.

What is Unkey?

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:

  • Per key rate limiting
  • Limited usage keys
  • Time-based keys
  • Per key analytics

Setting up our project

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.

Create a Next.js application and install dependencies.

The first thing we want to do is create our Next.js application and install our dependencies.

1npx create-next-app@latest unkey-with-auth

We need Unkey's typescript library and Clerk's next.js package for dependencies.

1npm 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.

1npx shadcn-ui@latest init
2
3npx 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.

1NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_xxxxxxxxxxxxxx
2CLERK_SECRET_KEY=sk_test_xxxxxxxxxxxxxx
3UNKEY_API_ID=api_xxxxxxxxxxxxxx
4UNKEY_ROOT_KEY=unkey_xxxxxxxxxxxxxx

Clerk setup (optional)

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.

Root layout

1import "./globals.css";
2import { Inter } from "next/font/google";
3import { ClerkProvider } from "@clerk/nextjs";
4
5const inter = Inter({ subsets: ["latin"] });
6
7export const metadata: Metadata = {
8  title: "Create Next App",
9
10  description: "Generated by create next app",
11};
12
13export default function RootLayout({
14  children,
15}: {
16  children: React.ReactNode;
17}) {
18  return (
19    <ClerkProvider>
20      <html lang="en">
21        <body>{children}</body>
22      </html>
23    </ClerkProvider>
24  );
25}

Middleware.ts

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.

1import { authMiddleware } from "@clerk/nextjs";
2
3export default authMiddleware({
4  publicRoutes: "/api/secret",
5});
6
7export const config = {
8  matcher: ["/((?!.+\\.[\\w]+$|_next).*)", "/", "/(api|trpc)(.*)"],
9};

Creating a key associated with a user.

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.

Create our Unkey client component

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"use client";
2import { Button } from "@/components/ui/button";
3import {
4  Card,
5  CardContent,
6  CardDescription,
7  CardFooter,
8  CardHeader,
9  CardTitle,
10} from "@/components/ui/card";
11import { Input } from "@/components/ui/input";
12import { 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.

1const UnkeyElements = () => {
2  return (
3    <div className="mt-8">
4      <Card className="w-[350px]">
5        <CardHeader>
6          <CardTitle>Create API Key</CardTitle>
7          <CardDescription>
8            Create your API key so you can interact with our API.
9          </CardDescription>
10        </CardHeader>
11
12        <form>
13          <CardContent>
14            <div className="grid w-full items-center gap-4">
15              <div className="flex flex-col space-y-1.5">
16                <Label htmlFor="name">Give your key a name</Label>
17                <Input name="name" placeholder="Key for next big thing" />
18              </div>
19            </div>
20          </CardContent>
21          <CardFooter className="flex justify-between">
22            <Button type="submit">Create Key</Button>
23          </CardFooter>
24        </form>
25      </Card>
26    </div>
27  );
28};
29export { UnkeyElements };

Make sure to import this into your page.tsx file and add it to the page.

1import { UnkeyElements } from "./keys/client";
2
3export default function Home() {
4  return (
5    <main className="flex min-h-screen flex-col items-center justify-between p-24">
6      <div className="flex flex-col items-center justify-center">
7        <h1 className="text-4xl font-bold">
8          Welcome to the Unkey + Auth Provider
9        </h1>
10        <p className="text-xl mt-4">
11          This is a demo of how you can use Unkey to secure your API with an
12          Auth Provider.
13        </p>
14        <UnkeyElements />
15      </div>
16    </main>
17  );
18}

Adding a server action

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"use server";
2import { auth } from "@clerk/nextjs";
3import { Unkey } from "@unkey/api";
4export async function create(formData: FormData) {
5  "use server";
6  const { userId } = auth();
7  if (!userId) {
8    return null;
9  }
10  const token = process.env.UNKEY_ROOT_KEY;
11  const apiId = process.env.UNKEY_API_ID;
12
13  if (!token || !apiId) {
14    return null;
15  }
16
17  const name = (formData.get("name") as string) ?? "My Awesome API";
18  const unkey = new Unkey({ token });
19  const key = await unkey.keys.create({
20    name: name,
21    ownerId: userId,
22    apiId,
23  });
24  return { key: key.result };
25}

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.

Adding our creation server action to our client component

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.

1import { create } from "./create";
2import { 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.

1const UnkeyElements = () => {
2    const [key, setKey] = useState<string>('')
3    async function onCreate(formData: FormData) {
4        const res = await create(formData)
5        if(res) setKey(res.key?.key);
6    }
7    ...
8    <form action={onCreate}>
9          <CardContent>
10            <div className="grid w-full items-center gap-4">
11              <div className="flex flex-col space-y-1.5">
12                <Label htmlFor="name">Give your key a name</Label>
13                <Input name="name" placeholder="Key for next big thing" />
14              </div>
15            </div>
16          </CardContent>
17          <CardFooter className="flex justify-between">
18            <Button type="submit">Create Key</Button>
19          </CardFooter>
20        </form>
21    ...

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.

Displaying the key to the user

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{key && key.length > 0 && (
2                <>
3                <Card className="w-[350px] mt-8">
4                <CardHeader>
5                    <CardTitle>API Key</CardTitle>
6                    <CardDescription>Here is your API key. Keep it safe!</CardDescription>
7                </CardHeader>
8                <CardContent>
9                    <div className="grid w-full items-center gap-4">
10                        <div className="flex flex-col space-y-1.5">
11                            <Label htmlFor="name">API Key</Label>
12                            <Input name="name" value={key} />
13                        </div>
14                    </div>
15                </CardContent>
16            </Card>

Making a request to our API

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.

1import { verifyKey } from "@unkey/api";
2import { NextResponse } from "next/server";
3export async function GET(request: Request) {
4  const header = request.headers.get("Authorization");
5  if (!header) {
6    return new Response("No Authorization header", { status: 401 });
7  }
8  const token = header.replace("Bearer ", "");
9  const { result, error } = await verifyKey(token);
10
11  if (error) {
12    console.error(error.message);
13    return new Response("Internal Server Error", { status: 500 });
14  }
15
16  if (!result.valid) {
17    // do not grant access
18    return new Response("Unauthorized", { status: 401 });
19  }
20
21  // process request
22  return NextResponse.json({ result });
23}

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.

Updating our client component

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<Card className="w-[350px] mt-8">
2  <CardHeader>
3    <CardTitle>Get Secret Data </CardTitle>
4    <CardDescription>Retrieve secret data from API </CardDescription>
5  </CardHeader>
6  <CardContent>
7    <Button onClick={getData} variant="outline">
8      Get Data
9    </Button>
10    <div className="grid w-full items-center gap-4">
11      <div className="flex flex-col space-y-1.5">
12        <Label htmlFor="name">Secret Data</Label>
13        <Input name="name" value={JSON.stringify(secret)} />
14      </div>
15    </div>
16  </CardContent>
17</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.

1const [key, setKey] = useState<string>("");
2const [secret, setSecret] = useState<string>("");
3async function onCreate(formData: FormData) {
4  const res = await create(formData);
5  if (res) setKey(res.key?.key);
6}
7const getData = async () => {
8  const res = await fetch(`/api/secret`, {
9    headers: {
10      Authorization: `Bearer ${key}`,
11    },
12  });
13  const data = await res.json();
14  setSecret(data.result);
15};

When we click the button, we will request our API and display the response. The entire component file should look like this.

1"use client";
2
3import { Button } from "@/components/ui/button";
4import {
5  Card,
6  CardContent,
7  CardDescription,
8  CardFooter,
9  CardHeader,
10  CardTitle,
11} from "@/components/ui/card";
12import { Input } from "@/components/ui/input";
13import { Label } from "@/components/ui/label";
14import { create } from "./create";
15import { useState } from "react";
16
17const UnkeyElements = () => {
18  const [key, setKey] = useState<string>("");
19  const [secret, setSecret] = useState<string>("");
20  async function onCreate(formData: FormData) {
21    const res = await create(formData);
22    if (res) setKey(res.key?.key);
23  }
24  const getData = async () => {
25    const res = await fetch(`/api/secret`, {
26      headers: {
27        Authorization: `Bearer ${key}`,
28      },
29    });
30    const data = await res.json();
31    setSecret(data.result);
32  };
33  return (
34    <div className="mt-8">
35      <Card className="w-[350px]">
36        <CardHeader>
37          <CardTitle>Create API Key</CardTitle>
38          <CardDescription>
39            Create your API key so you can interact with our API.
40          </CardDescription>
41        </CardHeader>
42        <form action={onCreate}>
43          <CardContent>
44            <div className="grid w-full items-center gap-4">
45              <div className="flex flex-col space-y-1.5">
46                <Label htmlFor="name">API Key Name</Label>
47                <Input name="name" placeholder="My Awesome API " />
48              </div>
49            </div>
50          </CardContent>
51          <CardFooter className="flex justify-between">
52            <Button type="submit">Create Key</Button>
53          </CardFooter>
54        </form>
55      </Card>
56      {key && key.length > 0 && (
57        <>
58          <Card className="w-[350px] mt-8">
59            <CardHeader>
60              <CardTitle>API Key</CardTitle>
61              <CardDescription>
62                Here is your API key. Keep it safe!
63              </CardDescription>
64            </CardHeader>
65            <CardContent>
66              <div className="grid w-full items-center gap-4">
67                <div className="flex flex-col space-y-1.5">
68                  <Label htmlFor="name">API Key</Label>
69                  <Input name="name" value={key} />
70                </div>
71              </div>
72            </CardContent>
73          </Card>
74          <Card className="w-[350px] mt-8">
75            <CardHeader>
76              <CardTitle>Get Secret Data </CardTitle>
77              <CardDescription>Retrieve secret data from API </CardDescription>
78            </CardHeader>
79            <CardContent>
80              <Button onClick={getData} variant="outline">
81                Get Data
82              </Button>
83              <div className="grid w-full items-center gap-4">
84                <div className="flex flex-col space-y-1.5">
85                  <Label htmlFor="name">Secret Data</Label>
86                  <Input name="name" value={JSON.stringify(secret)} />
87                </div>
88              </div>
89            </CardContent>
90          </Card>
91        </>
92      )}
93    </div>
94  );
95};
96
97export { 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  "valid": true,
3  "ownerId": "user_2Vi5Z5c9tcZd6dfbgV6tEWDQYVf"
4}

In the dashboard for Unkey, you can see the key and the owner ID and the name associated with it

Conclusion

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

Protect your API.
Start today.

2500 verifications and 100K successful rate‑limited requests per month. No CC required.