Skip to main content
Technology & EngineeringAuth Services262 lines

Kinde

Build with Kinde for authentication and user management. Use this skill when the

Quick Summary34 lines
You are an auth specialist who integrates Kinde into projects. Kinde is a modern
auth platform that combines authentication, authorization (roles/permissions),
feature flags, and organization multi-tenancy in a single service with a generous
free tier.

## Key Points

- Use `getKindeServerSession()` in Server Components — no client-side token exposure
- Use middleware for blanket route protection
- Use permissions over roles for fine-grained access control
- Use feature flags for gradual rollouts — they're built into the auth session
- Use organizations for B2B multi-tenancy
- Sync users to your database via webhooks for app-specific data
- Checking auth in every component instead of using middleware
- Not handling the `isLoading` state in client components
- Using roles for fine-grained checks — use permissions instead
- Not setting up webhooks — your database won't know about Kinde users
- Hardcoding org codes — fetch them dynamically from the session
- Using client-side auth checks for security — always verify server-side

## Quick Example

```bash
npm install @kinde-oss/kinde-auth-nextjs
```

```typescript
// app/api/auth/[kindeAuth]/route.ts
import { handleAuth } from '@kinde-oss/kinde-auth-nextjs/server';

export const GET = handleAuth();
```
skilldb get auth-services-skills/KindeFull skill: 262 lines
Paste into your CLAUDE.md or agent config

Kinde Authentication Integration

You are an auth specialist who integrates Kinde into projects. Kinde is a modern auth platform that combines authentication, authorization (roles/permissions), feature flags, and organization multi-tenancy in a single service with a generous free tier.

Core Philosophy

Auth + feature flags + billing in one

Kinde isn't just authentication — it includes role-based access control, feature flags, and organization management. Instead of stitching together separate services, Kinde provides the full user management stack.

Developer-first pricing

Kinde's free tier supports up to 10,500 MAU. No per-user pricing surprises. This makes it viable for side projects and startups without auth costs eating into early revenue.

Framework-native SDKs

Kinde provides framework-specific SDKs (Next.js, Remix, SvelteKit, etc.) that follow each framework's conventions. The Next.js SDK uses App Router patterns, middleware, and Server Components natively.

Setup

Install (Next.js)

npm install @kinde-oss/kinde-auth-nextjs

Environment variables

KINDE_CLIENT_ID=your-client-id
KINDE_CLIENT_SECRET=your-client-secret
KINDE_ISSUER_URL=https://your-app.kinde.com
KINDE_SITE_URL=http://localhost:3000
KINDE_POST_LOGOUT_REDIRECT_URL=http://localhost:3000
KINDE_POST_LOGIN_REDIRECT_URL=http://localhost:3000/dashboard

Route handler

// app/api/auth/[kindeAuth]/route.ts
import { handleAuth } from '@kinde-oss/kinde-auth-nextjs/server';

export const GET = handleAuth();

Key Techniques

Authentication links

import { LoginLink, RegisterLink, LogoutLink } from '@kinde-oss/kinde-auth-nextjs/components';

function AuthNav() {
  return (
    <nav>
      <LoginLink>Sign in</LoginLink>
      <RegisterLink>Sign up</RegisterLink>
      <LogoutLink>Log out</LogoutLink>
    </nav>
  );
}

Server-side auth

import { getKindeServerSession } from '@kinde-oss/kinde-auth-nextjs/server';

export default async function DashboardPage() {
  const { getUser, isAuthenticated } = getKindeServerSession();

  if (!(await isAuthenticated())) {
    redirect('/api/auth/login');
  }

  const user = await getUser();

  return <div>Welcome {user?.given_name}</div>;
}

// API route
export async function GET() {
  const { getUser, isAuthenticated } = getKindeServerSession();

  if (!(await isAuthenticated())) {
    return new Response('Unauthorized', { status: 401 });
  }

  const user = await getUser();
  return Response.json({ user });
}

Client-side auth

'use client';
import { useKindeBrowserClient } from '@kinde-oss/kinde-auth-nextjs';

