ExpressとTypeScript APIにCORSサポートを追加する方法

April 07, 2021
執筆者
Mia Adjei
Twilion
レビュー担当者

add cors support to Express + TypeScirpt JP Header

このBlogはTwilio Developer Voicesチームに所属するMia Adjeiが執筆したこちらの記事を日本語化したものです。

Node.js、Express、TypeScriptを使用してすばらしいAPIを構築したと想像してください。クライアントサイドのWebアプリも完成。ブラウザでアプリケーションを起動しサーバーに接続できる段階まできました。もうすぐ世界中に公開できそうです。

ブラウザでアプリが動作している場所を開き、開発者ツール内のコンソールを開きます。アプリがサーバーに対し最初のAPIコールを実行します。しかし、アプリにデータは反映されず、コンソールにはこのようなエラーが表示されました。

Access to fetch at 'http://localhost:5000/rooms' from origin 'http://localhost:3000' 
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present 
on the requested resource. If an opaque response serves your needs, set the 
request's mode to 'no-cors' to fetch the resource with CORS disabled.

なんと、オリジン間リソース共有(CORS: Cross-Origin Resource Sharing)の制限によりエラーが発生していたのです。多くの開発者はこうしたストレスがたまる状況を経験したことがあると思います。

CORSとは一体、どいうものでしょうか。どうすればこのエラーメッセージを回避し、サーバーからアプリへデータを取得できるのでしょう。このチュートリアルでは、ExpressとTypeScript APIにCORSサポートを追加し、エキサイティングなプロジェクトを前進させ続ける方法を説明します。

オリジン間リソース共有(CORS)とは?

オリジン間リソース共有(CORS)とは、最新ブラウザに装備されているセキュリティプロトコルです。HTTPリクエストを開始したオリジンに応じて、異なるオリジンで共有するリソースの許可、制限を行います。

オリジンは、リクエストが開始された場所を示します。下記のように、オリジンには、必須の2要素(schemehostname)と任意の1要素(port)があります。

https://www.twilio.com
  ^       ^
  |       |
scheme hostname


http://localhost:5000
  ^       ^       ^
  |       |       |
scheme hostname  port

ブラウザは、すべてのリクエストにOriginヘッダーを追加します。リクエストがサーバーに届くと、リクエストのオリジンがリソース取得許可リストに含まれている場合に、サーバーがAccess-Control-Allow-Originヘッダーをレスポンスに追加します。この情報からブラウザはコンテンツがこのオリジンにアクセスできると判断します。

下の例では、すべてのオリジンからのリソースリクエストを許可しています。

Access-Control-Allow-Origin : *

しかし、下のヘッダーは、https://www.twilio.comからのリクエストのみを許可すると、ブラウザに指示しています。

Access-Control-Allow-Origin : https://www.twilio.com

ローカルで開発作業をする間、違うポートを使用する方も多いかもしれません。この記事の最初に出てきたエラーメッセージの例では、アプリがlocalhost:3000で提供されているにもかかわらず、localhost:5000からデータを取得しようとしていることが分かります。2つのポートが異なると、異なるオリジンからリクエストされたことを意味するため、デフォルト設定によりブラウザはこのリクエストを拒否します。

Access to fetch at 'http://localhost:5000/rooms' from origin 'http://localhost:3000' 
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present 
on the requested resource. If an opaque response serves your needs, set the 
request's mode to 'no-cors' to fetch the resource with CORS disabled.

この問題を解決し、サーバーとクライアント間のデータ転送を許可するには、サーバー側にCORSサポートを追加します。このチュートリアルでは、cors npmパッケージを使用してミドルウェアを追加します。このミドルウェアが、Access-Control-Allow-Originヘッダーを設定し、サーバーリソースにアクセスできるドメインを指定します。

>CORSについてさらに詳しく学びたい方は、CORSに関するMozillaのドキュメントを参照してください。

ExpressサーバーにCORSを設定する

まず、以下を準備してください。

このチュートリアルで提供するCORSの情報は、どのExpressプロジェクトにも使用できます。ただし、今回の例に沿って作業を進める場合、上に示したExpressプロジェクトのコードを使用されることをお勧めします。このVideo APIは、ExpressとTypeScriptにより構築されているため、最適なサンプルプロジェクトです。

このサンプルプロジェクトを使用する場合は、リポジトリのREADME.mdにある指示に従い、稼働させてください。

npm run startコマンドを実行するとターミナルにログステートメントが表示され、サーバーが5000番のポートで稼働していることを確認できます。

Express server listening on port 5000

このサンプルではなく、独自のExpress APIサーバーを使用していても問題ありません。この後のcURLリクエストが異なる内容になるため、プロジェクトに合わせてパラメーターを変更してください。

cURLコマンドを使用してシミュレーションを実行する

上記リポジトリのサンプルコードを使用する場合は、2つ目のターミナルウィンドウを起動し、下のcURLコマンドを実行します。

curl -H "Origin: http://localhost:3000" --head http://localhost:5000/rooms

このcURLコマンドを実行すると、ブラウザがどのようにリクエストを発行するかシミュレートできます。この場合、サーバーはhttp://localhost:5000で稼働しています。アプリはオリジンlocalhost:3000で稼働し、そのオリジンからリクエストを出しています。この例では、「アプリ」はビデオチャットルーム一覧をリクエストしようとしています。

ターミナルウィンドウには、下のようなレスポンスが表示されます。

HTTP/1.1 200 OK
X-Powered-By: Express
Vary: Origin
Content-Type: application/json; charset=utf-8
Content-Length: 52
ETag: <ETag>
Date: Tue, 30 Mar 2021 16:45:54 GMT
Connection: keep-alive
Keep-Alive: timeout=5

