「CakePHP4 で Swagger3 を使って API ドキュメントを作る」で APIドキュメントを作ったんですが、「CakePHP4 の認証処理 cakephp/authentication で Auth0 を使った認証を行う」を行ったら、Swagger UI からの API 実行で認証エラーが発生するようになりました。
認証があっても Swagger UI から API 実行を行えるように対応します。
この記事でわかること
- CakePHP4 で認証を行った Swagger3 のAPI呼び出し方法。
- CakePHP4 cakephp/authentication で複数の Authenticator の指定方法。
- この記事内のソースは以下で公開しています。
バージョン情報
バージョン | |
---|---|
CakePHP4 | 4.0.6 |
cakephp/authentication | 2.1.0 |
zircote/swagger-php | 3.0.3 |
事前準備
- 以下の記事のソースから発展させてます。
- docker-compose を実行し、コンテナを立ち上げます。
docker-compose up -d
APIドキュメント
認証の記述追加
共通のドキュメント内容を記述している ./src/Controller/Api/swagger.php に認証の記述を追加します。
SecurityScheme を記述して、HTTPヘッダ「X-Api-User-Local」に JSON形式でユーザー情報を渡せるようにします。
あくまで、ローカルで実行した場合の対応です。本番環境ではこのようなユーザー情報の渡し方は動かないようにします(無効化します)。
そのあと、すべての API に SecurityScheme で定義した認証を利用するように OpenApi security を記述します。
記述方法については、以下の issue が参考になりました。
// ... snip
/**
* @OA\SecurityScheme(
* securityScheme="api_user_local",
* type="apiKey",
* in="header",
* name="X-Api-User-Local",
* description="ローカルで実行した場合のユーザー情報 ({""sub"":""DUMMY""})",
* ),
* @OA\OpenApi(
* security={
* {
* "api_user_local":{}
* },
* },
* ),
*/
CORS の対応
X-Api-User-Local ヘッダの追加
ローカル開発環境で実行された場合、 CORS に対応したヘッダ情報を付与してレスポンスを返す ./src/Middleware/CorsMiddleware.php
を変更します。
Access-Control-Allow-Headers ヘッダとして返却する値に、 X-Api-User-Local
を追加して、 X-Api-User-Local
ヘッダを API呼び出し時に指定できるようにします。
class CorsMiddleware implements MiddlewareInterface
{
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$response = $handler->handle($request);
// ローカル開発モードのときのみ CORS に対応する
if (filter_var(env('SERVER', false))) {
return $response;
}
$response = $response->withHeader('Access-Control-Allow-Origin', '*');
$response = $response->withHeader('Access-Control-Allow-Methods', '*');
$response = $response->withHeader('Access-Control-Allow-Headers', 'Content-Type, X-Api-User-Local');
$response = $response->withHeader('Access-Control-Max-Age', '172800');
return $response;
}
}
OPTIONS リクエスト時は認証を行わないようにする
OPTIONS リクエストの処理を行っている ./src/Controller/Api/CorsController.php
を変更します。
OPTIONS リクエストは、認証を行わないようにするために beforeFilter()
で $this->Authentication->addUnauthenticatedActions()
を指定します。
class CorsController extends AppController
{
public function beforeFilter(EventInterface $event): void
{
parent::beforeFilter($event);
$this->Authentication->addUnauthenticatedActions(['options']);
}
public function options(): void
{
// ローカル開発モードのときのみ CORS に対応する
if (filter_var(env('SERVER', false))) {
throw new NotFoundException('Not support CORS.');
}
$this->viewBuilder()->setOption('serialize', []);
}
}
Authenticator の追加
SwaggerAuthenticator の作成
ローカル開発環境のときのみ、ヘッダ「X-Api-User-Local」による認証を認めます。
ユーザー情報は、ヘッダ「X-Api-User-Local」に JSON 形式でセットされていることを想定しています。
class SwaggerAuthenticator extends AbstractAuthenticator
{
protected $_defaultConfig = [];
public function authenticate(ServerRequestInterface $request): ResultInterface
{
// ローカル開発モードのときのみ Swagger によるアクセスを許可するため
// ローカル開発モードのとき以外は、ユーザー情報なしで返却する
if (filter_var(env('SERVER', false))) {
return new Result(null, Result::FAILURE_OTHER, ['Not support Swagger.']);
}
$user = $request->getHeaderLine('x-api-user-local');
if (!$user) {
return new Result(null, Result::FAILURE_CREDENTIALS_MISSING);
}
return new Result(json_decode($user, true), Result::SUCCESS);
}
}
Application.php の修正
上記で作った SwaggerAuthenticator を利用するように ./src/Application.php
を修正します。
API のときのみ SwaggerAuthenticator を利用するように $authenticationService->loadAuthenticator('Swagger')
を行います。
Authenticator は記述された順番で呼び出されます。
Auth0Authenticator より前に SwaggerAuthenticator を呼び出すようにします(今回のケースだと、どちらの Authenticator が先に処理されても問題なく動作します)。
public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
{
$path = $request->getUri()->getPath();
$isApi = (strpos($path, '/api/') === 0);
if ($isApi) {
return $this->getAuthenticationServiceForApi();
} else {
return $this->getAuthenticationServiceForPage();
}
}
private function getAuthenticationServiceForApi(): AuthenticationServiceInterface
{
$authenticationService = new AuthenticationService([
'unauthenticatedRedirect' => null,
'queryParam' => 'redirect',
'identityClass' => function ($identityData) {
return new Identity($identityData, [
'fieldMap' => [
'id' => 'sub',
],
]);
},
]);
$authenticationService->loadAuthenticator('Swagger');
$authenticationService->loadAuthenticator('Auth0');
return $authenticationService;
}
private function getAuthenticationServiceForPage(): AuthenticationServiceInterface
{
$authenticationService = new AuthenticationService([
'unauthenticatedRedirect' => '/users/login',
'queryParam' => 'redirect',
'identityClass' => function ($identityData) {
return new Identity($identityData, [
'fieldMap' => [
'id' => 'sub',
],
]);
},
]);
$authenticationService->loadAuthenticator('Auth0');
return $authenticationService;
}
動作確認
上記の対応で Swagger UI (僕は、 VSCode の Swagger Viewer
を使っています) からは認証を経由して API を呼び出すことができます。
デフォルトのテンプレートの場合、右上にある Authorize ボタンからユーザー情報を JSON形式(例えば、 {"sub":"DUMMY"}
)で入力すると、各 API を呼び出すことで確認できます。