方便每一頁都能自動包含 api_token 我們採取這樣的做法:
先在最基本的 layout 裡面加入 meta data,可以直接修改
resources/views/layouts/app.blade.php
<!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}">
@if (auth()->check())
<meta name="api-token" content="{{ auth()->user()->api_token }}">
@endif
當使用者是認證過的,每一頁就會塞 token 在裡面。
另外在 bootstrap.js 裡面加入 header 資訊
resources/assets/js/bootstrap.js
let token = document.head.querySelector('meta[name="csrf-token"]');
if (token) {
window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
} else {
console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
}
let api_token = document.head.querySelector('meta[name="api-token"]');
if (api_token) {
window.axios.defaults.headers.common['Authorization'] = 'Bearer ' + api_token.content;
}
修改過了 bootstrap.js 所以我們要重新 compile 來產生 public/js/app.js 之後頁面才會正常作用。生產的方式請參考下方的資料。
新的 app.js 生成之後記得重新載入頁面,以免因為 cache 的關係而導致結果不如預期。
接著我們使用的 axios 送 request 就會自動在 header 裡面帶上 api_token 囉。
參考資料:
https://laravel.com/docs/5.6/mix
2018年2月24日 星期六
Laravel 使用 auth:api 來做 route api 的認證
在 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
'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
訂閱:
文章 (Atom)
