25
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[SPA]脆弱性を潰して認証情報を扱う[アクセストークン]

Last updated at Posted at 2020-07-13

最近、新規事業で完全0-1のプロダクト開発をした際に
Rails/Vue.jsのSPAでいかにセキュアに認証情報を扱うかについて調べた結果を書いていきます
「それおかしくない?」とか「脆弱性つぶせてなくない?」とかあればコメントで指摘していただけるととても助かります

どう実装するか

実装の意図や詳細については下の方で解説します

Rails側

  • 認証情報をsecure属性とhttponly属性をつけたcookieでフロントエンドに返す
    • domain属性はAPIのドメインのみにする(特に設定変えなければデフォルトでそうなってるはず)
  • 独自ヘッダの有無のチェックを認証処理に組みこむ
  • gemrack-corsを導入する
    • originsをSPAのURLのみに制限する
    • credentialstrueにしておく

Vue.js側

  • withCredentialsオプションをtrueに設定する(axiosやxhr)
    • fetchならcredentials: 'include'とかかな
  • リクエストに独自ヘッダをつける
    • なんでもいいし中身も空文字でよくて、ヘッダがあることが重要

なぜ上記のような実装をするのか

以下の3つがポイントになります

  • httponly属性とsecure属性が有効でdomain属性にはAPIのドメインのみを設定したcookieでアクセストークンを持たせる
  • 独自ヘッダが必要であること
  • corsでoriginsを制限されていること

cookieについて

なぜhttponlyにするのか

  • XSSや悪意あるパッケージなどのjsによりcookieにあるトークンを抜かれないようにするため
    • jsから抜かれる危険性はlocalstorageを使わない理由でもある
  • 代わりに、cookieにあるトークンを非同期通信でもリクエストするためにwithCredentialsオプションを有効にする

なぜsecureにするのか

  • 非SSLのリクエストを盗聴されないように、とかっていうよくある理由

なぜdomainをAPIのドメインのみに制限するのか

  • サブドメをワイルドカードにしたりとか他のドメインを許容する必要がないため
    • 少なくとも今回のケースでは複数ドメインをまたいでcookieの共有は必要なかった
    • withCredentialを有効にしてcookieとして飛ばすからフロントエンドのjsからcookieを操作できる必要はない
      • フロントのurlからはcookie見えないけどAPIのドメインへのcookieとしてちゃんと存在しているのでwithCredential有効にしておけばちゃんとリクエストされる
  • ワイルドカードを入れようものならXSSで攻撃者のサーバにリクエストされたりした時にcookieが一緒に飛んでしまう
  • 恥ずかしながら自分は最初「フロントのドメインでも扱えなきゃいけないよなぁ」と思い込んでいました。。。

独自ヘッダについて

  • htmlのformによるCSRF対策
    • htmlのform(同期通信)では独自ヘッダを付与することはできないため

CORSについて

  • 独自ヘッダと併用してプリフライトリクエストを強制することでjsによるCSRF対策となる
    • originsでちゃんと制限かけることで外からの不正なリクエストを弾く
      • ただし、それだけでは不十分で、実際はブラウザ上ではエラーになるがリクエスト自体は飛んで処理されてしまうので「弾く」という表現は誤解を招く
      • レスポンスを見せないので不正なGETには有効だが単純リクエストのPOSTは処理されてしまうので困る
    • POSTは条件次第(formDataを使うこと)で単純リクエストになり、プリフライトリクエストが走らない場合があるため独自ヘッダによりプリフライトを強制させる
      • これにより外部サイトからの不正なリクエストをすべてプリフライトリクエストで止める事ができる
  • 独自ヘッダは2度美味しい
  • ちなみに、rack-corsの設定でcredentials: trueにしないとフロントでwithCredential: trueのリクエストを弾いてしまう

実装のサンプル

一部抜粋して書いていきます
参考記事などは下の方にまとめてます

rails側

rack-corsなどの設定

config/application.rb
# ...割愛

    # apiモードのrailsでcookiesを使えるようにするため
    # コントローラ側に `include ActionController::Cookies` も必要
    config.middleware.use ActionDispatch::Cookies

    config.middleware.insert_before 0, Rack::Cors do
      allow do
        # 複数のorigins制限するためドメインの配列
        # 環境変数はjsonにすると配列が簡単に扱えるやんという最近見つけたtips
        origins JSON.parse(ENV.fetch('CORS_DOMAINS_JSON') { '[]' })
        resource "*",
          # axiosなどで `withCredential: true` にした上で、そのcookieを受け取るため
          credentials: true,

          headers: :any,
          methods: [:get, :post, :patch, :delete]
      end
    end

# ...割愛

認証情報をcookieで返す

  • アクセストークンの期限はDBにあるのでpermanent
  • 開発環境はsecure外したいので環境変数COOKIE_SECUREで操作
# ...割愛

cookies.permanent[:access_token] = {
  value: access_token,
  httponly: true,
  secure: ENV['COOKIE_SECURE'].present?,
}
cookies.permanent[:refresh_token] = {
  value: refresh_token,
  httponly: true,
  secure: ENV['COOKIE_SECURE'].present?,
}

# ...割愛

認証処理

access_token = cookies[:access_token]

# ...割愛(access_tokenチェックの処理など)

raise 適当な例外クラス if request.headers[:'X-REQUESTED-BY-MY-APP'].nil?

Vue.js側

withCredentialsを有効にする

src/main.js
// ...割愛

Vue.prototype.$http = axios.create(
  { baseURL: process.env.VUE_APP_API_URI, withCredentials: true },
);

// ...割愛

独自ヘッダをつける

  • Vue.prototype.$httpにインスタンス入れることでどこからでもthis.$httpで使える
  • interceptorsを使うことですべてのリクエストでヘッダ設定の処理を走らせる
src/main.js
// ...割愛

Vue.prototype.$http.interceptors.request.use((request) => ({
  ...request,
  headers: {
    ...request.headers,
    ...{ 'X-REQUESTED-BY-MY-APP': '' },
  },
}));


// ...割愛

疑問

XSS脆弱性があっても認証情報を抜かれないための対策とか、外からのCSRFの対策はしたけど
XSSでXHRのコードをインジェクトされて外部ではなく本来のバックエンドに勝手にリクエストを飛ばされないようにするための対策ってどうすればいいんだろう。。。
根本的にXSSを塞ぐしかないってことになるのかな

参考記事

rails-apiでcookieを使う
RailsでAPIにCORSを設定する
rack-corsでCORS設定をする
さよならCSRF(?) 2017
CORS: OPTIONSリクエスト(preflight request)を避ける

25
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?