Skip to main content

@next-auth/kysely-adapter

Kysely adapter for Auth.js / NextAuth.js.

Installation​

npm install next-auth kysely pg @next-auth/kysely-adapter
npm install --save-dev @types/pg

KyselyAdapter()​

Basic usage​

This is the Kysely Adapter for next-auth. This package can only be used in conjunction with the primary next-auth package. It is not a standalone package.

This adapter supports the same first party dialects that Kysely (as of v0.24.2) supports: PostgreSQL, MySQL, and SQLite. The examples below use PostgreSQL with the pg client.

Configure Auth.js​

pages/api/auth/[...nextauth].ts
import NextAuth from "next-auth";
import GoogleProvider from "next-auth/providers/google";
import { KyselyAdapter } from "@next-auth/kysely-adapter";
import { db } from "../../../db";

export default NextAuth({
adapter: KyselyAdapter(db),
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
],
});

Configure Kysely​

Kysely's constructor requires a database interface that contains an entry with an interface for each of your tables. You can define these types manually, or use kysely-codegen / prisma-kysely to automatically generate them.

This adapter also exports a wrapper around the original Kysely class, AuthedKysely, that can be used to provide an additional level of type-safety. While using it isn't required, it is recommended as it will verify that the database interface has all the fields that Auth.js requires.

db/index.ts
import type { GeneratedAlways } from "kysely";
import { PostgresDialect } from "kysely";
import { Pool } from "pg";
import { AuthedKysely } from "@next-auth/kysely-adapter";

interface User {
id: GeneratedAlways<string>;
name: string | null;
email: string;
emailVerified: Date | null;
image: string | null;
}

interface Account {
id: GeneratedAlways<string>;
userId: string;
type: string;
provider: string;
providerAccountId: string;
refresh_token: string | null;
access_token: string | null;
expires_at: number | null;
token_type: string | null;
scope: string | null;
id_token: string | null;
session_state: string | null;
oauth_token_secret: string | null;
oauth_token: string | null;
}

interface Session {
id: GeneratedAlways<string>;
userId: string;
sessionToken: string;
expires: Date;
}

interface VerificationToken {
identifier: string;
token: string;
expires: Date;
}

interface Database {
User: User;
Account: Account;
Session: Session;
VerificationToken: VerificationToken;
}

export const db = new AuthedKysely<Database>({
dialect: new PostgresDialect({
pool: new Pool({
host: process.env.DATABASE_HOST,
database: process.env.DATABASE_NAME,
user: process.env.DATABASE_USER,
password: process.env.DATABASE_PASSWORD,
}),
}),
});
note

An alternative to manually defining types is generating them from the database schema using kysely-codegen, or from Prisma schemas using prisma-kysely. When using generated types with AuthedKysely, import Codegen and pass it as the second generic arg:

import type { Codegen } from "@next-auth/kysely-adapter"
new AuthedKysely<Database, Codegen>(...)

Schema​

db/migrations/001_create_db.ts
import { Kysely, sql } from "kysely";

export async function up(db: Kysely<any>): Promise<void> {
await db.schema
.createTable("User")
.addColumn("id", "uuid", (col) =>
col.primaryKey().defaultTo(sql`gen_random_uuid()`)
)
.addColumn("name", "text")
.addColumn("email", "text", (col) => col.unique().notNull())
.addColumn("emailVerified", "timestamptz")
.addColumn("image", "text")
.execute();

await db.schema
.createTable("Account")
.addColumn("id", "uuid", (col) =>
col.primaryKey().defaultTo(sql`gen_random_uuid()`)
)
.addColumn("userId", "uuid", (col) =>
col.references("User.id").onDelete("cascade").notNull()
)
.addColumn("type", "text", (col) => col.notNull())
.addColumn("provider", "text", (col) => col.notNull())
.addColumn("providerAccountId", "text", (col) => col.notNull())
.addColumn("refresh_token", "text")
.addColumn("access_token", "text")
.addColumn("expires_at", "bigint")
.addColumn("token_type", "text")
.addColumn("scope", "text")
.addColumn("id_token", "text")
.addColumn("session_state", "text")
.addColumn("oauth_token_secret", "text")
.addColumn("oauth_token", "text")
.execute();

await db.schema
.createTable("Session")
.addColumn("id", "uuid", (col) =>
col.primaryKey().defaultTo(sql`gen_random_uuid()`)
)
.addColumn("userId", "uuid", (col) =>
col.references("User.id").onDelete("cascade").notNull()
)
.addColumn("sessionToken", "text", (col) => col.notNull().unique())
.addColumn("expires", "timestamptz", (col) => col.notNull())
.execute();

await db.schema
.createTable("VerificationToken")
.addColumn("identifier", "text", (col) => col.notNull())
.addColumn("token", "text", (col) => col.notNull().unique())
.addColumn("expires", "timestamptz", (col) => col.notNull())
.execute();

await db.schema
.createIndex("Account_userId_index")
.on("Account")
.column("userId")
.execute();

await db.schema
.createIndex("Session_userId_index")
.on("Session")
.column("userId")
.execute();
}

export async function down(db: Kysely<any>): Promise<void> {
await db.schema.dropTable("Account").ifExists().execute();
await db.schema.dropTable("Session").ifExists().execute();
await db.schema.dropTable("User").ifExists().execute();
await db.schema.dropTable("VerificationToken").ifExists().execute();
}

This schema is adapted for use in Kysely and is based upon our main schema.

For more information about creating and running migrations with Kysely, refer to the Kysely migrations documentation.

Naming conventions​

If mixed snake_case and camelCase column names is an issue for you and/or your underlying database system, we recommend using Kysely's CamelCasePlugin (see the documentation here) feature to change the field names. This won't affect NextAuth.js, but will allow you to have consistent casing when using Kysely.

KyselyAdapter(db: Kysely<Database>): Adapter

Parameters​

ParameterType
dbKysely<Database>

Returns​

Adapter


AuthedKysely​

Wrapper over the original Kysely class in order to validate the passed in database interface. A regular Kysely instance may also be used, but wrapping it ensures the database interface implements the fields that Auth.js requires. When used with kysely-codegen, the Codegen type can be passed as the second generic argument. The generated types will be used, and AuthedKysely will only verify that the correct fields exist.

Type parameters​

  • DB extends T
  • T = Database