Skip to content

Commit

Permalink
docs(examples): Added example apps for Clerk integration (#244)
Browse files Browse the repository at this point in the history
Adds example apps for integrating Arcjet with Clerk.
  • Loading branch information
davidmytton authored Feb 20, 2024
1 parent f495f1b commit 95c7abd
Show file tree
Hide file tree
Showing 40 changed files with 11,698 additions and 10 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,6 @@ dist

# One-off scripts
*.sh

# macOS
.DS_Store
20 changes: 10 additions & 10 deletions .trunk/trunk.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
# To learn more about the format of this file, see https://docs.trunk.io/reference/trunk-yaml
version: 0.1
cli:
version: 1.19.0
version: 1.20.0
# Trunk provides extensibility via plugins. (https://docs.trunk.io/plugins)
plugins:
sources:
- id: trunk
ref: v1.4.2
ref: v1.4.3
uri: https://github.com/trunk-io/plugins
# Many linters and tools depend on runtimes - configure them here. (https://docs.trunk.io/runtimes)
runtimes:
Expand All @@ -20,17 +20,17 @@ lint:
enabled:
- [email protected]
- [email protected]
- trivy@0.48.2
- yamllint@1.33.0
- semgrep@1.55.2
- [email protected].1
- trivy@0.49.1
- yamllint@1.35.1
- semgrep@1.61.1
- [email protected].2
- [email protected]
- git-diff-check
- markdownlint@0.38.0
- osv-scanner@1.5.0
- prettier@3.1.1
- markdownlint@0.39.0
- osv-scanner@1.6.2
- prettier@3.2.5
- [email protected]
- trufflehog@3.63.7
- trufflehog@3.67.6
disabled:
# tfsec and checkov are replaced by Trivy
- tfsec
Expand Down
5 changes: 5 additions & 0 deletions examples/nextjs-14-clerk-rl/.env.local.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Get your Arcjet key from https://app.arcjet.com
ARCJET_KEY=
# Get your Clerk keys from https://clerk.com/docs/quickstarts/nextjs
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=
CLERK_SECRET_KEY=
3 changes: 3 additions & 0 deletions examples/nextjs-14-clerk-rl/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}
36 changes: 36 additions & 0 deletions examples/nextjs-14-clerk-rl/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
1 change: 1 addition & 0 deletions examples/nextjs-14-clerk-rl/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
omit=optional
45 changes: 45 additions & 0 deletions examples/nextjs-14-clerk-rl/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<a href="https://arcjet.com" target="_arcjet-home">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://arcjet.com/arcjet-logo-minimal-dark-mark-all.svg">
<img src="https://arcjet.com/arcjet-logo-minimal-light-mark-all.svg" alt="Arcjet Logo" height="128" width="auto">
</picture>
</a>

# Arcjet Rate Limit / Clerk Authentication Example

