在 config/auth.php 裡面就已經描述好兩種認證方式:
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
],
],
如果我們建立 project 之後做了 make:auth 就直接建立 users 資料表的話。
不管怎麼樣 Route::middleware('auth:api') 都是回應 401 認證不會過的。
可以先看到 vendor/laravel/framework/src/Illuminate/Auth/TokenGuard.php 有描述。
public function __construct(UserProvider $provider, Request $request)
{
$this->request = $request;
$this->provider = $provider;
$this->inputKey = 'api_token';
$this->storageKey = 'api_token';
}
public function user()
{
// If we've already retrieved the user for the current request we can just
// return it back immediately. We do not want to fetch the user data on
// every call to this method because that would be tremendously slow.
if (! is_null($this->user)) {
return $this->user;
}
$user = null;
$token = $this->getTokenForRequest();
if (! empty($token)) {
$user = $this->provider->retrieveByCredentials(
[$this->storageKey => $token]
);
}
return $this->user = $user;
}
$token = 這邊會從 request 那裡面找附帶的 token
public function getTokenForRequest()
{
$token = $this->request->query($this->inputKey);
if (empty($token)) {
$token = $this->request->input($this->inputKey);
}
if (empty($token)) {
$token = $this->request->bearerToken();
}
if (empty($token)) {
$token = $this->request->getPassword();
}
return $token;
}
找尋 token 的順序為:
1. 從 query 找 $this->inputKey 參數
2. 從 input 找 $this->inputKey 參數
3. 從 header 找 bearer token
4. 從 header 找 password。註 1
1 和 2 很容易處理的,只要在送 request 之前把參數加一加就可以。
以 1 為例,我們只 url 加上 api_token 這個參數即可。
http://lara.loc/api/v1/stock?api_token=97531
回應的結果:
出現的是 QueryException,原因是 users 的資料表裡面沒有 api_token 這個欄位。
到 users 的資料表裡面加上這個欄位,並且賦予一個 unique 的值就可以了。
Schema::table('users', function (Blueprint $table) {
$table->string('api_token', 60)->unique();
});
app/Http/Controllers/Auth/RegisterController.php
在 User::create 的時候直接亂數產生一個 api_token 給他。
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'api_token' => str_random(60),
'password' => bcrypt($data['password']),
]);
}
並且記得在 app/User.php 裡面,在 $fillable 添加。
protected $fillable = [
'name', 'email', 'password', 'api_token',
];
之後註冊的使用者就會自動產生一組 api_token。
最後再回到我們送出 request 的部分:
axios.post('/api/v1/stock?api_token={{ Auth::user()->api_token }}', form)
.then(function (response) {
console.log(response);
});
就可以順利通過認證。
也可以把 api_token 放在 header 裡面傳送:
axios.defaults.headers.common['Authorization'] = 'Bearer {{ Auth::user()->api_token }}';
axios.post('/api/v1/stock', form)
.then(function (response) {
console.log(response);
});
註 1
vendor/symfony/http-foundation/Request.php
/**
* Returns the password.
*
* @return string|null
*/
public function getPassword()
{
return $this->headers->get('PHP_AUTH_PW');
}
參考資料:
https://gist.github.com/gaillafr/02dc370c120062cf5f23896465f65fd9
2018年2月24日 星期六
2018年2月23日 星期五
Laravel 的 api route 使用 csrf 的認證
當前的 app 可以使用的認證 (auth guard) 都描述在:
以上
app/Http/Kernel.php 裡面..
每一個 guard 對應到的認證 php 描述在:$routeMiddleware
第一個 $middleware 裡面描述的是不管哪一種方式認證都會載入的 class
第二個 $middlewareGroups 是有指定到對應的 group 才會載入的。
Laravel 已經做好了 CsrfToken 的認證,所以我們只要知道怎麼加入使用即可。
首先修改:app/Http/Kernel.php
在 $routeMiddleware 增加一項認證方式。
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,
// your authenticate
'myapi' => \App\Http\Middleware\ApiAuth::class,
'csrf' => \App\Http\Middleware\VerifyCsrfToken::class,
];
因為 csrf 的認證也需要用到 session,所以我們選擇在 $middleware 作增加。
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,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
];
沒有加入這兩行,會跳出 Session 相關的 exception。
接下來把 route 的 middleware 指向 csrf 就可以做認證了。
//following Which require authentication ................
Route::group(
[
'prefix' => 'v1',
'middleware' => 'csrf'
],
function() {
Route::get('stock',function(){
return response([1,2,3,4],200);
});
Route::post('stock', 'StockController@fetch')->name('home');
}
);
[
'prefix' => 'v1',
'middleware' => 'csrf'
],
function() {
Route::get('stock',function(){
return response([1,2,3,4],200);
});
Route::post('stock', 'StockController@fetch')->name('home');
}
);
這邊要注意一點,內建的 VerifyCsrfToken 預設 GET 是直接 pass,所以如果 GET 也想要做認證的話,要自己 override public function handle($request, Closure $next)。
原始的方式在:
vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php
public function handle($request, Closure $next)
{
if (
$this->isReading($request) ||
$this->runningUnitTests() ||
$this->inExceptArray($request) ||
$this->tokensMatch($request)
) {
return $this->addCookieToResponse($request, $next($request));
}
throw new TokenMismatchException;
}
/**
* Determine if the HTTP request uses a ‘read’ verb.
*
* @param \Illuminate\Http\Request $request
* @return bool
*/
protected function isReading($request)
{
return in_array($request->method(), ['HEAD', 'GET', 'OPTIONS']);
}
{
if (
$this->isReading($request) ||
$this->runningUnitTests() ||
$this->inExceptArray($request) ||
$this->tokensMatch($request)
) {
return $this->addCookieToResponse($request, $next($request));
}
throw new TokenMismatchException;
}
/**
* Determine if the HTTP request uses a ‘read’ verb.
*
* @param \Illuminate\Http\Request $request
* @return bool
*/
protected function isReading($request)
{
return in_array($request->method(), ['HEAD', 'GET', 'OPTIONS']);
}
以上
訂閱:
文章 (Atom)
