Skip to content
Migrating from NextAuth.js v4? Read our migration guide.

SurrealDB Adapter

Resources

Setup

Installation

npm install @auth/surrealdb-adapter surrealdb

Environment Variables

A valid authentication combination must be provided. The following authentication combinations are supported:

  • RootAuth
  • NamespaceAuth
  • DatabaseAuth
  • ScopeAuth
AUTH_SURREAL_URL (required)
AUTH_SURREAL_NS
AUTH_SURREAL_DB
AUTH_SURREAL_USER
AUTH_SURREAL_PW
AUTH_SURREAL_SCOPE
SURREAL_NS (required when using RootAuth or NamespaceAuth)
SURREAL_DB (required when using RootAuth or NamespaceAuth)

Configuration

./auth.ts
import NextAuth from "next-auth"
import { SurrealDBAdapter } from "@auth/surrealdb-adapter"
import clientPromise from "./lib/surrealdb"
 
export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [],
  adapter: SurrealDBAdapter(clientPromise),
})

The SurrealDB adapter does not handle connections automatically, so you will have to make sure that you pass the Adapter a SurrealDBClient that is connected already. Below you can see an example how to do this.

Utils File

./lib/surrealdb_utils.ts
import type { ConnectOptions, AnyAuth } from "surrealdb"
import { Surreal, ConnectionStatus } from "surrealdb"
 
/**
 * Maintains a single instance of surrealdb.
 * Automatically reconnects unless options.reconnect is set to false manually.
 */
export class MySurreal {
  // A single instantiation of a surreal connection
  private _surrealdb: Surreal | undefined
  private _url: string | URL
  private _opts: ConnectOptions | undefined
 
  constructor(
    url: Parameters<InstanceType<typeof Surreal>["connect"]>[0],
    opts?: ConnectOptions
  ) {
    this._url = url
    if (opts && opts.reconnect === undefined) {
      opts.reconnect = true
    }
    this._opts = opts
  }
 
  async surrealdb(): Promise<Surreal> {
    // Init Surreal
    if (!this._surrealdb) {
      this._surrealdb = new Surreal()
    }
 
    if (this._surrealdb.status == ConnectionStatus.Connected) {
      return this._surrealdb
    } else if (this._surrealdb.status == ConnectionStatus.Disconnected) {
      try {
        // Connect as a database user
        await this._surrealdb.connect(this._url, this._opts)
        if (process.env.NODE_ENV === "development") {
          const str = this.toConnectionString(
            this._surrealdb.status,
            this._opts
          )
          console.info(str)
        }
      } catch (error) {
        if (error instanceof Error) throw error
        throw new Error(error as unknown as string)
      }
    }
    return this._surrealdb
  }
 
  private toConnectionString(status: ConnectionStatus, opts?: ConnectOptions) {
    let str = `${status}`
    const auth = opts?.auth
 
    if (auth && typeof auth !== "string") {
      if ("username" in auth) {
        str += ` as ${auth.username}`
      }
      if ("database" in auth && "namespace" in auth) {
        str += ` for ${auth.namespace} ${auth.database}`
      } else if ("namespace" in auth) {
        str += ` for ${auth.namespace}`
      } else if ("database" in auth) {
        str += ` for ${auth.database}`
      }
    }
    return str
  }
}
 
/**
 * Converts environment variables to an AnyAuth type
 * to connect with the database
 * @param param0 - environment variables
 * @returns {RootAuth | NamespaceAuth | DatabaseAuth | ScopeAuth}
 */
export function toAnyAuth({
  username,
  password,
  namespace,
  database,
  scope,
}: Record<string, string | undefined>) {
  let auth: AnyAuth
  if (username && password && namespace && database) {
    auth = {
      database,
      namespace,
      username,
      password,
    }
  } else if (username && password && namespace) {
    auth = {
      namespace,
      username,
      password,
    }
  } else if (username && password) {
    auth = {
      username,
      password,
    }
  } else if (scope) {
    auth = {
      namespace,
      database,
      username,
      password,
      scope,
    }
  } else {
    throw new Error("unsupported any auth configuration")
  }
  return auth
}

Authorization

The clientPromise provides a connection to the database. You could use any connect option you wish. For quick setup, use the DatabaseAuth method. For best security, we recommend creating a Record Access method if you know how to properly setup access table permissions.

./lib/surrealdb.ts
import { type Surreal } from "surrealdb"
import { handleDisconnect, MySurreal, toAnyAuth } from "./lib/surrealdb_utils"
 
const clientPromise = new Promise<Surreal>(async (resolve, reject) => {
  try {
    const {
      AUTH_SURREAL_URL: auth_url,
      AUTH_SURREAL_NS: auth_ns,
      AUTH_SURREAL_DB: auth_db,
      AUTH_SURREAL_USER: auth_user,
      AUTH_SURREAL_PW: auth_pw,
      AUTH_SURREAL_SCOPE: auth_scope,
      SURREAL_NS: namespace,
      SURREAL_DB: database,
    } = process.env
    if (!auth_url) throw new Error("required auth_url")
    const auth = toAnyAuth({
      namespace: auth_ns,
      database: auth_db,
      username: auth_user,
      password: auth_pw,
      scope: auth_scope,
    })
    const surreal = new MySurreal(auth_url, {
      namespace,
      database,
      auth,
    })
    const db = await surreal.surrealdb()
    resolve(db)
  } catch (e) {
    reject(e)
  }
})

HTTP ENGINE

With this configuration, we can use the database’s http endpoint. Thus, the AUTH_SURREAL_URL should begin with http or https.

Websocket ENGINE

With this configuration, we can use the database’s websocket endpoint. Thus, the AUTH_SURREAL_URL should begin with ws or wss.

Auth.js © Balázs Orbán and Team - 2025