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.