Angularアプリ開発の同一オリジンポリシーエラー対策 〜 CORS(オリジン間リソース共有)できるプロキシールートを設定する


2022/03/21
蛸壺の技術ブログ|Angularアプリ開発の同一オリジンポリシーエラー対策

Angularを使ってSPA(シングルページアプリケーション)開発をローカル環境で進めていると、社内のどこかにあるオンプレのサーバー上のデータベースから何かデータを取得したり、別に起動しているDockerコンテナへネットワークアクセスしたりする場合があります。

何もしなければ、以下のような
『同一オリジンポリシー違反』のエラーがブラウザ内部で起こっていることを良く目にするはずです。

合同会社タコスキングダム|蛸壺の技術ブログ

現行のブラウザはセキュリティ強化の観点から、リスエスト元のオリジンとは別のオリジンをまたぐアクセス、いわゆる
『CORS(Cross-Origin Resource Sharing; オリジン間リソース共有)』は問答無用でブロックする仕様になっています。

Angularアプリのローカル環境をしている以上、このブラウザの規約から免れることが出来ないわけですが、ローカル環境でもクロスオリジン出来ないままだと開発者としては非常に不便極まりないことでしょう。

救済措置として、Angularには
「Proxy要求・応答」の機能が備わっています。

今回はこのProxy機能の使い方のポイントを解説します。


同一オリジンポリシー? CORS?

まずは用語の説明を軽くおさらいします。

AngularなどのJavascriptフレームワークでのWebアプリケーション開発を考える上で、「CORS」と「同一オリジンポリシー」は良く理解しておく必要があります。

以下のサイトで詳しい用語の解説がなされていますので、ガッツリと知りたい方はそちらの記事でご確認ください。

参考サイト|CORS & Same Origin Policy 入門

要点だけかいつまむと、同一オリジンポリシーとは、
「スキームかホストかポート番号のどれか一つでも違えば別ドメインだからリソースの直接アクセスは禁止にするね」というブラウザ側にあるルールの話で、これによりCORS(別のオリジンをまたぐリソース共有)は基本的に出来ない、というお話です。

合同会社タコスキングダム|蛸壺の技術ブログ

ブラウザのこの仕組みのおかげで、悪意のある特定のネットワーク攻撃から保護されているため、ブラウザを使う利用者には安心な機能なのですが、Angularアプリ開発者にとってはおいそれと別ドメインのリソースファイルが使えないため非常に不便な仕様です。

社内などでサーバー管理者・ネットワーク管理者が存在している場合は、自分の利用するドメインをCORS出来るように許可してほしいと言えば、許可されたサーバー上のリソースにアクセスできる可能性はあります。

開発版のアプリでそこまで大事にしなくとも、簡単にCORSできる救済措置として、Angular開発標準の
「Proxy機能」が備わっています。

以降ではこの使い方のコツを説明していきます。


Angularのプロキシ機能を使ってみる

今回はAngularのプロキシ機能を説明するため、以下のような簡単な模式図のようなローカルの開発環境を想定してプロキシルートを考えていきます。

合同会社タコスキングダム|蛸壺の技術ブログ

proxy.conf.jsonにプロキシルートを定義する

では早速Angularの開発プロジェクトのルートフォルダにproxy.conf.jsonをいうファイルを新規作成しましょう。

すると必要最小限のファイル構造は以下のようなになっていると思います。

            
            $ tree -I node_modules -L 1
.
├── README.md
├── angular.json
├── browserslist
├── package.json
├── proxy.conf.json
├── src
└── tsconfig.json
        
で、proxy.conf.jsonの中身をここでは以下のように編集します。

            
            {
    "/hoge/*": {
        "target": "http://localhost:23456",
        "changeOrigin": false,
        "secure": false,
        "logLevel": "debug"
    },
    "/piyo/*": {
        "target": "http://192.168.1.123:54321",
        "secure": false,
        "changeOrigin": true,
        "logLevel": "debug",
        "pathRewrite": {
          "^/piyo": "http://192.168.1.123:54321/dev/v1"
        }
    },
    "/fuga/*": {
        "target": "http://far-remote-guest.co.jp:67890",
        "secure": false,
        "changeOrigin": true,
        "logLevel": "debug",
        "pathRewrite": {
          "^/fuga": "http://far-remote-guest.co.jp:67890/prod/api/v2"
        }
    }
}
        
こうすることでブラウザやAngularアプリケーションには同一オリジンで通信が閉じているように見せかけておいて、裏ではしっかりプロキシ通信が動作しているようにすることができます。

