@tozielinski/next-upstash-nonce
v1.4.2
Published
Create, store, verify and delete nonces in Redis by Upstash for Next.js
Maintainers
Readme
next-upstash-nonce
Create, store, verify and delete nonces in Redis by Upstash for Next.js and/or secure your API endpoints with a rate limiter
Quick Start
Install the package:
npm install @tozielinski/next-upstash-nonceCreate database
Create a new redis database on upstash
Create a NonceManager Instance
import { NonceManager } from '@tozielinski/next-upstash-nonce'
import { Redis } from "@upstash/redis";
const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL as string,
token: process.env.UPSTASH_REDIS_REST_TOKEN as string,
});
export const nonceManager = new NonceManager(redis, {ttlSeconds: 60* 5});Create a ServerAction, to use it from client side
'use server'
import {nonceManager} from "@/[wherever you store your nonceManager instance]";
export async function createNonce(): Promise<string> {
return await nonceManager.create();
}Secure your API endpoint with a Nonce
'use server'
import {NextResponse} from "next/server";
import {nonceManager} from "@/[wherever you store your nonceManager instance]";
export async function POST(req: Request) {
const nonce: boolean = req.headers.get("x-api-nonce");
if (!nonce) {
return NextResponse.json(
{ error: "Missing nonce", valid: false },
{ status: 401 }
);
}
const valid = await nonceManager.verifyAndDelete(nonce);
// valid will be true if nonce was found and deleted
// false if nonce was not found or expired
return NextResponse.json({nonce: nonce, valid: valid});
}or more simple
'use server'
import {NextResponse} from "next/server";
import {nonceManager} from "@/[wherever you store your nonceManager instance]";
import {NonceCheckResult} from "@tozielinski/next-upstash-nonce";
export async function POST(req: Request) {
const result: NonceCheckResult = await nonceManager.verifyAndDeleteNonceFromRequest(req);
// result will be {nonce: string, valid: true} or
// {valid false, reason: string, response: NextResponse}
// if nonce was not found or expired:
//
// export type NonceCheckResult = | {
// valid: true;
// nonce: string
// } | {
// valid: false;
// reason: 'missing-header' | 'invalid-or-expired';
// response: Response
// };
return NextResponse.json({nonce: result.nonce, valid: result.valid});
}Use it in your client side
'use client'
import {useState} from "react";
import {createNonce} from "@/[wherever you store your server action]";
export default function NonceSecuredComponent() {
const [running, setRunning] = useState(false);
const [message, setMessage] = useState('');
const handleClick = async () => {
if (running) return;
setRunning(true);
setMessage("Starting SSA...");
const nonce = await createNonce();
const res = await fetch('/api/[name of your API endpoint]', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Nonce': nonce,
},
body: JSON.stringify({success: true}),
});
if (res.ok) {
const data = await res.json();
setMessage(`Nonce: ${data.nonce} Valid: ${data.valid}` || "No nonce received");
setRunning(false);
} else
setMessage(res.statusText);
}
return (
<div>
<button
onClick={handleClick}
disabled={running}
className="px-6 py-3 rounded-xl bg-blue-600 text-white disabled:opacity-50"
>
{running ? "Running..." : "Start SSA"}
</button>
<p>{message}</p>
</div>
)
}Secure your API endpoint with a Rate Limiter
'use server'
import {NextResponse} from "next/server";
import {nonceManager} from "@/[wherever you store your nonceManager instance]";
import {RateLimitResult} from "@tozielinski/next-upstash-nonce";
export async function POST(req: Request) {
const rateLimiter: RateLimitResult = await nonceManager.rateLimiter(req!);
// result will be {valid: true, ip: string, requests: number} or
// {valid false, ip:string, requests: number, reason: string, response: NextResponse}
// if rate limit was reached or broken:
//
// export type RateLimitResult = | {
// valid: true;
// ip: string;
// requests: number;
// } | {
// valid: false;
// ip: string;
// requests: number;
// reason: `too-many-requests: ${number}`;
// response: Response;
// };
return NextResponse.json({valid: result.valid, ip: result.ip, requests: result.requests});
}