Skip to content

Local HTTPS setup (mkcert)

Последнее обновление: 2026-04-07 (DEV-305 side-quest)

TL;DR

Локальный dev-сервер фронта работает на https://localhost:3000 через mkcert. Это обязательный onboarding-шаг на каждой машине — иначе npm run dev упадёт с «file not found» на сертификатах, а без HTTPS в Safari перестаёт работать авторизация (PHP-сессия теряется между запросами, CSRF ротируется на каждую перезагрузку).

Установка (один раз на машину)

bash
brew install mkcert nss
mkcert -install                         # добавит локальный CA в system keychain (спросит пароль)
cd path/to/partizap-frontend
mkcert localhost 127.0.0.1 ::1          # создаст localhost+2.pem и localhost+2-key.pem

Проверка:

bash
ls localhost+2*.pem
# localhost+2-key.pem  localhost+2.pem
npm run dev
# ➜ Local: https://localhost:3000/

Файлы сертификатов уже в .gitignore (*.pem). Коммитить их нельзя — они привязаны к локальному CA каждого разработчика.

Почему это обязательно

Симптомы без HTTPS

  • Safari: после логина страница выглядит как авторизованная, но при любом reload авторизация «забывается». В DevTools → Storage → Cookies нет PARTIZAP_SESSION. CSRF_TOKEN меняется на каждый запрос GET /health.
  • Chrome / Firefox: работает (до поры до времени), поэтому баг не виден пока не откроешь сайт в Safari.

Root cause

  1. Фронт на http://localhost:3000 проксирует /api/* на https://dev.partizap.ru/api/* через nitro.devProxy (см. nuxt.config.ts).
  2. PHP-бэкенд выставляет PARTIZAP_SESSION cookie с атрибутами HttpOnly; Secure; SameSite=Lax (session.cookie_secure=1 в проде).
  3. cookieDomainRewrite в devProxy убирает Domain=dev.partizap.ru, чтобы кука легла на host-only localhost. Но атрибут Secure остаётся.
  4. Chrome 89+ трактует http://localhost как secure context и принимает Secure-куки на HTTP.
  5. Safari этого не делает (либо делает с оговорками, меняющимися от версии и настроек «Prevent cross-site tracking»). Кука отбрасывается сразу при получении.
  6. На следующем запросе GET /health из app/plugins/auth.ts → authStore.initSession() бэкенд не видит PARTIZAP_SESSION в Cookie-хедере, стартует новую PHP-сессию и выдаёт новый CSRF-токен.
  7. Пользователь видит «ротацию» CSRF и разлогинивание.

Переключение локалки на HTTPS делает localhost secure-контекстом со всех сторон — Safari перестаёт выкидывать Secure-куки, сессия живёт между reload'ами.

Конфигурация в коде

nuxt.config.ts

ts
devServer: {
  https: {
    key: './localhost+2-key.pem',
    cert: './localhost+2.pem',
  },
},

Имена файлов фиксированы под вывод mkcert localhost 127.0.0.1 ::1 (суффикс +2 = два дополнительных SAN). Если генерируешь через mkcert localhost (один host), файлы будут localhost.pem / localhost-key.pem — тогда поправь пути в nuxt.config.ts.

.gitignore

# Local HTTPS certificates (mkcert)
*.pem

Troubleshooting

npm run dev падает с ENOENT: localhost+2.pem

Не сделан onboarding-шаг. Прогони блок из раздела «Установка».

Safari показывает «Не удаётся установить защищённое соединение»

mkcert -install не прошёл или CA был удалён из keychain. Перезапусти:

bash
mkcert -install

Если Firefox всё ещё ругается, нужен nss:

bash
brew install nss
mkcert -install

Порт 3000 занят, Nuxt поднимается на 3001

Проверь, что не осталось зависшего nuxt dev от прошлой сессии:

bash
lsof -iTCP:3000 -sTCP:LISTEN
kill <PID>

NODE_TLS_REJECT_UNAUTHORIZED=0 warning в логах Nuxt

Это общий Nuxt-ворнинг про devProxy-target (HTTPS-upstream на dev.partizap.ru), не про наш локальный cert. Игнорировать — у dev-стенда нормальный публичный сертификат.

Баг воспроизводится на проде (dev.partizap.ru / partizap.ru)

Это уже не local-HTTPS проблема — фронт и бэк там на одном HTTPS-домене, куки работают нативно. Если CSRF ротируется или сессия теряется на проде в Safari, смотреть в сторону:

  • Настроек session.cookie_samesite в PHP
  • ITP (Safari удаляет first-party cookies после 7 дней неактивности)
  • Конфликтов с SameSite=None / Partitioned (CHIPS)

Удаление / откат

Если нужно удалить локальный CA и вернуть macOS в исходное состояние:

bash
mkcert -uninstall      # снимает CA из keychain и NSS
rm localhost+2*.pem    # в проекте

После этого надо вернуть devServer.https в nuxt.config.ts или откатить коммит.

Связанные артефакты

  • Коммит: 27d5bcbchore: enable local HTTPS dev server via mkcert (ветка chore/local-https-mkcert)
  • Проект: partizap-frontend
  • Конфиг: nuxt.config.ts → devServer.https
  • Игнор: .gitignore → *.pem
  • Плагин, зависящий от стабильной сессии: app/plugins/auth.ts (дёргает GET /health и GET /auth/me при старте)
  • Клиент, читающий CSRF_TOKEN из cookie: app/shared/api/client.ts