Laravel6系でゲストログイン機能（かんたんログイン）の実装

#はじめに
本記事では、ログイン時に`ユーザー名`や`パスワード`を入力しなくても、
ワンクリックでゲストユーザーとしてログインできる**「ゲストログイン機能」**の実装方法をご紹介します。（初心者向け）

転職活動用のポートフォリオ等にゲストログイン機能があると、多忙な採用担当の方の負担が減り、アプリを利用してもらえる確率が高まるメリットがあります。

しかし便利な反面、悪意のあるユーザーによってゲストユーザー情報を改変される等、
アプリに脆弱性を生む可能性も否定はできないのでご注意ください。
（個人アプリの開発レベルでは、それほど影響はないかもしれませんが、、）

**2020/1/11編集:**
本記事でご紹介するゲストログイン機能のロジックを、以下のように変更しました。
`usersテーブルのメールアドレスを利用してゲストログイン　　→　usersテーブルの主キー（id）を利用してゲストログイン`

メールアドレス情報を利用してゲストログインする場合、万が一
ユーザープロフィール編集機能等でメールアドレスを書き換えられてしまった場合、ゲストログインができなくなってしまうためです。

#事前準備

ゲストログイン機能の実装の前に、以下が予め準備されていることが前提となります。


①ログイン機能の実装されている
②DBにusersテーブルがあり、`nameカラム`や`emailカラム`が定義されている
③ゲストログイン用のユーザー(ゲストユーザー)の登録されている
　→ 例）ユーザー名：`ゲストユーザー`
　　　　 メールアドレス：`guest@guest.com`
④Userモデルが作成されている

※セキュリティをしっかり固める場合、ゲストユーザーのアカウントを通常のユーザーアカウントと一緒に、usersテーブル/Userモデルで管理するのはあまり望ましくありません。

#実装内容

ログイン機能とゲストユーザーの準備ができたら、いよいよ実装に入ります。
ご紹介する実装内容は割と簡単で、以下になります。


- ゲストログインの処理を記述(Controller)

- ルーティングの設定(web.php等)

- ログイン画面等、任意のページにゲストログイン用のリンクを設置する(View) 

- 補足）ユーザープロフィール編集機能の制限 

#ゲストログインの処理を記述(Controller)

まず今回は、Laravel6系をインストールしたら初めから存在する
`app/Http/Controllers/Auth/LoginController.php`
に、ゲストログインの処理を書いていきます。

```LoginController.php

<?php

namespace App\Http\Controllers\Auth;

use App\Models\User; //※ ModelsディレクトリにUser.phpがある場合
use Illuminate\Support\Facades\Auth;
#　〜〜〜〜〜〜〜　略　〜〜〜〜〜〜〜

class LoginController extends Controller
{

  #　〜〜〜〜〜〜〜　略　〜〜〜〜〜〜〜

    // ゲストユーザー用のユーザーIDを定数として定義
    private const GUEST_USER_ID = 1;

    // ゲストログイン処理
     public function guestLogin()
    {
        // id=1 のゲストユーザー情報がDBに存在すれば、ゲストログインする
        if (Auth::loginUsingId(self::GUEST_USER_ID)) {
            return redirect('/');
        }

        return redirect('/');
    }
}

```

**解説〜**

まず、以下のように`GUEST_USER_ID`という定数に **1** を代入しています。
この **1** は、DBのusersテーブルのidカラムの値が **1** であるユーザー情報を呼び出すために、後ほど使用します。（本記事の例では、これがゲストユーザーアカウント）

定数は、以下のような形式で定義します。
`const 定数名  =  値;`

```LoginController.php
private const GUEST_USER_ID = 1;
```

例）usersテーブルの、idとユーザー名一覧
![スクリーンショット 2021-01-11 14.15.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613419/b57bce25-1bc8-1910-1ff1-b73555d77644.png)

※皆さんのDB環境では、ゲストユーザー用に設定するアカウントのidが1ではないかもしれません。
　こちらの数値については、適宜ご確認いただき、ご調整ください。


```LoginController.php
public function guestLogin()
  {
      // id=1 のゲストユーザー情報がDBに存在すれば、ゲストログインする
      if (Auth::loginUsingId(self::GUEST_USER_ID)) {
          return redirect('/');
   }
```


処理の流れとしては、まず
`Auth::loginUsingId()`メソッドの引数に、先ほど定義した定数
`GUEST_USER_ID = 1`を渡し、主キーが1のユーザー、つまり本記事の例の場合
ゲストユーザーでログインします。
（定義した定数を使う時は、`self::定数名`　という形式）
ゲストユーザーとしてのログインが成功すると、続いて
`return redirect('/');`でトップページへリダイレクトするようになっています。

