Open source authentication framework. Read the docs

The most comprehensive authentication framework for TypeScript

Quick Setup

Get authentication running in minutes

Answer a few prompts and Keyloom scaffolds routes, providers, and environment variables for you.

CLI flow~5 min setup
Next steps: configure keyloom.config, set env vars, run migrations
1

Install the CLI

Add the dev-only CLI and run the interactive init to detect your framework, install deps, and scaffold config.

pnpm add -g @keyloom/cli
2

Install Hooks

Lets you access auth state and user info on the client.

pnpm add @keyloom/react
3

Install UI

Don't want the hastle of building your own auth UI? Use our UI library.

pnpm add @keyloom/ui

Prefer to follow along?

Watch the full walkthrough to see environment syncing, provider setup, and deployment in real time.

Open the guided tutorial

Powerful primitives

Build faster with focused components

Before / AfterKeyloom VS Next-Auth
before
import { NextAuthOptions } from 'next-auth';
import GithubProvider from 'next-auth/providers/github';
import GoogleProvider from 'next-auth/providers/google';
import { PrismaAdapter } from '@next-auth/prisma-adapter';
import { prisma } from './prisma';

export const authOptions: NextAuthOptions = {
  adapter: PrismaAdapter(prisma),
  providers: [
    GithubProvider({
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    }),
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }),
  ],
  callbacks: {
    session: async ({ session, token }) => {
      if (token.sub) {
        const user = await prisma.user.findUnique({
          where: { id: token.sub },
          include: { accounts: true, sessions: true }
        });
        return {
          ...session,
          user: { ...session.user, id: token.sub, role: user?.role }
        };
      }
      return session;
    },
    jwt: ({ token, user, account }) => {
      if (user) {
        token.sub = user.id;
        token.role = user.role;
      }
      return token;
    },
    signIn: async ({ user, account, profile }) => {
      try {
        if (account?.provider === 'github' || account?.provider === 'google') {
          const existingUser = await prisma.user.findUnique({
            where: { email: user.email! }
          });
          if (!existingUser) {
            await prisma.user.create({
              data: {
                email: user.email!,
                name: user.name,
                image: user.image,
                role: 'USER'
              }
            });
          }
        }
        return true;
      } catch (error) {
        console.error('Sign in error:', error);
        return false;
      }
    },
  },
  pages: {
    signIn: '/auth/signin',
    error: '/auth/error',
    signOut: '/auth/signout'
  },
  session: { strategy: 'jwt', maxAge: 30 * 24 * 60 * 60 },
  events: {
    signIn: async ({ user }) => {
      await prisma.user.update({
        where: { id: user.id },
        data: { lastLogin: new Date() }
      });
    }
  }
};
after
import { defineKeyloom } from '@keyloom/core';
import { PrismaAdapter } from '@keyloom/adapters';
import { PrismaClient } from '@prisma/client';
const db = new PrismaClient();
import github from '@keyloom/providers/github';
import google from '@keyloom/providers/google';

export default defineKeyloom({
  baseUrl: process.env.NEXT_PUBLIC_APP_URL!,
  session: { strategy: 'database', ttlMinutes: 60, rolling: true },
  adapter: PrismaAdapter(db),
  providers: [
    github({
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!
    }),
    google({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!
    }),
  ],
  secrets: { authSecret: process.env.AUTH_SECRET! },
});

Built for your stack

  • Next.js
  • React
  • Node.js
  • Prisma
  • PostgreSQL
  • Tailwind CSS
  • Vercel
  • GitHub