Akamai EdgeWorkersでオリジンサーバーの処理を変更せずにトークン認証を実現する

## 有効期限つきURLでコンテンツを保護
CDN はオリジンサーバーをオフロードする有効な手段です。認証が必要なコンテンツでも、CDN で認可してキャッシュを活用できます。多くのメディアやエンタープライズ系のお客様は Akamai の Auth Token 2.0 の機能を利用して、課金対象コンテンツや認証必須のコンテンツを配信しています。簡単に説明すると、Auth Token は期限付きのトークンを発行し、URL の中に含んだトークンを CDN で認可する仕組みとなります。

Auth Token を利用するためには、オリジンサーバー側でトークンを発行するための実装が必要になります。しかし、エッジコンピューティングによって、オリジンサーバーの改修を不要にして、同等な認証認可が実現できます。本記事は、オリジンサーバー側でトークンの生成とURLに付与する作業を Akamai EdgeWorkers で代替できることを説明します。

トークンが付与されたURLのリクエストは Akamai CDN の Edge Auth 2.0 の機能で検証され、認可できるか判断されます。

:::note
参考
[How To: Configure Akamai Token Authentication and Generate Tokens with Node.js](https://www.linkedin.com/pulse/how-configure-akamai-token-authentication-generate-tokens-elissen/)
[EdgeWorkers:Akamaiのエッジサーバー上でJavascriptで書いたコードが実行可能に](https://www.akamai.com/ja/blog/developers/edgeworker)
:::

### 全体の処理の流れ
Akamai CDNを利用しているオリジンサーバー上でトークンを処理する場合のトークン発行〜認証の全体のフロー図は以下のようになります。
![sequence_all_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2911525/65be77d9-0796-3a9c-0a44-db39206277c8.png)


## クライアントがページをリクエストしてからトークンが付与されたページを受け取るまでの流れ
### オリジンサーバーでトークンを発行する場合 (EdgeWorkersを使わないパターン)
オリジンサーバーにて既存の処理を変更し、トークンを生成してリンクにトークンが付与された状態のページを返却する必要があります。
![sequence_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2911525/1d96c6d2-830c-a434-2d13-6700554c78dd.png)


### EdgeWorkersを使う場合
以下はEdgeWorkersを使った処理の流れです。オリジンサーバーではトークンを生成する必要がありません。
![sequence_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2911525/ed55152a-8729-8abf-29ac-82b776d6cb64.png)
こちらのパターンではトークンを付与する前の状態のページをCDNのEdgeサーバーでキャッシュしておくことでオリジンへのアクセスを減らすことができますので、パフォーマンスの向上とオリジンサーバーの負荷低減も期待できます。トークンをオリジンサーバーで生成しないためその処理の負荷も低減できます。

## 実際の設定

### CDNの設定
トークン認証でコンテンツを保護する設定を行います。これで/premium-contents/配下へのアクセス時にクエリストリングパラメータ __token__ に正当なトークンが必要になります。トークンがない、または不正な場合は403エラーを返します。
![auth_settings.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2911525/21ad80c0-3799-c9f2-8474-5462348bc182.png)
ここで設定した __*Encryption key*__ をユーザ変数にも格納します。これはトークンを生成する際に必要な暗号化キーをEdgeWorkersから参照するためのものです。
![variables.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2911525/c6535fa9-acbc-203f-c7d4-4d357cd7904b.png)


トークンを付与する必要があるページでEdgeWorkersを起動するよう設定します。
![ew.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2911525/64406eba-5eb7-01a8-261c-62cbd2ddf10f.png)

### EdgeWorkersのコード
HTTPリクエストを発行してページを取得し、Aタグのhref属性にトークンを付与するサンプルです。コード中でimportしている *edgeauth.js* は[こちら](https://github.com/Achuthananda/TokenAuthEdgeWorker/tree/master/utils)です。

```javascript
import { httpRequest } from 'http-request'
import { createResponse } from 'create-response'
import EdgeAuth from './edgeauth.js'
import { HtmlRewritingStream } from 'html-rewriter'

export async function responseProvider(request) {
    // ページを取得
    const response = await httpRequest(`https://${request.getVariable("PMUSER_SUBREQ_HOST")}${request.path}`)

    // トークンを生成
    const token = new EdgeAuth({
        key: request.getVariable("PMUSER_TOKEN_KEY"), // プロパティから暗号化キーを取得
        windowSeconds: 60 * 60 * 24 * 30
    }).generateACLToken("/*")

    // リンクにトークンを付与する
    const rewriter = new HtmlRewritingStream()
    rewriter.onElement("a", element => {
        let href = element.getAttribute("href")
        if (href) {
            href += (href.indexOf("?") >= 0 ? "&" : "?") + `token=${token}`
            element.setAttribute("href", href)
        }
    })

    return createResponse(
        response.status, 
        {}, 
        response.body.pipeThrough(rewriter))
}
```

## おわりに
このように簡単な実装でトークン認証が実現できました。オリジンサーバー側の改修が不要であることは大きなメリットであると考えます。
処理をどこで行うのが最適なのか、エッジコンピューティングという選択肢も含め検討してみてはいかがでしょうか。