YesodはCSRF対策のサーバ側処理としてdefaultCsrfMiddleware
というYesod Middlewareを提供している、ということはYesod Advent Calendar 2016|9日目に書きました。
ただ、CSRF対策はサーバ側だけでは当然ながら不充分で、クライアント側にも対処が必要です。ところが困ったことにこのクライアント側で対処がなされていない処理が見あたります。
中でも困るのがログアウト処理で、単純にdefaultCsrfMiddleware
を使うとログアウト処理(=/auth/logoutへのアクセス)でCSRFチェックでNGとなってしまい、ログアウトに失敗してしまいます。
これに対処する1つの方法として、特定パスへのアクセス時にCSRF対策を行わないようにするMiddlewareを作り、defaultCsrfMiddleware
の代わりに使うようにしてみたいと思います。
以下サンプルコード。
{-# LANGUAGE OverloadedStrings #-}
module Lib.Middleware (
csrfMiddleware
) where
import GHC.Base
import Yesod
import Network.Wai (Request(rawPathInfo))
import Data.Textual.Encoding (decodeUtf8)
import Data.Text (Text, isSuffixOf)
csrfMiddleware :: Yesod site =>
Route site -> HandlerT site IO res -> HandlerT site IO res
csrfMiddleware logoutRoute handler = do
path <- getRequestPath
renderFunc <- getUrlRender
logoutPath <- return $ renderFunc logoutRoute
if isSuffixOf path logoutPath then
handler
else
defaultCsrfMiddleware handler
getRequestPath :: MonadHandler m => m Text
getRequestPath = getRequest
>>= return . reqWaiRequest
>>= return . rawPathInfo
>>= return . decodeUtf8
次のような考え方で作っています。
- CSRFチェックを行わないリソースを引数でもらう
- このようなリソースへのリクエストに対してはハンドラ関数をそのまま呼び出す
- それ以外のリソースへのリクエストに対してはYesodが提供している
defaultCsrfMiddleware
を介してハンドラ関数を呼び出す
このYesod Middlewareを使うには、Foundation.hs
の中のdefaultCsrfMiddleware
を使っている行を、以下のように書き換えます。
yesodMiddleware = csrfMiddleware (AuthR LogoutR) . defaultYesodMiddleware
理想的にはCSRF対策が成されていないプログラムを見つけたら修正してPull Requestなりで本家に取り込んでもらうのがいいのでしょうけれども、このような方法もある、ということでご紹介しました。