This example shows how to use an Arcjet rate limit with a user ID from [Clerk
authentication and Next.js](https://clerk.com/docs/quickstarts/nextjs).

It sets up 2 API routes:

* `/api/public` does not require authentication and has a low rate limit based
on the user IP address.
* `/api/private` uses Clerk authentication and has a higher rate limit based on
the Clerk user ID.

## How to use

1. From the root of the project, install the SDK dependencies.

```bash
npm ci
```

2. Enter this directory and install the example's dependencies.

```bash
cd examples/nextjs-14-clerk-rl
npm ci
```

3. Rename `.env.local.example` to `.env.local` and add your Arcjet and Clerk
keys.

4. Start the dev server.

```bash
npm run dev
```

5. Visit `http://localhost:3000`.
6. Try the different routes linked on the page.
59 changes: 59 additions & 0 deletions examples/nextjs-14-clerk-rl/app/api/private/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* Testing this route requires a Clerk user JWT token passed in the
* Authorization header.
*
* `curl -v http://localhost:3000/api/private -H "Authorization: Bearer TOKENHERE"`
*
* Get the token from the /api/token route.
*/
import arcjet, { tokenBucket } from "@arcjet/next";
import { NextResponse } from "next/server";
import { currentUser } from "@clerk/nextjs";

// The arcjet instance is created outside of the handler
const aj = arcjet({
// Get your site key from https://app.arcjet.com
// and set it as an environment variable rather than hard coding.
// See: https://nextjs.org/docs/app/building-your-application/configuring/environment-variables
key: process.env.ARCJET_KEY!,
rules: [
// Create a token bucket rate limit. Fixed and sliding window rate limits
// are also supported. See https://docs.arcjet.com/rate-limiting/algorithms
tokenBucket({
mode: "LIVE", // will block requests at the limit. Use "DRY_RUN" to log only
// Rate limit based on the Clerk userId
// See https://clerk.com/docs/references/nextjs/authentication-object
// See https://docs.arcjet.com/rate-limiting/configuration#characteristics
characteristics: ["userId"],
refillRate: 5, // refill 5 tokens per interval
interval: 10, // refill every 10 seconds
capacity: 10, // bucket maximum capacity of 10 tokens
}),
],
});

export async function GET(req: Request) {
// Get the current user from Clerk
// See https://clerk.com/docs/references/nextjs/current-user
const user = await currentUser();
if (!user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

// Deduct 5 tokens from the user's bucket
const decision = await aj.protect(req, { userId: user.id, requested: 5 });

if (decision.isDenied()) {
return NextResponse.json(
{
error: "Too Many Requests",
reason: decision.reason,
},
{
status: 429,
}
);
}

return NextResponse.json({ message: "Hello World" });
}
42 changes: 42 additions & 0 deletions examples/nextjs-14-clerk-rl/app/api/public/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import arcjet, { tokenBucket } from "@arcjet/next";
import { NextResponse } from "next/server";

// The arcjet instance is created outside of the handler
const aj = arcjet({
// Get your site key from https://app.arcjet.com
// and set it as an environment variable rather than hard coding.
// See: https://nextjs.org/docs/app/building-your-application/configuring/environment-variables
key: process.env.ARCJET_KEY!,
rules: [
// Create a token bucket rate limit. Fixed and sliding window rate limits
// are also supported. See https://docs.arcjet.com/rate-limiting/algorithms
tokenBucket({
mode: "LIVE", // will block requests at the limit. Use "DRY_RUN" to log only
// Use the built in ip.src characteristic
// See https://docs.arcjet.com/rate-limiting/configuration#characteristics
characteristics: ["ip.src"],
refillRate: 5, // refill 5 tokens per interval
interval: 10, // refill every 10 seconds
capacity: 10, // bucket maximum capacity of 10 tokens
}),
],
});

export async function GET(req: Request) {
// Deduct 5 tokens from the bucket
const decision = await aj.protect(req, { requested: 5 });

if (decision.isDenied()) {
return NextResponse.json(
{
error: "Too Many Requests",
reason: decision.reason,
},
{
status: 429,
}
);
}

return NextResponse.json({ message: "Hello World" });
}
21 changes: 21 additions & 0 deletions examples/nextjs-14-clerk-rl/app/api/token/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* This retrieves the Clerk JWT token for the current user so you can test the
* private API route.
*/
import { auth } from "@clerk/nextjs";

export async function GET(req: Request) {
const { userId, getToken } = auth();

if (!userId) {
return new Response("Unauthorized", { status: 401 });
}

try {
const token = await getToken();

return Response.json({ token });
} catch (error) {
return Response.json(error);
}
}
Binary file added examples/nextjs-14-clerk-rl/app/favicon.ico
Binary file not shown.
95 changes: 95 additions & 0 deletions examples/nextjs-14-clerk-rl/app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
:root {
--max-width: 1100px;
--border-radius: 12px;
--font-mono: ui-monospace, Menlo, Monaco, "Cascadia Mono", "Segoe UI Mono",
"Roboto Mono", "Oxygen Mono", "Ubuntu Monospace", "Source Code Pro",
"Fira Mono", "Droid Sans Mono", "Courier New", monospace;

--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;

--primary-glow: conic-gradient(from 180deg at 50% 50%,
#16abff33 0deg,
#0885ff33 55deg,
#54d6ff33 120deg,
#0071ff33 160deg,
transparent 360deg);
--secondary-glow: radial-gradient(rgba(255, 255, 255, 1),
rgba(255, 255, 255, 0));

--tile-start-rgb: 239, 245, 249;
--tile-end-rgb: 228, 232, 233;
--tile-border: conic-gradient(#00000080,
#00000040,
#00000030,
#00000020,
#00000010,
#00000010,
#00000080);

--callout-rgb: 238, 240, 241;
--callout-border-rgb: 172, 175, 176;
--card-rgb: 180, 185, 188;
--card-border-rgb: 131, 134, 135;
}

@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;

--primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0));
--secondary-glow: linear-gradient(to bottom right,
rgba(1, 65, 255, 0),
rgba(1, 65, 255, 0),
rgba(1, 65, 255, 0.3));

--tile-start-rgb: 2, 13, 46;
--tile-end-rgb: 2, 5, 19;
--tile-border: conic-gradient(#ffffff80,
#ffffff40,
#ffffff30,
#ffffff20,
#ffffff10,
#ffffff10,
#ffffff80);

--callout-rgb: 20, 20, 20;
--callout-border-rgb: 108, 108, 108;
--card-rgb: 100, 100, 100;
--card-border-rgb: 200, 200, 200;
}
}

* {
box-sizing: border-box;
}

html,
body {
max-width: 100vw;
overflow-x: hidden;
}

body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(to bottom,
transparent,
rgb(var(--background-end-rgb))) rgb(var(--background-start-rgb));
}

a {
color: inherit;
}

a:hover {
text-decoration: none;
}

@media (prefers-color-scheme: dark) {
html {
color-scheme: dark;
}
}
25 changes: 25 additions & 0 deletions examples/nextjs-14-clerk-rl/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { ClerkProvider } from "@clerk/nextjs";
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";

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 className={inter.className}>{children}</body>
</html>
</ClerkProvider>
);
}
Loading

0 comments on commit 95c7abd

Please sign in to comment.