この出力をよく見ると、レスポンスにはAccess-Control-Allow-Originヘッダーがありません。ブラウザがこのレスポンスを受信していれば、ブラウザはリクエストをブロックしたでしょう。

そこで、サーバー側を更新し、特定のオリジン間リクエストには、Access-Control-Allow-Originヘッダーを付けて応答するようにします。

Expressプロジェクトにcors npmパッケージを追加

ターミナルウィンドウで、プロジェクトのルートに移動します。サンプルコードの場合、プロジェクトのルートディレクトリはexpress-video-apiです。

以下のコマンドを実行し、corsパッケージとそのTypeScript型をインストールします。

npm install cors 
npm install --save-dev @types/cors

package.jsonファイルを開くと、corsdependencyとして追加され、@types/corsdevDependenciesに追加されたことが分かります。

CORSオプションを構成する

アプリのエントリーポイントであるファイルを開きます。サンプルコードを使用している場合、このファイルはsrc/index.tsです。

コードエディタで開き、expressをインポートした行の下にcorsもインポートします。

import cors from 'cors';

app.use(express.json());の上にこの行を追加し、Expressサーバーがcorsミドルウェアの使用を許可するようにします。

app.use(cors); /* NEW */ 

app.use(express.json());

サーバー上のリソースへのアクセスを許可するオリジンリストを追加し、このリストをCORSオプションに渡します。このチュートリアルでは、許可するオリジンとしてlocalhost:3000を追加します。

// Add a list of allowed origins.
// If you have more origins you would like to add, you can add them to the array below.
const allowedOrigins = ['http://localhost:3000'];

const options: cors.CorsOptions = {
  origin: allowedOrigins
};

次に、これらのオプションをcorsミドルウェアの引数として渡します。

app.use(cors(options));

Expressサーバーは、この変更を検出し、更新します。

サンプルプロジェクトの場合、コードは以下のようになります。

import express from 'express';
import cors from 'cors';
import config from './config';
import roomsRouter from './routes/room';
import { Twilio } from 'twilio';

// Initialize Twilio client
const getTwilioClient = () => {
  if (!config.TWILIO_ACCOUNT_SID || !config.TWILIO_API_KEY || !config.TWILIO_API_SECRET) {
    throw new Error(`Unable to initialize Twilio client`);
  }
  return new Twilio(config.TWILIO_API_KEY, config.TWILIO_API_SECRET, { accountSid: config.TWILIO_ACCOUNT_SID })
}

export const twilioClient = getTwilioClient();

const app = express();

// Add a list of allowed origins.
// If you have more origins you would like to add, you can add them to the array below.
const allowedOrigins = ['http://localhost:3000'];

const options: cors.CorsOptions = {
  origin: allowedOrigins
};

// Then pass these options to cors:
app.use(cors(options));

app.use(express.json());

// Forward requests for the /rooms URI to our rooms router
app.use('/rooms', roomsRouter);

app.listen(5000, () => {
  console.log('Express server listening on port 5000');
});

CORSの動作をテストする

これでサーバーにCORSオプションを構成できました。ターミナルで以下のcURLコマンドを使用し、シミュレーションを再度実行してください。

curl -H "Origin: http://localhost:3000" --head http://localhost:5000/rooms

レスポンスを見ると、Access-Control-Allow-Originヘッダーとオリジンhttp:://localhost:3000を確認できます。これは、クライアントサイドのアプリをlocalhost:3000で実行すると、アプリがサーバーからリソースを取得できることを意味します。

HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: http://localhost:3000
Vary: Origin
Content-Type: application/json; charset=utf-8
Content-Length: 52
ETag: <ETag>
Date: Tue, 30 Mar 2021 16:46:39 GMT
Connection: keep-alive
Keep-Alive: timeout=5

以下のcURLコマンドを実行し、もう一度テストしてみましょう。

curl -H "Origin: http://localhost:4000" --head http://localhost:5000/rooms

このコマンドでは、リクエスト内のオリジンをhttp://localhost:4000に変更しています。このホストは、許可されているオリジンには含まれていないためレスポンスにAccess-Control-Allow-Originヘッダーを確認できません。

HTTP/1.1 200 OK
X-Powered-By: Express
Vary: Origin
Content-Type: application/json; charset=utf-8
Content-Length: 52
ETag: <ETag>
Date: Tue, 30 Mar 2021 16:40:47 GMT
Connection: keep-alive
Keep-Alive: timeout=5

結果としてlocalhost:4000で稼働しているアプリからサーバーにアクセスしようとしても、リソースにアクセスすることはできません。コンソールには、最初に示したようなCORSエラーが表示されます。

Express APIプロジェクトで次にすべき事

このチュートリアルで使用したサンプルコードの最新版を確認したい方は、GitHubリポジトリにあるadded-corsブランチをご覧ください。ご自身のアプリにCORSを実装する方法の資料は、こちらのgistを確認してください。

ExpressとTypeScriptサーバーにCORSサポートを追加する方法をご理解いただけたでしょうか。これで、サーバーとクライアント側アプリケーションを連係させる設定ができました。皆さんは、どのようなアプリをお考えでしょうか。何を構築されるか、とても楽しみです。

Mia Adjeiは、Developer Voicesチームのソフトウェア開発者です。開発者が新しいプロジェクトの構想を練り、ひらめきを感じる瞬間に出会えるようサポートしています。Miaへのご連絡は、madjei [at] twilio.comまでどうぞ。