この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
サーバーレス開発部改めCX事業本部の岩田です。 Amplify Reactを使ってCognitoユーザープールにサインインし、払い出されたトークンを使ってSwagger UIからAPIを実行できる環境を作ったので、構築手順等ご紹介します。
環境
今回利用した環境です
- Node.js v8.10
- Swagger UI 3.23.0
- React 15.6.2
- AWS Amplify 1.1.29
- AWS Amplify React 2.3.9
概要
現在開発中の案件では以下のような環境でAPIの開発を行なっています。
- API Gatewayの定義にはSwaggerを利用
- Swagger UI × S3の静的ウェブサイトホスティングで上記Swaggerの定義を公開して顧客と共有
- APIの認可にはCognitoユーザープールを利用
ちなみに、Swagger UIとはこんなツールです
見たことのある方も多いのではないでしょうか?
Swagger UIを利用することで、顧客側からも簡単にAPIをテストできるのですが、Cognitoによる認可を付けると、途端に面倒になります。APIを実行する前にサインインし、払い出されたトークンをAPIのリクエストヘッダーにセットする必要があります。
この辺りの手順をもっと楽チンにするべくCognitoとSwagger UIの連携について調べたところ、既にブログ化されていたのですが、今回は
- 顧客側にCognitoのアプリクライアントIDを入力してもらう必要がある
- Swaggerを使ってAPI GatewayにCognitoオーソライザーを定義する場合、
type: oauth2
ではなくtype: apiKey
として定義する必要があり、API Gatewayの定義をそのまま顧客と共有している今回の構成にマッチしない
という理由から採用を見送り、別のやり方を模索することにしました。 色々と考えた結果、Amplify Reactでサクっとサインイン画面を用意しつつ、サインイン後にSwagger UIの画面に遷移、以後のリクエストには払い出されたトークンを自動的に設定するという方式を選択しました。
API Gatewayの準備
では、ここから実際に環境を作っていきます。 あまり詳細まで解説しませんので、不明点があればこのあたりの記事も合わせてご参照下さい。
- AWS Amplify+Angular6+Cognitoでログインページを作ってみる ~バックエンド編~
- UnityでCognito UserPoolsから得たトークンでリクエストし、API Gatewayでsubを受け取る
サンプルをもとにPetStoreAPIを作成し、Cognitoオーソライザーの設定を行います
今回はList all pets
APIを変更し、Cognitoオーソライザーで認可後、後続のLambdaに処理を引き渡すよう設定しました。Lambdaはevent
オブジェクトをJSONに変換して返却するよう実装しています。
ひと通り準備できたら、APIをデプロイしてSwaggerの定義をJSONで出力しておきます。
出力したJSONファイルは静的ウェブサイトホスティングを有効化したS3バケットにアップしておきます。 アップできたら、バックエンド側は準備完了です。
Swagger UIの環境作成
続いてフロント側を準備します。
まずはアプリのひな形作成とライブラリの導入です。
$ create-react-app myapp
$ cd myapp
$ npm install aws-amplify aws-amplify-react swagger-ui
準備できたらApp.jsを編集します
App.js
import React, {Component} from 'react';
import SwaggerUi, {presets} from 'swagger-ui';
import 'swagger-ui/dist/swagger-ui.css';
import Amplify, {Auth} from 'aws-amplify';
import {withAuthenticator } from 'aws-amplify-react';
Amplify.configure({
Auth: {
region: 'ap-northeast-1',
userPoolId: '<CognitoのユーザープールID>',
userPoolWebClientId: '<CognitoのアプリクライアントID>',
}
});
class App extends Component {
componentDidMount() {
Auth.currentSession()
.then(data => {
const idToken = data.getIdToken().getJwtToken();
SwaggerUi({
dom_id: '#swaggerContainer',
url: 'https://<Swaggerの定義をアップしたS3バケット名>.s3-ap-northeast-1.amazonaws.com/PetStore-dev-swagger.json',
presets: [presets.apis],
requestInterceptor: (req) =>{
console.log(req);
// S3の静的Webサイトホスティングに対してAuthorizationヘッダーでIDトークンを送るとエラーになるので
// S3へのリクエストの時はリクエストを加工しない
if (req.url.indexOf('s3-ap-northeast-1.amazonaws.com') !== -1){
return req;
}
req.headers.Authorization = idToken;
return req;
}
});
});
}
render() {
return (
<div className="App">
<header className="App-header">
</header>
<div id="swaggerContainer" />
</div>
);
}
}
export default withAuthenticator(App, true);
ポイントとなるのはcomponentDidMount
の処理です。
Auth.currentSession()
.then(data => {
const idToken = data.getIdToken().getJwtToken();
の部分で、Cognitoから払い出されたIDトークンを取得します。
次に以下のコードでSwagger UIのコンポーネントを作成します。
SwaggerUi({
dom_id: '#swaggerContainer',
url: 'https://<Swaggerの定義をアップしたS3バケット名>.s3-ap-northeast-1.amazonaws.com/PetStore-dev-swagger.json',
presets: [presets.apis],
requestInterceptor: (req) =>{
console.log(req);
// S3の静的Webサイトホスティングに対してAuthorizationヘッダーでIDトークンを送るとエラーになるので
// S3へのリクエストの時はリクエストを加工しない
if (req.url.indexOf('s3-ap-northeast-1.amazonaws.com') === -1){
req.headers.Authorization = idToken;
}
return req;
}
});
requestInterceptor
を実装することで、デフォルトのリクエストオブジェクトを加工することができます。ここではリクエストヘッダーのAuthorization
に先ほど取得したIDトークンを設定しています。
また、S3の静的Webサイトホスティングに対してAuthorization
ヘッダーでIDトークンを送るとAuthorization header is invalid -- one and only one ' ' (space) required
という403エラーが返ってくるので、S3向けにはAuthorization
ヘッダーをセットしないように制御しています。
上記の実装だと、1時間後にCognitoのIDトークンの有効期限が切れるとリクエストが発行できなくなります。本来は
requestInterceptor = (req)=>{
return Auth.currentSession()
.then(data => {
// // S3の静的Webサイトホスティングに対してAuthorizationヘッダーでIDトークンを送るとエラーになるので
// // S3へのリクエストの時はリクエストを加工しない
if (req.url.indexOf('s3-ap-northeast-1.amazonaws.com') === -1){
const idToken = data.getIdToken().getJwtToken();
req.headers.Authorization = idToken
}
})
}
のようにAmplifyにトークンを自動リフレッシュさせたいのですが、requestInterceptorにPromiseを返す関数を設定するとSwagger UIに表示されるCURLコマンドが正しく生成されないという不具合があるため、タイムアウトについては我慢しています。
https://github.com/swagger-api/swagger-ui/issues/4778
実装できたらnpm start
して動かしてみましょう。
いつものサインイン画面が出てくるので、サインインすると、、、
Swagger UIの画面が表示されました!!
Cognitoオーソライザーを設定したList all pets
のAPIを叩いてみます
リクエストヘッダーにAuthorization
が設定できていることが分かります
開発者コンソールにもリクエストの情報が出力されています
レスポンスもちゃんと返ってきてますね!!
まとめ
Swagger UI標準の Authorize
が表示されっぱなしになっているのが気になりますが、まあ一般公開するページではないので我慢することにします。
Swaggerのドキュメントによると
OIDC is currently not supported in Swagger Editor and Swagger UI. Please follow this issue for updates.
とのことなので、Swagger UIがOIDCにも対応して、もっと簡単かつ良い感じにCognitoと一緒に使えると良いですね!!