Creating a database adapter
Auth.js adapters allow you to integrate with any (even multiple) database/back-end service, even if we don't have an official package available yet. (We welcome PRs for new adapters! See the guidelines below.)
Auth.js adapters are very flexible, and you can implement only the methods you need, and only create the database tables/columns that are actually going to be used.
An Auth.js adapter is a function that receives an ORM/database client and returns an object with methods (based on the Adapter
interface) that interact with the database. The same database Adapter will be compatible with any Auth.js library.
User management
Auth.js differentiates between users and accounts. A user can have multiple accounts. An account is created for each provider type the user signs in with for the first time. For example, if a user signs in with Google and then with Facebook, they will have two accounts, one for each provider. The first provider the user signs in with will also be used to create the user object. See the profile()
provider method.
Methods and models
Not yet invoked by Auth.js:
See also: User and Account models.
Although Auth.js doesn't require it, for basic display purposes, we recommend adding the following columns to the User
table as well: name
, email
, image
. You can configure the columns via the profile()
provider method. If you don't need to save these properties, create an empty profile() {}
method.
Although Auth.js doesn't require it, the Account
table typically saves tokens retrieved from the provider. You can configure the columns via the account()
provider method. If you don't need to save tokens, create an empty account() {}
method.
Database session management
Auth.js can manage sessions in two ways. Learn about them and their advantages and disadvantages at Concepts: Session strategies.
Methods and models
If you want to use database sessions, you will need to implement the following methods:
To add database session management, you will need to expand your database tables/columns as follows:
See also: Session models.
Verification tokens
When you want to support email/passwordless login, Auth.js uses a database to store temporary verification tokens that are tied to a user's email address.
Methods and models
See also: Verification Token models.
Official adapter guidelines
When all of the below steps are done, you are ready to submit a PR to our repository.
If you created an adapter and want us to distribute it as an official package, please make sure it meets the following requirements. Check out this existing adapter to learn about the package structure, required files, test setup, config, etc.
-
The Adapter must implement all methods of the
Adapter
interface -
Adapter tests must be included and must pass. Docker is favored over services, to make CI resilient to network errors and to reduce the number of GitHub Action Secrets (which also lets us run these tests in fork PRs)
-
The Adapter must follow these coding styles
- Written in TypeScript
- Passes the linting rules of the monorepo
- Does not include polyfills
- Configured as an ES module (ESM)
- Documented via JSDoc comments
- Have at least one named export exported from its main module. (For example
export function MyAdapter(): Adapter {}
) - collection/table names should follow the convention (plural/singular, camelCase/snake_case) of the underlying ORM/database docs/conventions
-
Configure the monorepo to help us maintain the package
- Add the Adapter to our documentation generator file
- Add a (preferably
.svg
) logo to this directory - Add the Adapter to our GitHub workflow files here and here
- Make sure to
.gitignore
generated files if any
-
The Adapter must be able to handle any property coming from the user
ORMs/database clients might have their own data types, but Auth.js expects these to be normalized as plain JavaScript objects for consistency. If your ORM/database client does not convert automatically, you need to convert the values when reading/writing from/to the database.
You might be tempted to check the name of a property and convert it based on that, but this is not scalable (eg.: a
User
object might have more than oneDate
property, not onlyemailVerified
).Instead, we recommend creating util functions that convert the values. Below is an example of how to convert dates (if your ORM/database client uses other data types, remember to convert them too, not only dates). It checks if the value can be parsed as a date, and if so, it converts it to a
Date
object. Otherwise, it leaves the original value as is.:
// https://github.com/honeinc/is-iso-date/blob/8831e79b5b5ee615920dcb350a355ffc5cbf7aed/index.js#L5
const isoDateRE =
/(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))/
const isDate = (val: any): val is ConstructorParameters<typeof Date>[0] =>
!!(val && isoDateRE.test(val) && !isNaN(Date.parse(val)))
export const format = {
/** Takes an object that's coming from a database and converts it to plain JavaScript. */
from<T>(object: Record<string, any> = {}): T {
const newObject: Record<string, unknown> = {}
for (const [key, value] of Object.entries(object))
if (isDate(value)) newObject[key] = new Date(value)
else newObject[key] = value
return newObject as T
},
/** Takes an object that's coming from Auth.js and prepares it to be written to the database. */
to<T>(object: Record<string, any>): T {
const newObject: Record<string, unknown> = {}
for (const [key, value] of Object.entries(object))
if (value instanceof Date) newObject[key] = value.toISOString()
else newObject[key] = value
return newObject as T
},
}