# 登入驗證

## 設定

很多時候我們都需要網站的登入功能，因為實在太常用了，所以 Laravel 直接就實作了整個登入機制，讓我們可以很快速的建立登入功能。

### 設定檔

```
app/config/auth.php
```

在這個檔案中，預設了一個可以儲存使用者的 Model 及資料表的名稱，

```
'model' => 'User',
'table' => 'users',
```

你可以在 app/models 中找到 User.php 這個 Model。而資料表的名稱就叫 users。如果有需要可以自行修改。

！重要，在建立 users 資料表時，必須建立一個 remember\_token 的字串欄位，長度 100、nullable。這個欄位是用來儲存 session 的 token。在 migrations 檔中可以使用

```
$table->rememberToken();
```

來建立。

## 儲存密碼

當使用者輸入密碼時，如果直接存入資料庫會有被偷盜的風險。Laravel 提供 Hash 類別，使用 Bcrypt hashing 的方式對密碼加密。

### 加密

在使用者要設定密碼時，將該密碼加密後儲存：

```
$password = Hash::make('secret');
```

這樣原本的 'secret' 這個字串，就會變成像這樣 '$2y$10$ytWDg0ufsazdFkYsVnW.2eJhnc4gm5vZqHoEEycxqtCZCigWofGK6'

### 驗證

在使用者輸入登入密碼時，驗證該密碼：

```
if (Hash::check('secret', $password))
{
    //密碼正確
}
```

## 實戰使用者登入

我們來將之前練習的迷你部落格加上一個登入頁面，讓登入的使用者才能新增、修改文章，未登入的則只能看到文章。

步驟：

* 建立 migration 檔來新增 users 資料表
* 使用 seeder 來新增一位使用者
* 建立 Route
* 建立 Controller
* 建立登入表單頁面

### 建立 migration 檔來新增 users 資料表

開啟終端機，進入網站根目錄，輸入：

```
php artisan migrate:make create_users_table
```

會建立一個檔案 app/database/migrations/2015\_01\_08\_060754\_create\_users\_table.php，編輯它：

```
<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateUsersTable extends Migration {

    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function($table){
          $table->increments('id');
          $table->string('username');
          $table->string('email');
          $table->string('password');

          //記得要加 remember_token 這個欄位，可手動指定
          //$table->string('remember_token', 100)->nullable();
          //或使用內建方法
          $table->rememberToken();
          $table->timestamps();
      });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('users');
    }

}
```

接下來到終端機中，輸入：

```
php artisan migrate
```

如果沒出現錯誤，現在你在資料庫管理軟體中就可以看到 users 資料表了。

### 使用 seeder 來新增一位使用者

在 app/database/seeds 目錄中，新增一個檔案 UserTableSeeder.php：

```
<?php

class UserTableSeeder extends Seeder {

    public function run()
    {
        DB::table('users')->delete();

        User::create([
            'username' => 'Tony',
            'email'    => 'tony@mail.com',
            'password' => Hash::make('password'),
        ]);
    }
}
```

好了之後，編輯 app/database/seeds/DatabaseSeeder.php，在 run() 方法中加入：

```
$this->call('UserTableSeeder');
```

完成。回到終端機，輸入：

```
php artisan db:seed
```

現在資料庫中 users 資料表已經建立一位使用者了。

### 建立 Route

打開 app/routes.php ，加入：

```
Route::get('login', 'LoginController@show');
Route::post('login', 'LoginController@login');
Route::get('logout', 'LoginController@logout');
```

我們會在 LoginController@show 方法中回傳一個登入頁面；在 @login 方法中做登入驗證；在 @logout 方法中登出使用者。

### 建立 Controller

在 app/controllers 下建立 LoginController.php：

```
<?php

class LoginController extends BaseController {

    public function show()
    {

    }

    public function login()
    {

    }

    public function logout()
    {

    }

}
```

對應到 Route 中所指定的方法。

#### 1.登入頁

現在依序來加入功能。首先是 LoginController@show：

```
public function show()
{
    return View::make('admin.login');
}
```

在 app/views 新建一個目錄 admin，在其中新增一個檔案 login.blade.php，結果是 app/views/admin/login.blade.php，內容如下：

```
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Blog Login</title>
    <style type="text/css">
    .fail {width:200px; margin: 20px auto; color: red;}
    form {font-size:16px; color:#999; font-weight: bold;}
    form {width:160px; margin:20px auto; padding: 10px; border:1px dotted #ccc;}
    form input[type="text"], form input[type="password"] {margin: 2px 0 20px; color:#999;}
    form input[type="submit"] {width: 100%; height: 30px; color:#666; font-size:16px;}
    </style>
</head>
<body>
    @if ($errors->has('fail'))
        <div class="fail">{{ $errors->first('fail') }}</div>
    @endif
    {{ Form::open(['url'=>'login', 'method'=>'post']) }}
        {{ Form::label('email', 'Email') }}
        {{ Form::text('email') }}
        {{ Form::label('password', 'Password') }}
        {{ Form::password('password') }}
        {{ Form::submit('Login') }}
    {{ Form::close() }}
</body>
</html>
```