現在のオリジン(
http://localhost:12345)が手元のローカル環境を示しているわけですが、同一オリジンポリシーからすると、現在の手元のオリジン以外の別のオリジンのリソースは取りに行くことが出来ないことになります。

が、プロキシ機能が裏で働いていると、

            
            [hogeルート]:
    http://local:12345/hoge/(以下のリソースファイル)
        ---> http://localhost:23456/(以下のリソースファイル)

[piyoルート]:
    http://local:12345/piyo/(以下のリソースファイル)
        ---> http://192.168.1.123:54321/dev/v1/(以下のリソースファイル)

[fugaルート]:
    http://local:12345/fuga/(以下のリソースファイル)
        ---> http://far-remote-guest.co.jp:67890/prod/api/v2/(以下のリソースファイル)
        
というようにルートがマウントされたかのように機能します。

ローカルやリモート問わずアクセスしたい外部APIサーバーが複数あっても、好きなだけプロキシルートを定義することでAngular開発でもCORSを簡単に設定できるのです。

proxy.conf.jsonの書式

折角ですのでプロキシルートの書き方もおさらいしておきます。

なお詳しいドキュメントは公式の内容で確認してください。

参考|バックエンドサーバーへのプロキシ-Angular Document

説明書きのエッセンスだけを汲み取ると、一つのプロキシルートの定義の作法ですが、

            
            //...
{
  "/(プロキシルート名)/*": {
    "target": "(アクセス先の外部ドメイン名)",
    "secure": truefalse,
    "pathRewrite": {
      "^/(プロキシルート名)/": "/(置き換えたいルート先)/"
    },
    "changeOrigin": truefalse,
    "logLevel": "debug"
  }
}
//...
        
というような書式が基本的な設定のテンプレートになります。

各パラメータのザックリとした説明ですが、

            
            /(プロキシルート名)/*:
    プロキシルートを現在のドメインで使うためのの便宜上のパスです。
    ページ等でルーティングに使用していないパスなら自由に定義することができます。
    例えば、/hoge/piyo/*や/hoge/piyo/hoge/*など深い階層にすることも可能です。
    ここでの「*」文字はプロキシルートの中にある全てのリソースにマッチすることを意味しています。

target:
    プロキシ接続先のオリジン名を指定します。
    前述したようにオリジン名は基本として、スキーム名+ホスト名+ポート番号
    の組み合わせです。

secure:
    プロキシ先のドメインのスキームが暗号化されているかを指定します。
    基本的にhttpならfalse、httpsならtrueを設定します。

logLevel:
    angularでは基本的に開発段階でしか使わないので、debugで良いでしょう。

changeOrigin:
    CORSさせることにおいてはもっとも重要なオプションです。
    プロキシ先のオリジンが別のマシーン環境にあるかを指定します。
    基本的にlocalhost(127.0.0.1)で到達できないオリジンならばtrue、
    localhostなど内部のルーティングテーブルに見えるアドレスならばfalse
    で指定します。

pathRewrite:
    プロキシ接続さきのパスを置き換える際に利用します。
    pathRewriteを使わない場合、例えば現在のドメインオリジンをhttp://localhost:1111、
    接続先のドメインオリジンをhttp://remoteguest:2222とすると、
    http://localhost:1111/(プロキシルート名)/ から http://remoteguest:2222/(プロキシルート名)/
    へとプロキシパス変換されてしまいます。
    このような単純なパスの変換ではREST APIなどの仕組みだと、エンドポイントを柔軟に
    書き換えたいときに不便です。
    そこでpathRewriteを使って適切なエンドポイントとなるようにパスの書き換えを行います。
    この例では、
    http://localhost:1111/(プロキシルート名)/ から http://remoteguest:2222/(置き換えたいルート先)/
    とアクセス先が変更されます。
    なお^記号は正規表現で「パス名の文字列の先頭からマッチ」するという意味です。
        
これらのパラメータを適切に設定することでクロスオリジンアクセスを柔軟にローカル環境で実現することが可能です。

なお、Angularのローカルサーバーの中身は、webpack-dev-serverですので、さらに詳しい書式は
webpack-dev-serverのproxy機能で確認できます。

さらに言えば、webpack-dev-serverのプロキシはExpressのプロキシ機能を利用しているので、最終的なプロダクトでプロキシしたい場合には、Expressで設計し直す必要があります。

Expressでのプロキシ機能構築方法に関してはまた別の機会にブログで紹介するつもりです。

プロキシ機能付きでangularのローカルサーバーを起動する

プロキシルートを設定しただけではAngularが自動で認識してくれませんので、package.jsonの中でスクリプトタグのstartを以下のようにコマンドオプションを追記します。

            
            {
    //...中略
    "scripts": {
        "ng": "ng",
        "start": "ng serve --port 12345 --proxy-config proxy.conf.json",
        //...以下略
        
ここではポート番号はAngularのデフォルトで4200ですので、--portオプションでお好みに変えておきます。

ポイントとして、angular-cliの
ng serveコマンドには、--proxy-configを付けることで先程作成したproxy.conf.jsonの内容で内臓のプロキシ機能を使うことが可能です。

後はAngular側の実装でHttpClientモジュールのhttpメソッドがリクエストヘッダを考慮せずともそのまま利用できると思います。

以下は実装の雰囲気だけですが、プロキシルートにhttpアクセスするサービスを作ると、

            
            import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable({
    providedIn: 'root'
})
export class HogeService {

    constructor(private http: HttpClient) { }

    hoge(): Observable<any> {
        return this.http.get('/hoge');
    }

    piyo(): Observable<any> {
        const requestBody: any = { piyo: "piyo" };
        return this.http.post('/piyo', JSON.stringify(requestBody));
    }

    fuga(): Observable<any> {
        return this.http.get('/fuga');
    }

}
        
みたいな感じでAPIアクセスが可能です。

最終的にローカル開発サーバーを立ち上げてみて、CORS出来ているかどうかを確認してください。

            
            $ yarn start
        


まとめ

今回はAngularのローカル開発環境で欠かすことの出来ないプロキシ接続の設定に関して説明していきました。

このテクニックを使うことで、DockerコンテナーやK8sノードで構築したローカル環境でのエコシステムでも容易にAngularアプリ開発を進めることが出来るようになります。

参考サイト

Do you know how to resolve CORS issues in Angular?

angular-cli server - how to proxy API requests to another server?

Angular —バックエンドサーバーにプロキシする方法

記事を書いた人

記事の担当:taconocat

ナンデモ系エンジニア

主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。