Encodera

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

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

Оглавление

JSON API и токены доступа

Мобильному приложению или фронтенду на React нужны не HTML-страницы, а данные в JSON. API — набор адресов, которые принимают и отдают JSON. Сессии и cookie для таких клиентов неудобны, поэтому пользователь получает токен доступа — длинную случайную строку, которую клиент отправляет в заголовке каждого запроса.

Маршруты API

Контроллер, который возвращает объект или модель, уже отвечает JSON:

// app/controllers/api/posts_controller.ts
import type { HttpContext } from '@adonisjs/core/http'
import Post from '#models/post'
 
export default class PostsController {
  async index({}: HttpContext) {
    return Post.query().where('is_published', true).orderBy('created_at', 'desc')
  }
 
  async show({ params }: HttpContext) {
    return Post.findOrFail(params.id)
  }
}
// start/routes.ts
const ApiPostsController = () => import('#controllers/api/posts_controller')
 
router
  .group(() => {
    router.get('/posts', [ApiPostsController, 'index'])
    router.get('/posts/:id', [ApiPostsController, 'show'])
  })
  .prefix('/api')

Откройте /api/posts — появится JSON-массив статей. Свойства моделей приходят в camelCase: isPublished, createdAt.

Охранник access_tokens

Токены хранятся в таблице auth_access_tokens. Если проект создан на наборе API, всё уже настроено. В наборе Web добавьте охранник в config/auth.ts рядом с web:

// config/auth.ts
import { tokensGuard, tokensUserProvider } from '@adonisjs/auth/access_tokens'
 
// внутри guards:
api: tokensGuard({
  provider: tokensUserProvider({
    tokens: 'accessTokens',
    model: () => import('#models/user'),
  }),
}),

В модели пользователя объявите хранилище токенов:

// app/models/user.ts
import { DbAccessTokensProvider } from '@adonisjs/auth/access_tokens'
 
export default class User extends compose(BaseModel, AuthFinder) {
  // ...столбцы
 
  static accessTokens = DbAccessTokensProvider.forModel(User)
}

Миграцию для таблицы токенов создайте командой node ace make:migration auth_access_tokens по образцу из документации пакета auth.

Выдача токена

// app/controllers/api/tokens_controller.ts
import type { HttpContext } from '@adonisjs/core/http'
import User from '#models/user'
 
export default class TokensController {
  async store({ request }: HttpContext) {
    const { email, password } = request.only(['email', 'password'])
    const user = await User.verifyCredentials(email, password)
    const token = await User.accessTokens.create(user)
 
    return { type: 'bearer', value: token.value!.release() }
  }
}

release() возвращает сам токен строкой вида oat_.... Он показывается один раз — в базе хранится только его хеш.

Защищённые маршруты

// start/routes.ts
import { middleware } from '#start/kernel'
 
router
  .post('/api/posts', [ApiPostsController, 'store'])
  .use(middleware.auth({ guards: ['api'] }))

Клиент передаёт токен в заголовке Authorization:

curl -X POST http://localhost:3333/api/posts \
  -H "Authorization: Bearer oat_MQ.abc123" \
  -H "Accept: application/json" \
  -d "title=Из API&body=Статья, созданная через API"

Без токена ответ будет 401, с верным токеном внутри контроллера доступен auth.user.

В наборе Web защита CSRF отклонит такой POST, потому что у клиента API нет CSRF-токена. Исключите адреса API в config/shield.ts:

// config/shield.ts
csrf: {
  enabled: true,
  exceptRoutes: (ctx) => ctx.request.url().startsWith('/api/'),
  // ...остальные настройки
},

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

Клиент API не отправляет заголовок Accept: application/json. Тогда при ошибке валидации AdonisJS считает запрос обычной формой и отвечает редиректом вместо JSON со статусом 422. Пусть клиент всегда отправляет Accept: application/json — тогда и ошибки придут в JSON.

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

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