Auth.js (формально NextAuth.js) — это фантастическая библиотека для настройки аутентификации в вашем приложении на основе Node.js.
И вам повезло, потому что в Auth.js есть отличные руководства по настройке библиотеки в вашем проекте, не говоря уже о разнообразных адаптерах для работы с вашими данными.
Auth.js предпочитает, чтобы ваша база данных была настроена очень особым образом. Итак, что произойдет, если вам нужно сделать что-то другое? Причин несколько:
- вы предпочитаете, чтобы ваши таблицы базы данных назывались по-другому, например:
usersвместоuser, - разные имена столбцов, например:
user_idвместоuserId, - это входит в уже существующий проект, поэтому изменение первичных и внешних ключей невозможно,
- вы настороженно относитесь к безопасности и хотите поставить несколько ловушек,
- есть дополнительные данные, которые вы хотите сохранить, или
- вы хотите/должны использовать очень специфический движок базы данных, который не поддерживается.
К счастью, есть способ заставить Auth.js работать по-вашему.
Создание пользовательского адаптера
Где вы создаете свой адаптер в своем проекте, зависит от вас, но Auth.js должен использовать его при инициализации, и это зависит от вашей настройки. Вот несколько ссылок для начала:
- Next.js: https://authjs.dev/getting-started/email-tutorial
- Sveltekit: https://authjs.dev/reference/sveltekit
- Nuxt.js: https://sidebase.io/nuxt-auth/getting-started/quick-start
В этом руководстве я буду использовать Prisma.js для обработки подключений к базе данных и запросов, но что бы вы ни выбрали, просто помните, что клиент базы данных должен передаваться в адаптер.
import Email from "@auth/core/providers/email";
import { PrismaClient } from "@prisma/client";
import { MyAdapter } from "../../Adapter";
export default NextAuth({
adapter: MyAdapter(PrismaClient), // <-- db client goes in the adapter
providers: [
Email({
...
}),
],
})
Говоря о Prisma, вот схема для трех таблиц, которые были созданы в базе данных до того, как они были вытащены. Вы могли заметить, что я предпочитаю, чтобы мои внешние ключи использовали имя таблицы в единственном числе с добавлением _id (и метки времени с добавлением _at), что отличается от стандарта Auth.js:
model users {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
name String @db.VarChar(255)
email String @unique(map: "email") @db.VarChar(255)
created_at DateTime @default(now()) @db.Timestamp(0)
sessions sessions[]
}
model sessions {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
user_id BigInt @db.UnsignedBigInt
token String @unique(map: "token") @db.VarChar(255)
expires_at DateTime? @db.Timestamp(0)
users users @relation(fields: [user_id], references: [id], onDelete: Cascade, map: "sessions_ibfk_1")
@@index([user_id], map: "user_id")
}
model tokens {
identifier String @db.VarChar(255)
token String @unique(map: "token") @db.VarChar(255)
expires_at DateTime? @db.Timestamp(0)
@@unique([identifier, token], map: "identifier_token")
}
Следите за этим составным ключом identifier_token, я воспользуюсь им позже.
Золотое правило
При работе с адаптером следует помнить одно важное правило: входные и выходные данные принадлежат Auth.js, но запросы к базе данных ваши.
Вот базовый адаптер для Auth.js (и вам следует заменить MyAdapter на что-то другое):
export default function MyAdapter(client) {
return {
async createUser(user) {
// Create a new record in your user's table with the values in the user object
return user
},
async getUser(id) {
// Get the user with this id
return user
},
async getUserByEmail(email) {
// Get the user with this email
return user
},
async updateUser({ id, ...data }) {
// Update the user's data by their id
return { id, ...data }
},
async deleteUser(id) {
// Delete the user by their id
return
},
async createSession({ sessionToken, userId, expires }) {
// Create a new session record
return { sessionToken, userId, expires }
},
async getSessionAndUser(sessionToken) {
// Get the user and session token
return { user, session }
},
async updateSession({ sessionToken, expires }) {
// Update the session token
return { sessionToken, expires }
},
async deleteSession(sessionToken) {
// Delete the session token
return
},
async createVerificationToken({ identifier, expires, token }) {
// Create a new verification token
return { identifier, expires, token }
},
async useVerificationToken({ identifier, token }) {
// Delete the verification token (so it can't be used again)
// For magic links this will called by the URL in the email
return { identifier, token, expires }
},
}
}
Вы можете просмотреть и заменить каждый комментарий запросом к базе данных в соответствии с вашей схемой. Я проигнорировал getUserByAccount, linkAccount и unlinkAccount, так как сейчас меня интересуют только волшебные ссылки, но их достаточно легко добавить позже, если Я хочу добавить аутентификацию для сторонних сайтов.
Ранее я указал на identifier_token в схеме, которая является составным ключом между столбцами identifier и token в таблице. Хотя ваш дизайн может отличаться, это отличный способ показать, что правило их против вашего для useVerificationToken:
Мы начинаем с пустой асинхронной функции, у которой есть запрошенные входные и выходные данные:
async function useVerificationToken ({ identifier, token }) { // <-- theirs
return { identifier, token, expires } // <-- also theirs
}
Когда вы добавляете вашзапрос к базе данных, вы можете использовать реквизиты Auth.js, но убедитесь, что вы возвращаете правильные пары ключ/значение:
async function useVerificationToken ({ identifier, token }) {
const verificationToken = await client.tokens.delete({
where: {
identifier_token: { // <-- compound key
identifier,
token,
},
},
});
return {
identifier,
token,
expires: verificationToken.expires_at // <-- their key, your value
};
}
Я использую expires_at в своей базе данных, но Auth.js хочет, чтобы expires возвращалось в объекте, поэтому мне нужно вернуть его вместе с identifier и token.
Я также должен обрабатывать identifier_token как объект в моем запросе, но это то, что я получаю при использовании составного ключа.

Заканчивать
Все функции в Auth.js работают вместе, поэтому, если вы хотите настроить работу адаптера, независимо от ваших причин, вам нужно поддерживать Auth.js в хорошем состоянии и возвращать ожидаемые ключи. Что происходит между запуском каждой функции и оператором return, зависит от вас.
И вы всегда можете ознакомиться с другими пакетами адаптеров Auth.js и изучить различные способы создания собственного.