Quick Start
AuthKit is a self-hosted authentication service. You get an API server, a developer dashboard, and an SDK — all under your own infrastructure.
This guide walks you from zero to a protected Next.js route in under 5 minutes.
# 1. Install the SDK npm install @paribeshn/authkit # 2. Create an account at your AuthKit instance and make an app # 3. Copy your Publishable Key and Secret Key from the dashboard
// app/layout.tsx "use client"; import { AuthProvider } from "@paribeshn/authkit/react"; export default function RootLayout({ children }) { return ( <html> <body> <AuthProvider publishableKey={process.env.NEXT_PUBLIC_PUBLISHABLE_KEY} baseUrl={process.env.NEXT_PUBLIC_API_URL} > {children} </AuthProvider> </body> </html> ); }
// middleware.ts import { authkitMiddleware } from "@paribeshn/authkit/nextjs"; export default authkitMiddleware({ secretKey: process.env.AUTH_SECRET_KEY!, signInUrl: "/sign-in", publicRoutes: ["/sign-in", "/sign-up", "/forgot-password"], }); export const config = { matcher: ["/((?!_next|favicon.ico).*)"], };
// app/dashboard/page.tsx — protected automatically by middleware "use client"; import { useAuth } from "@paribeshn/authkit/react"; export default function Dashboard() { const { user, signOut } = useAuth(); return ( <div> <p>Hello, {user?.email}</p> <button onClick={signOut}>Sign out</button> </div> ); }
Installation
Install the SDK from npm. It ships ESM and CJS with full TypeScript types.
npm install @paribeshn/authkit
The package has three entrypoints:
| Prop | Type | Description |
|---|---|---|
| @paribeshn/authkit/react | module | AuthProvider, useAuth, useUser, guards, pre-built UI components |
| @paribeshn/authkit/nextjs | module | authkitMiddleware, getUserFromRequest |
| @paribeshn/authkit/server | module | verifyToken, authMiddleware, verifyWebhook |
Environment variables
# .env.local NEXT_PUBLIC_PUBLISHABLE_KEY=pk_live_... # from dashboard — safe to expose NEXT_PUBLIC_API_URL=https://your-api.com # your AuthKit backend URL AUTH_SECRET_KEY=sk_live_... # secret key — server only, never expose
AuthProvider
AuthProvider is the React context that powers all hooks and guards. Wrap your root layout with it.
import { AuthProvider } from "@paribeshn/authkit/react"; <AuthProvider publishableKey="pk_live_..." baseUrl="https://your-authkit-api.com" > {children} </AuthProvider>
| Prop | Type | Description |
|---|---|---|
| publishableKey | string | Your app's publishable key from the dashboard. |
| baseUrl | string | URL of your AuthKit backend (no trailing slash). |
AuthProvider manages token refresh automatically in the background. Tokens are stored in sessionStorage so they survive hot-reloads without re-auth.
useAuth & useUser
These hooks give you auth state and actions anywhere inside AuthProvider.
useAuth
import { useAuth } from "@paribeshn/authkit/react"; function MyComponent() { const { isLoaded, // false while initial token check is in progress isSignedIn, // true when user has a valid session user, // AppUser | null accessToken, // current JWT access token signOut, // () => Promise<void> } = useAuth(); if (!isLoaded) return <Spinner />; if (!isSignedIn) return <p>Not signed in</p>; return <p>Hello, {user.email}</p>; }
useUser
import { useUser } from "@paribeshn/authkit/react"; function Profile() { const { user, isLoaded } = useUser(); return ( <div> <p>{user?.firstName} {user?.lastName}</p> <p>{user?.email}</p> <p>Verified: {user?.emailVerified ? "Yes" : "No"}</p> </div> ); }
| Prop | Type | Description |
|---|---|---|
| id | string | Unique user ID |
| string | null | User's email address | |
| username | string | null | Username (if set) |
| firstName | string | null | First name |
| lastName | string | null | Last name |
| emailVerified | boolean | Whether the email is verified |
| banned | boolean | Whether this user is banned |
| publicMetadata | object | App-controlled public metadata |
| lastSignInAt | string | null | ISO timestamp of last sign-in |
Guards
Use guards to conditionally render content based on auth state — no middleware needed for client-side gating.
AuthGuard
Renders fallback (defaults to null) when the user is not signed in.
import { AuthGuard } from "@paribeshn/authkit/react"; <AuthGuard fallback={<p>Please sign in.</p>}> <PrivateContent /> </AuthGuard>
GuestGuard
Renders fallback when the user IS signed in. Useful for sign-in/up pages.
import { GuestGuard } from "@paribeshn/authkit/react"; <GuestGuard fallback={<Redirect to="/dashboard" />}> <SignInForm /> </GuestGuard>
Pre-built Components
Drop-in forms that handle sign-up, sign-in, and password reset for your app users. They call your AuthKit backend automatically.
import { SignIn, SignUp, ForgotPassword, ResetPassword, } from "@paribeshn/authkit/react"; // Sign-in page export default function SignInPage() { return ( <SignIn onSuccess={() => router.push("/dashboard")} signUpUrl="/sign-up" forgotPasswordUrl="/forgot-password" /> ); } // Sign-up page export default function SignUpPage() { return ( <SignUp onSuccess={() => router.push("/dashboard")} signInUrl="/sign-in" /> ); } // Forgot password page export default function ForgotPage() { return <ForgotPassword signInUrl="/sign-in" />; } // Reset password page — reads token from ?token=... query param export default function ResetPage() { return ( <ResetPassword onSuccess={() => router.push("/sign-in")} /> ); }
Next.js Middleware
authkitMiddleware
Protects routes at the edge. Redirects unauthenticated requests to signInUrl. Verified requests pass through with the decoded session injected into headers.
import { authkitMiddleware } from "@paribeshn/authkit/nextjs"; export default authkitMiddleware({ secretKey: process.env.AUTH_SECRET_KEY!, signInUrl: "/sign-in", publicRoutes: [ "/sign-in", "/sign-up", "/forgot-password", "/reset-password", "/auth/callback", ], }); export const config = { matcher: ["/((?!_next|favicon.ico|.*\.png$).*)"], };
| Prop | Type | Description |
|---|---|---|
| secretKey | string | Your app's secret key from the dashboard. |
| signInUrl | string | Path to redirect unauthenticated users to. |
| publicRoutes | string[] | Routes that bypass authentication. |
getUserFromRequest
Extract the verified user from a Next.js request inside Server Components or Route Handlers.
import { getUserFromRequest } from "@paribeshn/authkit/nextjs"; import { NextRequest } from "next/server"; // Route handler export async function GET(req: NextRequest) { const user = await getUserFromRequest(req, { secretKey: process.env.AUTH_SECRET_KEY!, }); if (!user) return new Response("Unauthorized", { status: 401 }); return Response.json({ userId: user.sub }); }
Server SDK
verifyToken
Verify a JWT access token in any Node.js context (Express, Fastify, etc.).
import { verifyToken } from "@paribeshn/authkit/server"; const payload = await verifyToken(token, secretKey); // payload.sub — user ID // payload.appId — application ID // payload.exp — expiry timestamp
authMiddleware (Express)
Express middleware that verifies the Authorization: Bearer header and attaches req.auth to the request.
import express from "express"; import { authMiddleware } from "@paribeshn/authkit/server"; const app = express(); app.use( "/api/protected", authMiddleware({ secretKey: process.env.AUTH_SECRET_KEY! }), (req, res) => { // req.auth.sub — verified user ID res.json({ userId: req.auth.sub }); } );
verifyWebhook
Verify that a webhook payload came from your AuthKit server. Uses HMAC-SHA256.
import { verifyWebhook } from "@paribeshn/authkit/server"; // Express example app.post("/webhooks/auth", express.raw({ type: "*/*" }), (req, res) => { const signature = req.headers["x-authkit-signature"] as string; const event = verifyWebhook( req.body, // raw Buffer or string signature, process.env.WEBHOOK_SECRET! ); // event.type — "user.created" | "user.signed_in" | ... // event.data — user object switch (event.type) { case "user.created": console.log("New user:", event.data.email); break; case "user.deleted": console.log("Deleted:", event.data.id); break; } res.json({ received: true }); });
OAuth
Support Google and GitHub login without any OAuth app setup on your end. AuthKit uses its own platform-level credentials — you just flip a toggle.
Setup (dashboard)
In the AuthKit dashboard, open your app → Configure → Social Login. Toggle Google or GitHub on. That's it — no Client ID or Client Secret required.
Client usage
import { useAuth } from "@paribeshn/authkit/react"; function LoginButtons() { const { signInWithOAuth } = useAuth(); return ( <div> <button onClick={() => signInWithOAuth("google")}> Continue with Google </button> <button onClick={() => signInWithOAuth("github")}> Continue with GitHub </button> </div> ); }
signInWithOAuth redirects the user to the provider. After they authorize, they're redirected to /auth/callback in your app, which exchanges the code and establishes a session automatically.
Callback route
Add /auth/callback to your publicRoutes in authkitMiddleware so it is not blocked before the session is created.
export default authkitMiddleware({ secretKey: process.env.AUTH_SECRET_KEY!, signInUrl: "/sign-in", publicRoutes: [ "/sign-in", "/sign-up", "/auth/callback", // ← required for OAuth ], });
MFA / TOTP
AuthKit supports time-based one-time passwords (TOTP) compatible with Google Authenticator, Authy, and any RFC 6238 app. Once enabled, users must supply a 6-digit code at every sign-in.
Flow overview
| Prop | Type | Description |
|---|---|---|
| GET /v1/me/mfa | auth required | Check whether MFA is enabled for the current user. |
| POST /v1/me/mfa/setup | auth required | Generate a new TOTP secret + QR code data URL. |
| POST /v1/me/mfa/verify-setup | auth required | Confirm the first OTP code to activate MFA. |
| POST /v1/me/mfa/disable | auth required | Disable MFA (requires current OTP code to confirm). |
| POST /v1/mfa/verify | publishable key | Verify an OTP during the sign-in challenge flow. |
Setup — show the QR code
// 1. Call setup to get the QR code const res = await fetch(`${API_URL}/v1/me/mfa/setup`, { method: "POST", headers: { "x-publishable-key": PK, Authorization: `Bearer ${accessToken}`, }, }); const { qrDataUrl, secret } = await res.json(); // 2. Show the QR to the user <img src={qrDataUrl} alt="Scan with your authenticator app" /> <p>Manual entry: {secret}</p> // 3. After scanning, verify the first code to activate await fetch(`${API_URL}/v1/me/mfa/verify-setup`, { method: "POST", headers: { "x-publishable-key": PK, "Content-Type": "application/json", Authorization: `Bearer ${accessToken}`, }, body: JSON.stringify({ code: "123456" }), }); // Response includes backupCodes: string[] — store these safely
Backup codes
On successful setup, the API returns 8 one-time backup codes. Each code can be used once in place of a TOTP code. Show them to the user immediately — they cannot be retrieved later.
Disabling MFA
await fetch(`${API_URL}/v1/me/mfa/disable`, { method: "POST", headers: { "x-publishable-key": PK, "Content-Type": "application/json", Authorization: `Bearer ${accessToken}`, }, body: JSON.stringify({ code: "123456" }), });
Session Management
Users can view active sessions (devices) and revoke them individually or all at once.
import { useAuth } from "@paribeshn/authkit/react"; function SessionsPanel() { const { listSessions, revokeSession, revokeAllSessions } = useAuth(); const [sessions, setSessions] = useState([]); useEffect(() => { listSessions().then(setSessions); }, []); return ( <ul> {sessions.map(session => ( <li key={session.id}> <span>{session.userAgent}</span> {session.isCurrent && <span>(current)</span>} <button onClick={() => revokeSession(session.id)}> Revoke </button> </li> ))} </ul> ); }
| Prop | Type | Description |
|---|---|---|
| listSessions() | () => Promise<Session[]> | List all active sessions for the current user. |
| revokeSession(id) | (id: string) => Promise<void> | Revoke a single session by ID. |
| revokeAllSessions() | () => Promise<void> | Revoke every session except the current one. |
| Prop | Type | Description |
|---|---|---|
| id | string | Session ID |
| userAgent | string | null | Browser/device user-agent string |
| ipAddress | string | null | IP address at sign-in |
| createdAt | string | ISO timestamp when session was created |
| isCurrent | boolean | Whether this is the caller's active session |
Webhooks
AuthKit fires HMAC-SHA256 signed webhooks on every user lifecycle event. Configure the URL in the dashboard under your app → Webhooks.
Events
| Prop | Type | Description |
|---|---|---|
| user.created | object | Fired when a user signs up for the first time. |
| user.signed_in | object | Fired on every successful sign-in. |
| user.banned | object | Fired when an admin bans a user. |
| user.unbanned | object | Fired when an admin unbans a user. |
| user.deleted | object | Fired when a user or admin deletes an account. |
Payload shape
{ "type": "user.created", "timestamp": "2025-01-15T10:30:00Z", "data": { "id": "usr_abc123", "email": "jane@example.com", "firstName": "Jane", "lastName": "Doe", "emailVerified": true, "banned": false, "createdAt": "2025-01-15T10:30:00Z" } }
Verifying the signature
import { verifyWebhook } from "@paribeshn/authkit/server"; // The signature is in the X-AuthKit-Signature header // The raw body must be passed (not JSON.parse'd) const event = verifyWebhook(rawBody, signature, webhookSecret); if (!event) { return res.status(401).send("Invalid signature"); } console.log(event.type, event.data);
User Metadata
Attach arbitrary JSON to any user. publicMetadata is readable from the client; privateMetadata is server-only.
From the server
// Set metadata via the AuthKit REST API // PATCH /v1/users/:userId/metadata // Authorization: Bearer <secretKey> await fetch(`${process.env.NEXT_PUBLIC_API_URL}/v1/users/${userId}/metadata`, { method: "PATCH", headers: { "Content-Type": "application/json", Authorization: `Bearer ${process.env.AUTH_SECRET_KEY}`, }, body: JSON.stringify({ publicMetadata: { role: "admin", plan: "pro" }, privateMetadata: { stripeCustomerId: "cus_xyz" }, }), });
Reading on the client
import { useUser } from "@paribeshn/authkit/react"; function RoleBadge() { const { user } = useUser(); const role = user?.publicMetadata?.role as string | undefined; return role ? <span>{role}</span> : null; }
App Settings
Configure authentication policy per-application from the dashboard under Settings. All rules are enforced server-side automatically.
Password policy
| Prop | Type | Description |
|---|---|---|
| passwordMinLength | number (6–72) | Minimum password length. Default: 8. |
| requireUppercase | boolean | Reject passwords with no uppercase letter. Default: false. |
| requireNumber | boolean | Reject passwords with no digit. Default: false. |
Sign-up policy
| Prop | Type | Description |
|---|---|---|
| allowSignups | boolean | Set to false to stop new users from registering. Default: true. |
| requireEmailVerification | boolean | Gate access until the user verifies their email. Default: false. |
Session policy
| Prop | Type | Description |
|---|---|---|
| sessionDurationHours | number (1–8760) | How long refresh tokens stay valid. Default: 168 (7 days). |
| maxSessionsPerUser | number (1–100) | Oldest session is automatically pruned when this is exceeded. Default: 5. |
Settings are applied immediately — existing sessions are not invalidated by a duration change, but new sign-ins will use the updated duration.
Rate Limits
AuthKit enforces per-IP rate limits to protect against brute-force attacks. When a limit is hit, the server responds with 429 Too Many Requests and a Retry-After header.
| Prop | Type | Description |
|---|---|---|
| Sign-in / Sign-up / Forgot password | 5 per 15 minutes | Per IP address. |
| MFA verify | 5 per 15 minutes | Per IP address. |
| OTP send | 3 per 10 minutes | Per IP address. |
| All other /v1 endpoints | 60 per minute | Per IP address. |
Handling 429 in your app
try { await signIn({ email, password }); } catch (err) { if (err.status === 429) { const retryAfter = err.headers?.["retry-after"] ?? 60; setError(`Too many attempts. Try again in ${retryAfter}s.`); } }
Ready to start building?
Create an account, make an app, and get your keys in under a minute.
Get started →