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

沒有留言:

張貼留言