Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Аутентификация

Аутентификация — проверка, кто пришёл на сайт: пользователь вводит почту и пароль, а приложение запоминает его в сессии. В AdonisJS этим занимается пакет @adonisjs/auth. Если при создании проекта на наборе Web вы выбрали вход через session, всё уже настроено: модель User, миграция таблицы users и файл config/auth.ts с охранником (guard) web.

Модель пользователя

// app/models/user.ts
import { DateTime } from 'luxon'
import hash from '@adonisjs/core/services/hash'
import { compose } from '@adonisjs/core/helpers'
import { BaseModel, column } from '@adonisjs/lucid/orm'
import { withAuthFinder } from '@adonisjs/auth/mixins/lucid'
 
const AuthFinder = withAuthFinder(() => hash.use('scrypt'), {
  uids: ['email'],
  passwordColumnName: 'password',
})
 
export default class User extends compose(BaseModel, AuthFinder) {
  @column({ isPrimary: true })
  declare id: number
 
  @column()
  declare fullName: string | null
 
  @column()
  declare email: string
 
  @column({ serializeAs: null })
  declare password: string
 
  @column.dateTime({ autoCreate: true })
  declare createdAt: DateTime
}

Примесь withAuthFinder добавляет модели две вещи: пароль хешируется сам перед сохранением, а метод verifyCredentials проверяет почту и пароль. serializeAs: null не даёт паролю попасть в JSON.

Регистрация

// app/controllers/register_controller.ts
import type { HttpContext } from '@adonisjs/core/http'
import User from '#models/user'
import { registerValidator } from '#validators/auth'
 
export default class RegisterController {
  async store({ request, auth, response }: HttpContext) {
    const payload = await request.validateUsing(registerValidator)
    const user = await User.create(payload)
    await auth.use('web').login(user)
    return response.redirect('/posts')
  }
}

Валидатор лежит в app/validators/auth.ts:

// app/validators/auth.ts
import vine from '@vinejs/vine'
 
export const registerValidator = vine.compile(
  vine.object({
    fullName: vine.string().trim().minLength(2),
    email: vine.string().email().unique({ table: 'users', column: 'email' }),
    password: vine.string().minLength(8),
  })
)

Правило unique не даст зарегистрировать вторую учётную запись с той же почтой. После создания пользователь сразу входит на сайт.

Вход и выход

// app/controllers/session_controller.ts
import type { HttpContext } from '@adonisjs/core/http'
import User from '#models/user'
 
export default class SessionController {
  async store({ request, auth, response }: HttpContext) {
    const { email, password } = request.only(['email', 'password'])
    const user = await User.verifyCredentials(email, password)
    await auth.use('web').login(user)
    return response.redirect('/posts')
  }
 
  async destroy({ auth, response }: HttpContext) {
    await auth.use('web').logout()
    return response.redirect('/login')
  }
}

Если пароль неверный, verifyCredentials выбросит ошибку, и форма вернётся с сообщением. Показать его можно тегом @error('E_INVALID_CREDENTIALS') с переменной $message внутри.

Маршруты

// start/routes.ts
import router from '@adonisjs/core/services/router'
import { middleware } from '#start/kernel'
 
const SessionController = () => import('#controllers/session_controller')
 
router.on('/login').render('pages/auth/login').use(middleware.guest())
router.post('/login', [SessionController, 'store']).use(middleware.guest())
router.post('/logout', [SessionController, 'destroy']).use(middleware.auth())

middleware.guest() пускает только гостей, middleware.auth() — только вошедших.

Текущий пользователь

На маршрутах с middleware.auth() пользователь доступен как auth.user — в контроллере и в шаблоне:

{{-- resources/views/components/layout.edge --}}
@if(auth.user)
  <span>{{ auth.user.fullName }}</span>
  <form method="POST" action="/logout">
    {{ csrfField() }}
    <button type="submit">Выйти</button>
  </form>
@else
  <a href="/login">Войти</a>
@end

На публичных страницах без этого middleware auth.user пуст, пока не вызван await auth.check(). Как сделать это сразу для всех страниц, показано в уроке о middleware.

Частая ошибка

Пароль хешируют вручную перед User.create, а примесь withAuthFinder хеширует его ещё раз. В базу попадает хеш от хеша, и войти уже невозможно. С withAuthFinder передавайте в create пароль как есть.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.