【Laravel】はじめてのAPI構築(第4回) OAuth2編
前回、具体的なデータを投入した上で、シンプルなAPIを構築してみました。
前回作成したものは、公開してしまえば誰でもアクセスができる状態にあります。実際にそのような誰でもアクセスが可能なタイプのAPIも存在しています。検索系のAPIは認証(認可)がないものも沢山あると思います。
しかしながら、例えば「決済」であったり、独自サービスの「ログイン」であったり、他人に使用させる必要がないものについては、当然アクセスが可能な状態にしておくことを避けなければなりません。
そのために今回は認証について作業してみたいと思います。
認証(認可)の方法
GETリクエストにkeyを含める
外部の公開APIを利用したことがある方は、GET(POST)パラメータにkeyを含めるAPIを利用したことがあるかもしれません。
このkeyというのは大抵、APIの使用登録をする際に発行してもらい、アクセスする度にkeyパラメータも付与してアクセスするというものです。
認可という意味では、最も軽いと言えます。これに関しては、常にアクセス時にkeyが付与されており、keyが覗けてしまうと簡単に成りすますことが可能なので、セキュリティを高めるというよりは、誰がアクセスしているかを管理する目的が大きいと言えます。
重要な処理には、このようなkeyのみによる認可で済ますことはできません。
OAuth2 password grant
今回は、OAuth2による認可(password grant)を試してみます。「OAuth2」「password grant」とは何かについては、ここでは詳しく触れませんのでググってみてください。
ちなみに、OAuthを調べる際に、twitter、google、facebookなどのOAuthログインのクライアント側の記事も沢山でてくると思いますが、今回トライするのは、OAuthを提供する側(OAuthサービスプロバイダー)のところなので、混乱しないように注意してください。
構築手順
実装
# Passportをインストール
composer require laravel/passport
# マイグレーション
php artisan migrate
#「パスワードグラント」クライアント作成(今回は利用しないが、「パーソナルアクセス」クライアントも作成される)
php artisan passport:install
-------------------------------------------------------
Personal access client created successfully.
Client ID: 1
Client secret: 5uR3K1cITqwyVGbLnzy0DvC71R0cNkuO8IyskY98
Password grant client created successfully.
Client ID: 2
Client secret: U3eNL5hTgDp7RxKnRb0y4Z2OOBPB9S5GOgOaK3vk
-------------------------------------------------------
次に、User
モデルへHasApiTokens
トレイトを追加します。
今回は認証時に使用するユーザー情報は、プロジェクト作成時に生成されるUserモデル(usersテーブル)を利用しますが、別のテーブルで行う際には、モデルを自作してあげれば問題ありません。
<?php
namespace App;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\HasApiTokens;
class User extends Authenticatable
{
use Notifiable, HasApiTokens;
protected $fillable = [
'name',
'email',
'password'
];
protected $hidden = [
'password',
'remember_token'
];
protected $casts = [
'email_verified_at' => 'datetime'
];
}
次に、AuthServiceProviderのbootメソッドにて、Passport::routesを呼び出す記述を追加します。また、発行されるトークンの有効期限も設定しておきます。(何も設定しないと1年間有効なトークンが発行されるようです)
<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Laravel\Passport\Passport;
class AuthServiceProvider extends ServiceProvider
{
protected $policies = [
// 'App\Model' => 'App\Policies\ModelPolicy',
];
public function boot()
{
$this->registerPolicies();
Passport::routes();
Passport::tokensExpireIn(now()->addDays(10));
Passport::refreshTokensExpireIn(now()->addDays(30));
}
}
次に、auth.phpにてapi用のドライバ設定をします。
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],
ここまで書けたら、routeを下記のように修正して、アクセスに認可を必須としてあげます。
<?php
Route::group(['middleware' => ['auth:api']], function(){
Route::get ( '/blog', 'ApiController@blog' );
});
ダミーユーザー作成
初期状態のままでユーザー登録がされていないので、APIアクセス用のダミーユーザーを作成します。
php artisan tinker
-------------------------------------------------------
Psy Shell v0.10.12 (PHP 7.4.13 — cli) by Justin Hileman
-------------------------------------------------------
>>> factory(App\User::class, 1)->create();
-------------------------------------------------------
<warning>PHP Deprecated: Since fakerphp/faker 1.14: Accessing property "name" is deprecated, use "name()" instead. in /Users/supi/work/test/api/vendor/symfony/deprecation-contracts/function.php on line 25</warning>
<warning>PHP Deprecated: Since fakerphp/faker 1.14: Accessing property "safeEmail" is deprecated, use "safeEmail()" instead. in /Users/supi/work/test/api/vendor/symfony/deprecation-contracts/function.php on line 25</warning>
=> Illuminate\Database\Eloquent\Collection {#3406
all: [
App\User {#3402
name: "Dr. Alford Bradtke",
email: "rodriguez.holden@example.com",
email_verified_at: "2021-12-13 11:40:21",
#password: "$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi",
#remember_token: "QH2314l4oj",
updated_at: "2021-12-13 11:40:21",
created_at: "2021-12-13 11:40:21",
id: 1,
},
],
}
-------------------------------------------------------
>>> exit
Exit: Goodbye
これでダミーユーザーが作成されました。emailとpasswordを使用しますので、コピペしておきましょう。
パスワードは、デフォルトではpasswordとなっています。
POSTMAN
早速リクエストしてみます。今回はリクエストするためのツールとしてPOSTMANを使用しています。
他のツールを利用したり、コマンドで実行したりしても問題ないです。
/oauth/token
APIにアクセスするためのトークンを取得します。
- メソッド : POST
- URL : /oauth/token
- grant_type : passwordという文字列を指定
- client_id : passport:installで作成したPassword Grant ClientのID
- client_secret : passport:installで作成したPassword Grant Clientのsecret
- username : ダミーユーザーのemail
- password : ダミーユーザーのパスワード(password)
- scope : (空)
上記を参考に設定して、Sendボタンを押します。
無事にアクセストークンが取得できています!やりましたね!!
/api/blog
前回は認可なくアクセスできましたが、今はトークンなしではアクセスできない状態となっています。
取得したトークンを使って、以下のような設定をしてみましょう。
- メソッド : GET
- URL : /api/blog
- Accept : application/json
- Authorization : Bearer (取得したアクセストークン)
リクエストしてみましょう。
無事に取得できましたね。このようにアクセスを行ってAPIを使っていきます。
リフレッシュ
アクセストークンを取得した時に、もう1個refresh_tokenというものが発行されています。これは、アクセストークンを更新する際に利用します。最初にやってIDとパスワードによるアクセストークンの取得は可能な限り行わず、refresh_tokenによる更新でアクセストークンを取得しましょう。
grant_typeがrefresh_tokenとなっており、新たにrefresh_tokenというパラメータが増えていますね。
また、最初と違うのは、usernameとpasswordを必要としないということです。これによって、再度新しいアクセストークンが取得できます。こちらも試してみてください。
注意点
application/json
アクセストークンがない場合、どのような挙動になるのか確認したい人もいるかと思いますが、ブラウザから/api/blogにアクセスすると、下記のようなエラー画面が出るかもしれません。
これは、ブラウザからアクセスする際には、ヘッダに、「Accept : application/json」がないことで発生します。POSTMANなどのツールを使って、必ず「Accept : application/json」がある状態で確認をしてください。
いやちょっと待って!ブラウザから叩いてしまったユーザーにはその画面見せちゃうんですか?と疑問に思ったアナタ。なかなか良い質問です。
APIは、あるプログラムから呼び出して、受け取った値をプログラム中で利用しますので、ブラウザからアクセスができるようにする必要は必ずしもありません。その上でアクセスしてしまったユーザーに何が表示されるかですが、本番環境ではDEBUGモードがfalseになるので、下記のような公式エラー画面になります。
これで問題はないと思いますが、嫌な場合には独自でエラー画面を作成することも可能ですし、JSONで「Unauthenticated」のメッセージを返してあげることも出来ます。(WEBアプリとAPIの混合アプリケーションの場合には、それぞれ影響があると思うので、注意して行う必要はある)
OAuth2 まとめ
今回の作業でPassword Grantによる認可が可能となり、より本格的な形となりました。はじめてのAPI構築シリーズとしては、今回で一旦終わりとなります。
(未来に何か付け足す可能性はあります)
総じて、そこまで複雑なことをやらずに実現できたのではないでしょうか。あとは個々の実装に集中できますね!余談ですが、APIは重要な処理が集中しますし、細かい開発が頻繁に入ることが多いと思いますので、「見やすいコードを書くこと」と「ユニットテストを必ず書くこと」を超おすすめしますw
それではまた。