Help us understand the problem. What is going on with this article?

LaravelでセッションIDで認証状態をチェックするステートフルなAPIを使う

More than 1 year has passed since last update.

Laravelは標準でAPI用のルーティングなり設定が用意されています。
しかしこれらの設定はトークンを利用して認証状態をチェックするものでありステートレスであることが前提となっています。
サーバサイドでレンダリングして、一部分だけちょろっとAPI経由でデータを取得したいがために、ログインセッションを使って認証済みならデータを取得するAPIを作ろうとしたときにハマったのでメモ。

:smiley: 話すこと

LaravelにおいてセッションIDを利用してサクッとAPI作るやり方。

:expressionless: 話さないこと

ステートレスなAPIがベストなのかステートフルなAPIでもいいのかについて。
またAPIの設計のベストプラクティスについて。

TL;DR

ステートフルなAPIでも要件が許されるなら、ステートフルAPI用の設定を追加すればサクッと実装できます。
ステートフル用のAPIのルーティングファイルroutes/statefulApi.phpの追加、
Providers/RouteServiceProvider.phpで追加したルーティングファイルの読み込み、Http/Kernel.phpに専用のミドルウェアグループの定義、をそれぞれ行います。

環境

名前 バージョン
PHP 7.1.6
Laravel 5.5.14

修正前

以下のようなAPIのルーティング設定があります。

routes/api.php
<?php

use Illuminate\Http\Request;

Route::get('/post/index', 'Api\PostController@index');

コントローラは以下のとおりです。
トークンでの認証といった大仰なことは面倒ですしサクッとAPIを実装したいためcookieに保存したセッションIDで認証済みかチェックしたいので、middlewareはauthを指定しています。

Http/Controllers/Api/PostController.php
<?php

namespace App\Http\Controllers\Api;

use Illuminate\Http\Request;
use App\Post;
use App\Http\Controllers\Controller;

class PostController extends Controller
{
    /**
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth');
    }

    /**
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function index(Request $request)
    {
        return Post::all();
    }
}

Kernel.phpは以下のとおりです。デフォルトのままです。

Http/Kernel.php
<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    /**
     * The application's global HTTP middleware stack.
     *
     * These middleware are run during every request to your application.
     *
     * @var array
     */
    protected $middleware = [
        \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
    ];

    /**
     * The application's route middleware groups.
     *
     * @var array
     */
    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::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',
        ],
    ];

    /**
     * The application's route middleware.
     *
     * These middleware may be assigned to groups or used individually.
     *
     * @var array
     */
    protected $routeMiddleware = [
        'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
        'can' => \Illuminate\Auth\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
    ];
}

cookieに認証済みのセッションIDが保存されている状態で上記のAPIを実行すると401エラーになってしまいます。
それはなぜかというとwebのミドルウェアに\App\Http\Middleware\EncryptCookies::classがあるのですが、これはcookieの暗号化と復号を担っており、ブラウザのcookieに保存されているセッションIDはwebのミドルウェアで暗号化されたセッションIDで、ブラウザからAPIが実行されるときは暗号化された値がリクエストに付与されますが、apiのミドルウェアグループに\App\Http\Middleware\EncryptCookies::classがないため、セッションIDを復号できないために認証のチェックではじかれてしまって401エラーになります。
また、復号だけでなく\Illuminate\Session\Middleware\StartSession::classも必要です。

かといってapiのミドルウェアグループにcookieやsessionのミドルウェアを追加してしまうと、トークンを前提としたステートレスなAPIに余分なものがついてしまいます。
そのため既存のapiを汚さないために新しくステートフルなAPI用の設定を追加します。

ステートフルなAPI用の設定の追加

専用のルーティング設定ファイルを追加します。

routes/statefulApi.php
<?php

use Illuminate\Http\Request;

Route::get('/post/index', 'Api\PostController@index');

上記のルーティング設定ファイルを読み込むようにします。

Providers/RouteServiceProvider.php
<?php

namespace App\Providers;

use Illuminate\Support\Facades\Route;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;

class RouteServiceProvider extends ServiceProvider
{
    /**
     * 省略
     */

    /**
     * Define the routes for the application.
     *
     * @return void
     */
    public function map()
    {
        $this->mapApiRoutes();

        $this->mapWebRoutes();

        $this->mapStatefulApiRoutes();    // ★追加
    }

    /**
     * 省略
     */

// ★↓↓↓↓↓追加
    /**
     * Define the "statefulApi" routes for the application.
     *
     * These routes are typically stateless.
     *
     * @return void
     */
    protected function mapStatefulApiRoutes()
    {
        Route::prefix('api')
             ->middleware('stateful_api')
             ->namespace($this->namespace)
             ->group(base_path('routes/statefulApi.php'));
    }
// ★↑↑↑↑↑追加
}

Kernel.phpにステートフルAPI用の設定を追加します。
apiミドルウェアのものを基本としながらセッションIDで認証させるため
cookie関連のミドルウェアとStartSessionのミドルウェアを追加します。

Http/Kernel.php
    /**
     * The application's route middleware groups.
     *
     * @var array
     */
    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::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',
        ],

// ★↓↓↓↓↓追加
        'stateful_api' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            'throttle:60,1',
            'bindings',
        ],
// ★↑↑↑↑↑追加
    ];

このようにすることによってセッションIDで認証済みかどうかをチェックするようになります。
アクセストークンを発行・管理するのは面倒ですしそこまで必要ではない場合、ステートフルなAPIを使ってみるのもいいかと思います。

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
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  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
ユーザーは見つかりませんでした