Архитектура модуля passport/auth

Документ описывает слои модуля, порт интеграции с Passport и ключевые классы.

1. Слои

graph TD
    AC[AuthController] -->|InitiateSsoLoginHandler / HandleSsoCallbackHandler| APP[application]
    APP -->|PassportAuthPort| HTTP[PassportHttpClient]
    APP -->|SyncUserHandler| CORE[core]
    MW[PassportAuthMiddleware] -->|PassportAuthPort| HTTP
    MW -->|создаёт| ID[YiiIdentity (ядро)]
    AC -->|куки| BR[Браузер]
    HTTP -->|HTTP| PASS[Passport API]

2. Порт PassportAuthPort

interface PassportAuthPort
{
    public function buildAuthorizationUrl(string $state): string;          // GET /auth/authorize
    public function exchangeAuthorizationCode(string $code): PassportTokenDto; // POST /token
    public function refresh(string $refreshToken): PassportTokenDto;          // POST /token (refresh)
    public function getUser(string $passportId, string $accessToken): PassportUserDto; // GET /users/{id}
    public function logout(?string $cookieHeader, ?string $authorization): array;    // GET /auth/logout
}

3. Реализация PassportHttpClient

Файл: modules/passport/auth/infrastructure/http/PassportHttpClient.php

Конфигурация (конструктор)

АргументОткуда
Client (yii\httpclient)DI
baseUrlparams.php['passport_url']
clientIdparams.php['oauth2_client_id']
clientSecretparams.php['oauth2_client_secret']
redirectUriparams.php['app_url'] . '/auth/callback'
scopesparams.php['scopes']
userEndpoint/users/{id} (hardcodе)

Методы

МетодHTTPПараметрыОтвет
buildAuthorizationUrl—stateстрока URL
exchangeAuthorizationCodePOST /tokengrant_type=authorization_code, codePassportTokenDto
refreshPOST /tokengrant_type=refresh_tokenPassportTokenDto
getUserGET /users/{id}BearerPassportUserDto (с обогащением: post через /posts, owner через /roles)
logoutGET /auth/logoutCookie/Authorizationмассив status/headers/body

Поведение getUser

  1. Запрос GET {base}/users/{id} с Bearer-токеном.
  2. Парсит item/data (фолбэки).
  3. post_id → доп. запрос GET {base}/posts?ids={post_id} для названия должности.
  4. is_owner:
    • из data.isOwner / data.is_owner;
    • или по roles: если встретили запись с name === 'owner' — true;
    • иначе по role_id'ам → GET {base}/roles?ids=... ищем роль 'owner'.
  5. Возвращает PassportUserDto.

Парсинг токенов (parseTokenResponse)

4. DI (config/di.php)

  1. Проверяет обязательность OAUTH2_CLIENT_ID, OAUTH2_CLIENT_SECRET, OAUTH2_PUBLIC_KEY_PATH (иначе RuntimeException).
  2. Client (yii\httpclient\Client c CurlTransport).
  3. PassportAuthPort → PassportHttpClient.
  4. InitiateSsoLoginHandler → построение authorize URL.
  5. HandleSsoCallbackHandler → обмен кода + SyncUserHandler (ядро).
  6. PassportAuthMiddleware → JWT-проверка на запрос.

5. Обработчики

InitiateSsoLoginHandler

handle(InitiateSsoLoginCommand $command): string
// → passport->buildAuthorizationUrl($command->state)

HandleSsoCallbackHandler

handle(HandleSsoCallbackCommand $command): PassportTokenDto
  1. code пустой → RuntimeException.
  2. hash_equals(expectedState, state) — защита CSRF.
  3. exchangeAuthorizationCode($code) → токены.
  4. passportClaims($accessToken) — декодирование payload (без проверки подписи; проверка — в middleware).
  5. Берёт sub, вызывает getUser($sub, $accessToken).
  6. SyncUserHandler->handle(new SyncUserCommand(...)) — синхронизация локального пользователя (и роли).
  7. Возвращает токены (контроллер кладёт их в куки).

6. AuthController (presentation)

ДействиеПутьПоведение
actionLoginGET /auth/loginstate-кука + authorize URL + HTML-редирект
actionCallbackGET /auth/callbackstate-проверка, обмен кода, SyncUser, Set-Cookie токенов, HTML-редирект на фронтенд
actionLogoutGET /auth/logoutproxy в Passport + чистка кук

Детали потока — flow.md, безопасность — security.md.