2018年2月27日 星期二

Laravel 利用 git 發佈到 server 上運行

在本機端修改好的 laravel project 可以利用 git 來發布到其他地方去跑。

本地端的 git project 在 push 到 git server 之後不會有下列檔案和目錄:
1. node_modules/
2. vender/
3. .env

當你是第一次在其他地方 clone 了 project 那要注意幾個地方。
1. 透過 composer install 把需要的東西都裝上。
如果當地沒有 composer 就採用下面的命令裝一下。
使用 curl 指令下載:
curl -sS https://getcomposer.org/installer | php
如果沒有安裝 curl ,也可以用 php 指令下載:
php -r "readfile('https://getcomposer.org/installer');" | php

2. 確認 storage/ 以及 bootstrap/cache/ 這兩個目錄是 http access 可以寫入的,如果沒有設定就會出現 exception 。

3. config/app.php 裡面的 'key' => env('APP_KEY', 'YOUR_APP_KEY') 要記得填上,沒填上也是會出現 exception,網路上有人寫說在本地端產生 .env 然後再用 php artisan key:generate 是比較麻煩的做法,這樣每一次發布到不同的地方都要記得建才行。

這邊如果有了解到怎麼配合 .env 的檔案來區分 production 以及 development 的環境就不會每次都要產生無謂的檔案。

以 APP_NAME 為例:
我在本機開發環境在 .env 裡面設了 APP_NAME="Sammixoft mac",而在 config/app.php 裡面將 'name' => env('APP_NAME', 'Sammixoft'), 做這樣的設定。在名稱上就說明了是在哪個環境做事了。

再執行 php artisan config:cache 將 config 重建即可。

最後如果有使用 database 的話就記得再執行 php artisan migrate。

以上

2018年2月24日 星期六

Laravel 將 axios 的 header 預設包含 Authorization

方便每一頁都能自動包含 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



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

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');
    }
);

這邊要注意一點,內建的 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']);
}

以上

2018年2月19日 星期一

Laravel 為已存在的 table 新增欄位

首先建立一份新的 migration
php artisan make:migration add_paid_to_users --table="users"
後面加了 --table 的參數,產生出來的檔案會幫忙把 schema 加進去。
出來檔案會長這樣。



接著再把要修改的欄位加進去
Schema::table('users', function($table) {
    $table->string("title");
    $table->text("description");
    $table->timestamps();
});

最後執行 php artisan migrate 就可以了。

非常不建議在原來的 migration 檔案裡面修改欄位後執行 php artisan migrate:refresh
這會把原本的資料都給清除!!

Laravel 執行 php artisan migrate 出現 SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes

如果執行的 DB 是 mysql 5.7.7 以上的話是不會發生這問題的。

但如果是跑 MariaDB 或是比較舊版的 mysql 就會出現這個錯誤。



網路上找到的解決方法:
修改 app/Providers/AppServiceProvider.php 這個檔案。

use Illuminate\Support\Facades\Schema;
public function boot()
{
    Schema::defaultStringLength(191);
}