参考：[【Readouble】IDによるユーザー認証](https://readouble.com/laravel/6.x/ja/authentication.html)

なお、if文のブロック{  }の外にもう1つ
`return redirect('/');`を記述しています。
これにより、仮にゲストログインが失敗した場合もトップページへは戻れるようにしています。


#ルーティングの設定(web.php等)

次に、今回は
`routes/web.php`
を編集して、ゲストログイン機能用のルーティングを設定します。
以下を追記してください。

```web.php
# ゲストユーザーログイン
Route::get('guest', 'Auth\LoginController@guestLogin')->name('login.guest');
```

#任意のページにゲストログイン用のリンクを設置する(View)

続いて、ゲストログイン処理をブラウザから実行できるように「ゲスト
ログイン」ボタンをViewに追加します。

ボタンをお好みの場所に設置していきましょう。

ちなみに私は、自作アプリには以下のように
ヘッダーと、ログイン画面の２箇所に設置しました。


・ヘッダーのナビゲーションバー（右上）
![スクリーンショット 2021-01-02 21.21.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613419/a353fb94-ab72-9725-0e40-d002f5c7f573.png)


・ログイン画面（右下）
![スクリーンショット 2021-01-02 21.20.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613419/0dca560a-0f1c-5851-9d36-a9539644839b.png)

それでは実装に入ります。
以下のコードをView(Blade)の任意の場所に追記してください。(デザインはBootstrap使用)

```html
<button class="btn btn-success">
  <a href="{{ route('login.guest') }}" class="text-white">
    ゲストログイン
  </a>
</button>
```

リンク先の`route('login.guest')`は、先ほどweb.phpでルーティングを設定した
`name('login.guest')`とリンクしています。
また、< button >タグや< a >タグ内のclassには、Bootstrapのデザインを使用しています。
この辺については、お好みで調整してください。

**※「ゲストログイン」ボタンをクリックしたらバリデーションエラーが出る場合**
< button >タグを外して、< a >タグにCSSをかけてボタンのように見せる方法にするとうまくいくと思います。

```html
<a href="{{ route('login.guest') }}" class="btn btn-default p-3">
 ゲストログイン
</a>
```

〜〜〜〜〜〜〜〜〜〜〜

ここまで実装できたら、ゲストログイン機能の実装自体は完了です！
実際に「ゲストログイン」ボタンをクリックして、ゲストユーザーとしてログインできているか確認してみましょう！

#ゲストユーザーのプロフィール編集を制限する

最後に、セキュリティ的な対策です。
もしあなたの開発しているアプリに、`ユーザー名`や`メールアドレス`等を編集できる
**「ユーザープロフィール編集機能」**がある場合、
ゲストユーザー用アカウントのものに関しては変更できないようにした方が良いと思います。


そのため、以下のような対策を取る必要があります

- (A)メールアドレスの変更の際に、パスワードを求める仕様にする

- (B)ゲストユーザーでログイン時はメールアドレスを変更できない仕様にする

- (C)そもそもどのユーザーでもメールアドレスは変更できない仕様にする  

私の場合、(B)で実装しましたので、簡単にその実装方法をご紹介したいと思います。

##実装例

###フロントエンド側の対策

![スクリーンショット 2021-01-03 10.38.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613419/e58a0f69-87b0-f23c-c355-bd637e502186.png)

プロフィール編集画面にて、ゲストユーザーでログインしている時は
`ユーザー名`と`パスワード`を編集できないようにしています。
これは、<input>タグに`readonly属性`を追加することで実現できます。

**実装例**

```html
@if (Auth::id() == 1)
  <p class="text-danger">※ゲストユーザーは、ユーザー名とメールアドレスを編集できません。</p>
@endif
<div class="form-group">
  <label for="name">
    ユーザー名
    <small class="blue-grey-text">（15文字以内）</small>
  </label>
  @if (Auth::id() == 1)
    <input class="form-control" type="text" id="name" name="name" value="{{ $user->name }}" readonly>
  @else
    <input class="form-control" type="text" id="name" name="name" value="{{ $user->name ?? old('name') }}">
  @endif
</div>
<div class="form-group">
  <label for="email">メールアドレス</label>
  @if (Auth::id() == 1)
    <input class="form-control" type="text" id="email" name="email" value="{{ $user->email }}" readonly>
  @else
    <input class="form-control" type="text" id="email" name="email" value="{{ $user->email ?? old('email') }}">
  @endif
</div>
```
上記のコードでは、

```php
@if (Auth::id() == 1)
```
でゲストユーザーでログインしているかを判定し、もしゲストユーザーでログインしている場合には
< input >タグに`readonly属性`を付与するようにしています。
※厳密には、View側で` 1 `の値を使うよりは、configで定義する方がスマートかもしれません。

また、デザインにはBootstrapを使用しています。

ただし、この実装だけでは**重大な欠点**がありまして、Chrome等のデペロッパーツールを使用して
`readonly属性`を消されると簡単にメールアドレスを入力できてしまい、上書きされてしまいます。
なのでバックエンド側での対策も必要になってきます。

###バックエンド側の対策

私の場合、ユーザープロフィール編集機能の実装には以下の２つを作成しています。
・UserController.php
・UserRequest.php（フォームリクエストによるバリデーション）

実装の手順としては、次の2つです。

①User.Request側：ゲストユーザーがログインしている場合は、`ユーザー名`と`メールアドレス`をバリデーションにかけないようにする
②UserController側：バリデーションにかけた値だけをDBに保存するようにする

これで、ゲストユーザーのプロフィール編集ができないようになります。

**実装例**

```UserRequest.php
private const GUEST_USER_ID = 1;

public function rules()
{
    // ゲストユーザーログイン時は、ユーザー名とメールアドレスをバリデーションにかけない
    if(Auth::id() == self::GUEST_USER_ID) {
        return [
            'profile_image' => 'file|mimes:jpeg,png,jpg,bmb|max:2048',
            'self_introduction' => 'string|max:200|nullable',
        ];
    } else { // ゲストユーザー以外がログインしている時は、全てのユーザー情報をバリデーションにかける
        return [
            'name' => 'required|string|max:15|',
            'email' => 'required|string|email|max:255|',
            'profile_image' => 'file|mimes:jpeg,png,jpg,bmb|max:2048',
            'self_introduction' => 'string|max:200|nullable',
        ];
    }
}
```

```UserController.php
public function update(UserRequest $request, string $name)
{
    $user = User::where('name', $name)->first();

    // バリデーションにかけた値だけをDBに保存
    $user->fill($request->validated())->save();

    return redirect()->route('users.show',['name' => $user->name]);
}

```

以上で、ゲストログイン機能の実装は終了になります！
お疲れ様でした！