Laravel+JWTで認証付きAPIをつくる
JWTとは?
JWT = JSON Web Token
特徴としては、
で、すごいざっくりいうと改竄ができない、URLに埋め込むことができるTokenのこと
4について詳しく書くと トークンを使った認証はサーバー側で認証状態を管理しなく、送られてきたトークンを検証することしかしない。 サーバー側で認証状態をどう扱い、管理していくかという問題を考えずにすむことができるということ。
JWTの構成要素
※ Base64とは、すべてのデータをアルファベット(a~z, A~z)と数字(0~9)、一部の記号(+,/)の64文字で表すエンコード方式のこと
3つの要素が.
で繋がれた形になっている。
{ヘッダー}.{クレームデータ(JSONの中身)}.{電子署名}
ヘッダー
{ 'alg': 'HS256', 'typ': 'JWT' }
ヘッダ情報には、署名アルゴリズムの種類やメタ情報が記載されている。
alg:電子署名に使われているアルゴリズムを指定するキー
typ:トークンタイプ。JWT以外使うかは不明
※alg
は、HS256やRS256などがありますが、RS256を使用すると脆弱性があるようです。
RS256 でTOKENを発行する仕組みになっている場合、攻撃者が HS256 のアルゴリズムで公開鍵(利用用途的に公開されているので誰でも取得可能)を用いて TOKEN を発行するとチェックする際に HS256 を用いてチェックされ KEY は同じ公開鍵を利用するためチェックが通ってしまうことになり、改ざんが可能になるということみたいです。
※ 参考 JWTライブラリの危機的な脆弱性について調べた - Qiita
クレームデータ
JSONの中身が入っている。デコードするだけで内容を確認できる。
※デコードするだけで中身が全部見れてしまうので、見えてはいけない情報を含めるのは回避すべき
予約済みのキー名が存在している。以下はその一覧
キー名 | 役割 |
---|---|
exp | 期限切れ日時 |
ndf | 期間開始日時 |
iat | JWT発行日時 |
iss | 発行者(サーバ側)の識別子 |
aud | 利用する側(クライアント側)の識別子 |
電子署名
電子署名部分はデータ改竄されているかどうかをチェックする際に使われている。
JWT認証の流れ
- クライアントが認証情報(user_idやpassword)を送信する。
- サーバが認証情報を取得してuser_idやexpを含む、JSONを秘密鍵で暗号化してJWTとして返却する。
- クライアントはこのJWTを使って通常のAPI リクエストを行なう。
- サーバは、秘密鍵を使ってJWTを検証し、ログインIDをJSONから取り出し、処理を行なう。
クライアントから送信されるJWTはサーバで秘密鍵を使って改竄が行われたかを検証することができる為、user_id
やexp
の改竄を検知する事ができる。
LaravelでのJWTの使い方
認証機能を導入
make:authコマンドでユーザー登録、ログイン、パスワードリセットのベースを構築することができる。
php artisan make:auth
上記コマンドでマイグレーションファイルも作ってくれるのでmigrationをする
php aritsan migrate
composerでjwt-authをインストール
まずはjwt-authを入れる。
composer require tymon/jwt-auth 1.0.0-rc3
※Laravel5.5以上は、1.0以上のjwt-authが必要なのでバージョンを指定する
次にjwt-authの初期設定を行う
php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"
これを行うことで、/config/
配下にjwt.phpが作成されます。
※php artisan vendor:publishを何故行うのか
JWTAuthが持つconfigファイルは、vendor配下に設置されている。直接このファイルを変更しても今後パッケージが更新されると変更内容が失われてしまう。
そのため、このようなconfigファイルはPublish(内容を変更してもいいディレクトリにコピー)して使用する。
php artisan vendor:publish
を行うことで/config
配下にjwt.phpという形で変更可能なconfigファイルが作成される。
jwt-authが使えるシークレットキーを生成する。
php artisan jwt:secret
.env
に以下のように行が追加されたか確認する
JWT_SECRET=XXXXXXX
jwt-authの設定
以下のようにconfig\app.phpのproviders配列とaliases配列にjwt-authを登録する。
config/app.php
'providers' => [ ... Tymon\JWTAuth\Providers\LaravelServiceProvider::class, ], 'aliases' => [ ... 'JWTAuth' => Tymon\JWTAuth\Facades\JWTAuth::class, 'JWTFactory' => Tymon\JWTAuth\Facades\JWTFactory::class, ],
Guardを修正
guardは認証を管理するドライバークラスです。
apiでjwt-authを使うのでdriver:jwt
に変えます。
'guards' => [ ... 'api' => [ 'driver' => 'jwt', 'provider' => 'users', ], ],
Userモデルを編集
Userモデルを以下のように編集する。
<?php namespace App; use Illuminate\Notifications\Notifiable; use Illuminate\Foundation\Auth\User as Authenticatable; use Tymon\JWTAuth\Contracts\JWTSubject; class User extends Authenticatable implements JWTSubject { use Notifiable; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'name', 'email', 'password', ]; /** * The attributes that should be hidden for arrays. * * @var array */ protected $hidden = [ 'password', 'remember_token', ]; /** * Get the identifier that will be stored in the subject claim of the JWT. * * @return mixed */ public function getJWTIdentifier() { return $this->getKey(); } /** * Return a key value array, containing any custom claims to be added to the JWT. * * @return array */ public function getJWTCustomClaims() { return []; } }
Laravelの認証機能を使うためにAuthenticatableを継承し、jwt-authのインタフェースをJWTSubjectを通じて実装する。
getJWTIdentifier
はjwtのトークンを取得する処理。
getJWTCustomClaims
はクレームデータ(jwtの中身)に情報を追加したい時に使用する。
認証用コントローラの作成
認証用のコントローラAuthController
を作成する。
<?php namespace App\Http\Controllers; use App\Http\Controllers\Controller; use Illuminate\Http\Request; use JWTAuth; use Tymon\JWTAuth\Exceptions\JWTException; class AuthController extends Controller { public function login(request $request) { $credentials = $request->only('email', 'password'); try { if (! $token = JWTAuth::attempt($credentials)) { return response()->json(['error' => 'Unauthorized'], 401); } } catch (JWTException $e) { return response()->json(['error' => 'could_not_create_token'], 500); } return response()->json(compact('token')); } public function me() { $user = JWTAuth::parseToken()->authenticate(); return response()->json(compact('user')); } }
login
はリクエストからemailとpasswordをとってJWTAuth::attemptメソッドで認証し、
失敗した場合は401、成功した場合はトークンを発行して返す処理。
me
はJWTを付与してリクエストすると、ユーザー情報を返す処理。
最後に、ルーティングを定義する。
$router = app('Illuminate\Routing\Router'); $router->group(['middleware' => ['guest:api']], function () use ($router) { $router->post('/login', 'AuthController@login')->name('auth-login'); }); $router->group(['middleware' => ['auth:api']], function () use ($router) { $router->get('/me', 'AuthController@me')->name('auth-me'); });
以上で、Laravel+JWTで認証付きAPIを実装することができる。