完成後看起來像這樣：

![](https://2143920249-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M48rwYB4_KhIVG8EBhp%2F-M49JjoezbujiTyFIL6l%2F-M49VImn5slV87K5GlYj%2Flogin.png?alt=media\&token=770d2073-7063-4b77-a6bf-2bf1d1210b6a)

#### 2.驗證並登入

接著編輯 LoginController@login，處理使用者的登入驗證：

```
public function login()
{
    $input = Input::all();

    $rules = ['email'=>'required|email',
              'password'=>'required'
              ];

    $validator = Validator::make($input, $rules);

    if ($validator->passes()) {
        $attempt = Auth::attempt([
            'email' => $input['email'],
            'password' => $input['password']
        ]);

        if ($attempt) {
            //驗證成功
            return Redirect::intended('post');
        }

        //驗證失敗
        return Redirect::to('login')
                ->withErrors(['fail'=>'Email or password is wrong!']);
    }

    //輸入的資料不符合
    return Redirect::to('login')
                ->withErrors($validator)
                ->withInput(Input::except('password'));
}
```

我們將 email 及 password 兩個欄位設為必填(required)，同時使用 email 這個規則去驗證郵件格式是否正確。

輸入的資料都符合規定後，使用 Auth 類別來做驗證。如果驗證成功，Auth::attempt() 方法會回傳 true，這時就可以把它導向內部頁面，這個範例是 post 頁面。假如驗證失敗，就導回登入頁面，同時回傳錯誤訊息。

如果輸入的資料就已經不符合規定，那也不必驗證啦，直接返回登入頁面。不過，在 ->withInput 的部份，我們用 Input::except('password') 來排除密碼欄位內容的回傳，因為不需要。

在成功驗證並登入之後，如果要取得登入者的資訊，可以使用 Auth::user() 來取得，如下：

```
Auth::user()->email;
```

#### 3.登出

最後來處理登出的功能，編輯 LoginController@logout：

```
public function logout()
{
    Auth::logout();
    return Redirect::to('login');
}
```

使用 Auth::logout() 將使用者登出，然後導回登入頁面。

接著在頁面中顯示登出連結，編輯 app/views/site/home.blade.php：

```
@if(Auth::check())
    {{ Auth::user()->username}} 已登入，{{ HTML::link('logout', '登出') }}
@endif
```

Auth::check() 可以檢查使用者是否已經登入。

#### 4.頁面的登入檢查

完成了嗎？還沒，我們還沒讓 post 頁面受到登入機制的管制，現在(在登出的狀態)你可以直接輸入網址：

```
http://localhost/blog/public/post
```

你發現即使我們尚未登入，依然可以看到 post 頁面。

通常會每個必須被管制的頁面都要加檢查機制，但每個頁面都去 Auth::check() 不僅沒效率，也太麻煩了，我們直接從入口 (Route) 就截斷。編輯 app/routes.php，針對要管制的頁面 post 加入：

```
Route::get('post', ['before'=>'auth', 'uses'=>'HomeController@index']);
```

'before'=>'auth'，表示在進入這個 route 之前，要先執行 auth 這個過濾器(定義在 app/filters.php)。

如果要一個一個 route 去寫也太麻煩了，改用 group：

```
Route::group(['before'=>'auth'], function(){
    Route::get('post', 'HomeController@index');
    Route::get('post/create', 'HomeController@create');
    Route::post('post', 'HomeController@store');
    Route::get('post/{id}', 'HomeController@show');
    Route::get('post/{id}/edit', 'HomeController@edit');
    Route::put('post/{id}', 'HomeController@update');
    Route::delete('post/{id}', 'HomeController@destroy');
});
```

這樣在這個 group 之下的所有頁面都會受到 auth 過濾器的檢查。

在 \[Route 進階] 那個章節，有提到可以將同一位址往上提，於是可以修改成：

```
Route::group(['prefix'=>'post', 'before'=>'auth'], function(){
    Route::get('/', 'HomeController@index');
    Route::get('create', 'HomeController@create');
    Route::post('/', 'HomeController@store');
    Route::get('{id}', 'HomeController@show');
    Route::get('{id}/edit', 'HomeController@edit');
    Route::put('{id}', 'HomeController@update');
    Route::delete('{id}', 'HomeController@destroy');
});
```

當然，如果你的條件符合，也可以使用 resource 的方式：

```
Route::group(['before'=>'auth'], function(){
    Route::resource('post', 'HomeController');
});
```

完成，現在你已經無法直接輸入網址來進入頁面，請登入。
