FastlyでURL token 認証を利用する ２ 


:::note info
この記事は Enabling URL token validation : https://docs.fastly.com/guides/tutorials/enabling-url-token-validation 
を元に作成しています。最新の情報についてはdocsの情報をご参照下さい。
:::

[FastlyでURL token 認証を利用する](https://qiita.com/AtsushiFukuda/items/8406a1f5aa725be43c9f) では特定のURLを認証するTokenをクエリストリングでFastlyに送るサンプルを作成しましたが、今回はもう少し実際のサービスでもありそうな事例を考えて、それにあった設定を作成してみます。

要件としては以下の様なものと仮定します。
・TokenはクエリストリングではなくHTTP リクエストヘッダー `token` に含めてクライアントからFastlyに送信
・Token は特定の URL ではなく、特定の Path 配下で同じものが有効となる
・認証対象は特定 Path 配下の画像のみとする

具体的には
http://www.example.com/token_auth/usera/index.html
http://www.example.com/token_auth/usera/image01.jpg
http://www.example.com/token_auth/usera/image02.jpg

http://www.example.com/token_auth/userb/index.html
http://www.example.com/token_auth/userb/image01.jpg
http://www.example.com/token_auth/userb/image02.jpg

というような構成のページがあるとして、useraのTokenを持つユーザーは同一のTokenで /token_auth/usera/ 配下の画像をすべて見ることが出来るが、/token_auth/userb/ パス配下の画像にはアクセス出来ない、というイメージを想定しています。

おさらいですが、FastlyのToken認証の基本的な機能は以下となります。
「Token認証を利用することで、特定の期間のみアクセス可能なURLを作成することが出来ます。TokenはWebアプリケーション側で作成され、クエリストリングの形でユーザーに提供されます。リクエストはFastlyサーバーで認証され、Tokenが正しい場合のみコンテンツが配信されます。指定した期間を経過するとTokenは無効になります。」

それでは早速VCLのコードを書いていきたいと思います。コードはクエリストリングでTokenを渡すコードをもとにして、必要な部分を一部修正しています。


## Token認証用のVCLコード
VCLに自分でコードを直接記載したい場合は、Snippet、またはカスタムVCLを利用しますが、特別な理由がないのであれば手軽なSnippetの利用をお勧めします。Token認証の場合は`vcl_recv`内に以下のようなコードを記載します。

```
# only do this once per request
if (req.url ~ "^/token_auth" && req.url.ext ~ "(?i)^(gif|png|jpg|jpeg|webp)$" && req.request != "FASTLYPURGE" && fastly.ff.visits_this_service == 0 && req.restarts == 0) {
  declare local var.token STRING;
  declare local var.token_expiration STRING;
  declare local var.token_signature STRING;
  declare local var.to_sign STRING;
  declare local var.token_path STRING;

  # extract token from request header
  set var.token = req.http.token;

  # make sure there is a token
  if (var.token == "") {
    error 403;
  }

  # make sure there is a valid expiration and signature
  if (var.token !~ "^(\d{10,11})_([a-f0-9]{40})$") {
    error 403;
  }

  # extract token expiration and signature
  set var.token_expiration = re.group.1;
  set var.token_signature = re.group.2;
  
  # extract the path to authenticate from req.url
  set var.token_path = req.url.dirname; 

  # calculate string to sign
  set var.to_sign = var.token_path + var.token_expiration;

  # make sure the signature is valid
  if (!digest.secure_is_equal(var.token_signature, regsub(digest.hmac_sha1(digest.base64_decode("mysecret"), var.to_sign), "^0x", ""))) {
    error 403;
  }

  # make sure the expiration time has not elapsed
  if (time.is_after(now, std.integer2time(std.atoi(var.token_expiration)))) {
    error 410;
  }
}
```

上記のサンプルVCLコード内の`mysecretkey` は独自のシークレットキー(秘密の文字列)に変更し、外部にもれないように管理して下さい。シークレットキーが外部にもれると、Tokenを生成して認証を突破することが可能になります。

TokenはリクエストにHTTPリクエストヘッダー `token` の値としてリクエストに付与する必要があります。Tokenの値のフォーマットは`[expiration]_[signature]`となり、具体的には以下のようなフォーマットとなります。

    1441307151_4492f25946a2e8e1414a8bb53dab8a6ba1cf4615

### VCLでの処理内容

VCLでの処理について上から順に説明していきます。

```
if (req.url ~ "^/token_auth" && req.url.ext ~ "(?i)^(gif|png|jpg|jpeg|webp)$" && req.request != "FASTLYPURGE" && fastly.ff.visits_this_service == 0 && req.restarts == 0) {
```

リクエストが/token_auth 配下、かつ拡張子がgif, png, jpg, jpeg, webpの場合に Token 認証の対象としています。

```
  # extract token from request header
  set var.token = req.http.token;
```
クエリストリングではなく、リクエストヘッダーに設定された `token` から取得した値を Token とします。

```
  # extract token expiration and signature
  set var.token_expiration = re.group.1;
  set var.token_signature = re.group.2;
```
その後 Token が存在するかやフォーマットを確認し、問題がなければ有効期限とsignitureを変数に代入します。


```
  # extract the path to authenticate from req.url
  set var.token_path = req.url.dirname; 
```

認証用のパス情報を var.token_path に保存します。ここでは`req.url.dirname`を利用して、リクエストURLの最後の "/" の手前までを取得しています。
この処理の結果、
`/token_auth/usera/image01.jpg`
`/token_auth/usera/image02.jpg`
などのURLの場合、`var.token_path` には同一の `/token_auth/usera` が保存されます。

その後はリクエストに付与されていたSignatureとVCLで生成する文字列が同一かどうかを判断します。
その際に `req.url` ではなく`var.token_path`を利用している点以外は通常の認証する処理と同じになります。


## アプリケーション側で必要な設定

Fastlyを利用してToken認証を実施するためには、アプリケーション(Webサーバー)側でTokenを生成するためのコードを追加する必要があります。Fastlyでは各種言語での[Tokenコードを生成するコードのサンプル](https://github.com/fastly/token-functions)をGitHub上で提供しています。これらのコードの利用してTokenを生成して下さい。

ポイントとしてはTokenを生成する際に指定するPathを、上記のVCLで`var.token_path` に保存するPathと同じものにして下さい。Pathの内容が異なっていると生成されるSignatureも異なりますので認証がエラーとなります。

Token を生成したら、生成されたTokenをクライアントのアプリケーションなどから `token` という名前の HTTP リクエストヘッダーとしてFastlyサーバーに送信するようにして下さい。


## Token認証のテスト
テストは以下のようなコマンドを利用して行うことが可能です。生成したTokenをリクエストに設定して意図した通りに認証が行われるかを確認して下さい。

```
curl -svo /dev/null -H "token:<generated_token>" http://<your_domain>/token_auth/usera/image1.jpg
curl -svo /dev/null -H "token:<generated_token>" http://<your_domain>/token_auth/usera/image2.jpg
curl -svo /dev/null -H "token:<generated_token>" http://<your_domain>/token_auth/userb/image1.jpg
curl -svo /dev/null -H "token:<generated_token>" http://<your_domain>/token_auth/userb/image2.jpg
```

## まとめ
今回の記事ではtoken情報をHTTPリクエストヘッダーという形で送信していますが、もちろん別の方法（例えばCookieで渡したり、リクエストのパスに加えるなど）でtokenを送信することも可能です。
また、認証対象の範囲などもVCLを利用して自由にコントロールすることが出来ます。必要な要件に応じて設定を作成してみて下さい。