Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
23
Help us understand the problem. What is going on with this article?
@horikeso

LaravelのCSRFトークンをワンタイムトークン化

More than 3 years have passed since last update.

LaravelのCSRFトークンは画面を更新しても毎回同じ結果、CSRF
対策には物足りなさを感じたり、連打したら何回もリクエストが通ってしまうことを防ぎたいので考えてみました。

普通に別のトークンをhiddenに入れてチェックを行えば目的を達成できるのですがせっかくCSRFトークンがあるのでそれをいじってみる方法にしました。

フォーム画面で確認した程度なので不具合はあるかもしれません。

ポイント

ミドルウェアのVerifyCsrfTokenでトークンをリフレッシュしても画面に出てくるトークンには反映されない、もっと上のレベルでリフレッシュを行う必要がありました。

実装

app\Http\Middleware\OneTimeCsrfToken.php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Cache;

class OneTimeCsrfToken
{
    public function handle($request, Closure $next)
    {
        // トークンをリフレッシュ
        $request->session()->regenerateToken();

        $key = $request->session()->getId().'_cache_token';
        $input_key = $request->session()->getId().'_input_token';

        if ($request->method() !== 'POST')
        {
            Cache::forget($key);
            // 画面に表示されるトークンを保持
            $input_token = $request->session()->token();
            Cache::put($input_key, $input_token, 1);
        }
        else
        {
            // POST時の初回のみキャッシュにリフレッシュしたトークンを載せる
            $cache_token = Cache::get($key);
            if (is_null($cache_token))
            {
                $cache_token = $request->session()->token();
                Cache::put($key, $cache_token, 1);
            }
        }

        return $next($request);
    }
}
app\Http\Middleware\VerifyCsrfToken.php
<?php

namespace App\Http\Middleware;

use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
use Illuminate\Support\Facades\Cache;

class VerifyCsrfToken extends Middleware
{
    /**
     * The URIs that should be excluded from CSRF verification.
     *
     * @var array
     */
    protected $except = [
        //
    ];

    /**
     * Determine if the session and input CSRF tokens match.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return bool
     */
    protected function tokensMatch($request)
    {
        $key = $request->session()->getId().'_cache_token';
        $input_key = $request->session()->getId().'_input_token';

        $cache_token = Cache::get($key);
        $input_token = Cache::get($input_key);

        $token = $this->getTokenFromRequest($request);

        // キャッシュに載せたリフレッシュしたトークン(POST時の初回のみ)とリフレッシュしたトークンが同じ場合は
        // 初回アクセスなのでチェックを通過させる
        if ($cache_token === $request->session()->token())
        {
            // フォーム画面からきたトークンと同じトークンか判定し、同じ場合はチェックを通過させる
            if ($token === $input_token)
            {
                // 強制的に通過させる為にsessionのトークンをinputのものと同じにする。
                $request->session()->put('_token', $token);
            }
        }

        $result_flag = is_string($request->session()->token()) &&
               is_string($token) &&
               hash_equals($request->session()->token(), $token);

        return $result_flag;
    }
}
app\Http\Kernel.php
protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
+            \App\Http\Middleware\OneTimeCsrfToken::class,
            // \Illuminate\Session\Middleware\AuthenticateSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

        'api' => [
            'throttle:60,1',
            'bindings',
        ],
    ];
23
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
horikeso
個人的な備忘録ばかりですが、よろしくお願いします。難しいことはよくわかりません!

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
23
Help us understand the problem. What is going on with this article?