function Profile() {
  const { user, isAuthenticated, isLoading } = useKindeBrowserClient();

  if (isLoading) return <div>Loading...</div>;
  if (!isAuthenticated) return <div>Not signed in</div>;

  return (
    <div>
      <p>{user?.email}</p>
      <p>{user?.given_name} {user?.family_name}</p>
    </div>
  );
}

Middleware protection

// middleware.ts
import { withAuth } from '@kinde-oss/kinde-auth-nextjs/middleware';

export default withAuth;

export const config = {
  matcher: ['/dashboard/:path*', '/api/protected/:path*'],
};

Roles and permissions

import { getKindeServerSession } from '@kinde-oss/kinde-auth-nextjs/server';

export default async function AdminPage() {
  const { getPermission, getPermissions } = getKindeServerSession();

  const canManageUsers = await getPermission('manage:users');
  if (!canManageUsers?.isGranted) {
    redirect('/dashboard');
  }

  const allPermissions = await getPermissions();
  // allPermissions.permissions = ['manage:users', 'edit:posts', ...]

  return <div>Admin Panel</div>;
}

Organizations

import { getKindeServerSession } from '@kinde-oss/kinde-auth-nextjs/server';

export default async function OrgPage() {
  const { getOrganization, getUserOrganizations } = getKindeServerSession();

  const org = await getOrganization();
  // org.orgCode = 'org_abc123'

  const allOrgs = await getUserOrganizations();
  // allOrgs.orgCodes = ['org_abc123', 'org_def456']

  return <div>Current org: {org?.orgCode}</div>;
}

// Login to specific org
import { LoginLink } from '@kinde-oss/kinde-auth-nextjs/components';

<LoginLink orgCode="org_abc123">Sign in to Acme</LoginLink>

Feature flags

import { getKindeServerSession } from '@kinde-oss/kinde-auth-nextjs/server';

export default async function PricingPage() {
  const { getFlag } = getKindeServerSession();

  const newPricing = await getFlag('new-pricing-page', false, 'b');

  if (newPricing?.value) {
    return <NewPricingPage />;
  }

  return <CurrentPricingPage />;
}

// Client-side
'use client';
import { useKindeBrowserClient } from '@kinde-oss/kinde-auth-nextjs';

function Feature() {
  const { getFlag } = useKindeBrowserClient();
  const betaFeature = getFlag('beta-editor', false, 'b');

  if (!betaFeature?.value) return null;
  return <BetaEditor />;
}

Webhooks

// app/api/webhooks/kinde/route.ts
import { NextRequest } from 'next/server';
import jwksClient from 'jwks-rsa';
import jwt from 'jsonwebtoken';

const client = jwksClient({
  jwksUri: `${process.env.KINDE_ISSUER_URL}/.well-known/jwks.json`,
});

export async function POST(req: NextRequest) {
  const token = await req.text();

  const { header } = jwt.decode(token, { complete: true })!;
  const key = await client.getSigningKey(header.kid);
  const event = jwt.verify(token, key.getPublicKey()) as any;

  switch (event.type) {
    case 'user.created':
      await db.insert(users).values({
        id: event.data.user.id,
        email: event.data.user.email,
        name: `${event.data.user.first_name} ${event.data.user.last_name}`,
      });
      break;
    case 'user.updated':
      await db.update(users).set({
        email: event.data.user.email,
      }).where(eq(users.id, event.data.user.id));
      break;
  }

  return new Response('OK');
}

Best Practices

  • Use getKindeServerSession() in Server Components — no client-side token exposure
  • Use middleware for blanket route protection
  • Use permissions over roles for fine-grained access control
  • Use feature flags for gradual rollouts — they're built into the auth session
  • Use organizations for B2B multi-tenancy
  • Sync users to your database via webhooks for app-specific data

Anti-Patterns

  • Checking auth in every component instead of using middleware
  • Not handling the isLoading state in client components
  • Using roles for fine-grained checks — use permissions instead
  • Not setting up webhooks — your database won't know about Kinde users
  • Hardcoding org codes — fetch them dynamically from the session
  • Using client-side auth checks for security — always verify server-side

Install this skill directly: skilldb add auth-services-skills

Get CLI access →