laravel
laravel5.7

Laravel5.7で標準?のAPI認証を試す

LaravelのAPIに認証を付ける方法としてはPassportやjwt-auth等の外部ライブラリを利用する方法がありますが、「そもそも標準の認証機能って何よ」ということで試してみます。


標準の認証機能

あまり記述が見当たりませんが、認証用のテーブルにapi_tokenってカラムを足して、その値をHTTPヘッダのAuthorization: Bearer の値として送ってやるのが標準?みたいですね(違ってたらご指摘を)。

Guardで下記のように定義されているやつ。

'api' => [

'driver' => 'token',
'provider' => 'users',
],


準備と実装


migrationファイルの編集

では、テーブルを作成します。標準で用意されているmigrationファイルにapi_tokenを追加します。

60文字にしていますが、それより短くても長くてもOKなようです。

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateUsersTable extends Migration
{

public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
+ $table->string('api_token',60)->unique()->nullable();
});
}

/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('users');
}
}


migrate

migrateします。

php artisan migrate


ユーザーの追加

認証用のユーザー(token)を追加します。

めんどいのでtinkerで追加します。api_tokenには60文字の乱数(乱文字)をセット。

$ php artisan tinker

Psy Shell v0.9.8 (PHP 7.2.7 — cli) by Justin Hileman
>>>
>>>
>>> $user = new App\User;
=> App\User {#2898}
>>> $user->name = 'user1';
=> "user1"
>>> $user->email = 'user1@test.com';
=> "user1@test.com"
>>> $user->password = Hash::make('testtest');
=> "$2y$10$o3Grx73i7PaNV1ptGyukDetQM0FJjxYbXmHdWyNSH0TYK9m2ssf2e"
>>> $user->api_token = str_random(60);
=> "p1zQhxuL2EaNUVUZiMYYPi1iXLWW9R8RLwbMFHjBJmxLgUkkOn1DZApuuobo"
>>> $user->save();
=> true

準備は以上です。サーバを立ち上げておきます。

php artisan serve


動作確認


リクエストしてみる

リクエストもめんどいのでcurlで。

api_tokenに登録した値をAuthorization: Bearer {token}という形で添付して送ります。

APIとしては標準で記述されている認証付きAPIルートである/api/userを利用してみます(リクエストしたユーザの情報を返す)。


API処理としてLaravelに認識させるためにAccept: application/jsonヘッダも追加しています。


curl -H 'Accept: application/json' -H 'Authorization: Bearer p1zQhxuL2EaNUVUZiMYYPi1iXLWW9R8RLwbMFHjBJmxLgUkkOn1DZApuuobo' http://localhost:8000/api/user


レスポンス

あっさりOK。

でも、tokenの内容が見えてるのでいちおう隠します。

{"id":1,"name":"user1","email":"user1@test.com","email_verified_at":null,"created_at":"2018-10-05 22:02:33","updated_at":"2018-10-05 22:02:33","api_token":"p1zQhxuL2EaNUVUZiMYYPi1iXLWW9R8RLwbMFHjBJmxLgUkkOn1DZApuuobo"}


hiddenにapi_tokenを追加

User.phpのhiddenにapi_tokenも追記してやります。

protected $hidden = [

'password', 'remember_token', 'api_token',
];

見えなくなりました。

{"id":1,"name":"user1","email":"user1@test.com","email_verified_at":null,"created_at":"2018-10-05 22:02:33","updated_at":"2018-10-05 22:02:33"}


認証エラーを発生させてみる

tokenの値を少し変えてリクエストしてみます。

ハンドリングされていないエラーが出ます。標準では認証がNGをだとloginに飛ばされるようになっていますが、「loginなんて無いぞ」と言われています。make:authで必要な設定をしてもエラーは消えますが、APIリクエスト認証時のエラーを追加することにします。

{

"message": "Route [login] not defined.",
"exception": "InvalidArgumentException",
.
.
.


とりあえずエラーをでなくする

loginって(名前の)ルートがないぞ!と怒られるので、とりあえずloginって名前のルートを作ります。

めんどいので既にある/にname()で名前つけます。


routes/web.php

Route::get('/', function () {

return view('welcome');
})->name('login');

それかエラーの原因となっているリダイレクトを消してもいいです。


app/Http/Middleware/Authenticate.php

protected function redirectTo($request)

{
// return route('login');
}


再度リクエスト

再度(エラーになるよう)リクエストしてみます。API認証エラー時の標準メッセージが出ています。

{"message":"Unauthenticated."}


エラーメッセージを編集する

標準メッセージを変更したい場合はHandle.phpをオーバーライドします。


app/Exceptons/Handle.php

<?php

namespace App\Exceptions;

use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;

+use Request;
+use Response;
+use Illuminate\Auth\AuthenticationException;

class Handler extends ExceptionHandler
{

protected $dontReport = [
//
];

protected $dontFlash = [
'password',
'password_confirmation',
];

public function report(Exception $exception)
{
parent::report($exception);
}

public function render($request, Exception $exception)
{
return parent::render($request, $exception);
}

//追加
+ public function unauthenticated($request, AuthenticationException $exception)
+ {
+ if($request->expectsJson()){
+ return response()->json(['error' => 'NG'], 401);
+ }

+ return route('login');
+ }
}


一応確認。

{"error":"NG"}

認証の実装より、エラー処理の方が手間。が、とりあえずは以上です。

Passport等の「おまかせ」系に比べ100%自分で制御できてる感があり、これはこれで安心な感じ。