Laravelは標準でAPI用のルーティングなり設定が用意されています。
しかしこれらの設定はトークンを利用して認証状態をチェックするものでありステートレスであることが前提となっています。
サーバサイドでレンダリングして、一部分だけちょろっとAPI経由でデータを取得したいがために、ログインセッションを使って認証済みならデータを取得するAPIを作ろうとしたときにハマったのでメモ。
話すこと
LaravelにおいてセッションIDを利用してサクッとAPI作るやり方。
話さないこと
ステートレスな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のルーティング設定があります。
<?php
use Illuminate\Http\Request;
Route::get('/post/index', 'Api\PostController@index');
コントローラは以下のとおりです。
トークンでの認証といった大仰なことは面倒ですしサクッとAPIを実装したいためcookieに保存したセッションIDで認証済みかチェックしたいので、middlewareはauth
を指定しています。
<?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は以下のとおりです。デフォルトのままです。
<?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用の設定の追加
専用のルーティング設定ファイルを追加します。
<?php
use Illuminate\Http\Request;
Route::get('/post/index', 'Api\PostController@index');
上記のルーティング設定ファイルを読み込むようにします。
<?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のミドルウェアを追加します。
/**
* 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を使ってみるのもいいかと思います。