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.

bash
# 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
tsx
// 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>
  );
}
ts
// 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).*)"],
};
tsx
// 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.

bash
npm install @paribeshn/authkit

The package has three entrypoints:

PropTypeDescription
@paribeshn/authkit/reactmoduleAuthProvider, useAuth, useUser, guards, pre-built UI components
@paribeshn/authkit/nextjsmoduleauthkitMiddleware, getUserFromRequest
@paribeshn/authkit/servermoduleverifyToken, authMiddleware, verifyWebhook

Environment variables

bash
# .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.

tsx
import { AuthProvider } from "@paribeshn/authkit/react";

<AuthProvider
  publishableKey="pk_live_..."
  baseUrl="https://your-authkit-api.com"
>
  {children}
</AuthProvider>
PropTypeDescription
publishableKeystringYour app's publishable key from the dashboard.
baseUrlstringURL 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

tsx
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

tsx
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>
  );
}
PropTypeDescription
idstringUnique user ID
emailstring | nullUser's email address
usernamestring | nullUsername (if set)
firstNamestring | nullFirst name
lastNamestring | nullLast name
emailVerifiedbooleanWhether the email is verified
bannedbooleanWhether this user is banned
publicMetadataobjectApp-controlled public metadata
lastSignInAtstring | nullISO 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.

tsx
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.

tsx
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.

tsx
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.

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",
    "/reset-password",
    "/auth/callback",
  ],
});

export const config = {
  matcher: ["/((?!_next|favicon.ico|.*\.png$).*)"],
};
PropTypeDescription
secretKeystringYour app's secret key from the dashboard.
signInUrlstringPath to redirect unauthenticated users to.
publicRoutesstring[]Routes that bypass authentication.

getUserFromRequest

Extract the verified user from a Next.js request inside Server Components or Route Handlers.

ts
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.).

ts
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.

ts
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.

ts
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 → ConfigureSocial Login. Toggle Google or GitHub on. That's it — no Client ID or Client Secret required.

Client usage

tsx
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.

ts
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

PropTypeDescription
GET /v1/me/mfaauth requiredCheck whether MFA is enabled for the current user.
POST /v1/me/mfa/setupauth requiredGenerate a new TOTP secret + QR code data URL.
POST /v1/me/mfa/verify-setupauth requiredConfirm the first OTP code to activate MFA.
POST /v1/me/mfa/disableauth requiredDisable MFA (requires current OTP code to confirm).
POST /v1/mfa/verifypublishable keyVerify an OTP during the sign-in challenge flow.

Setup — show the QR code

tsx
// 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

ts
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.

tsx
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>
  );
}
PropTypeDescription
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.
PropTypeDescription
idstringSession ID
userAgentstring | nullBrowser/device user-agent string
ipAddressstring | nullIP address at sign-in
createdAtstringISO timestamp when session was created
isCurrentbooleanWhether 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

PropTypeDescription
user.createdobjectFired when a user signs up for the first time.
user.signed_inobjectFired on every successful sign-in.
user.bannedobjectFired when an admin bans a user.
user.unbannedobjectFired when an admin unbans a user.
user.deletedobjectFired when a user or admin deletes an account.

Payload shape

json
{
  "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

ts
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

ts
// 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

tsx
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

PropTypeDescription
passwordMinLengthnumber (6–72)Minimum password length. Default: 8.
requireUppercasebooleanReject passwords with no uppercase letter. Default: false.
requireNumberbooleanReject passwords with no digit. Default: false.

Sign-up policy

PropTypeDescription
allowSignupsbooleanSet to false to stop new users from registering. Default: true.
requireEmailVerificationbooleanGate access until the user verifies their email. Default: false.

Session policy

PropTypeDescription
sessionDurationHoursnumber (1–8760)How long refresh tokens stay valid. Default: 168 (7 days).
maxSessionsPerUsernumber (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.

PropTypeDescription
Sign-in / Sign-up / Forgot password5 per 15 minutesPer IP address.
MFA verify5 per 15 minutesPer IP address.
OTP send3 per 10 minutesPer IP address.
All other /v1 endpoints60 per minutePer IP address.

Handling 429 in your app

tsx
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 →