Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
4
Help us understand the problem. What is going on with this article?
@lvn-kazamaki

Laravel/ui と Next.js で認証してみた

やりたいこと

  • Next.js をフロント側として、 Laravel/ui で認証したい。
  • 一度ログインしたら、cookieに認証情報を持たせて楽したい。

Laravelの認証は、バージョン8.0から Jetstream などを推奨していますが、既存サービスとの兼ね合いから今回はこちらを選定します。

どうやるか

Laravel/uiresponse()->json で返却してくれる機能があるので基本はそれに乗っかってカスタマイズしていきます。
最初のログインは、Next.js側で用意したログインページに email password を入力、
cookie認証は、Usersテーブルにtokenカラムを用意してcookieに付与したtokenとで判別する形で進めます。

前提

  • Laravel/ui のインストールが完了している。
  • corsの設定が済んでいる(config/cors.php, Middleware/Cors.phpなど)

Laravel側の実装

まず、LoginController を見ていきましょう。

LoginController.php
<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\AuthenticatesUsers;

class LoginController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Login Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles authenticating users for the application and
    | redirecting them to your home screen. The controller uses a trait
    | to conveniently provide its functionality to your applications.
    |
    */

    use AuthenticatesUsers;

    /**
     * Where to redirect users after login.
     *
     * @var string
     */
    protected $redirectTo = RouteServiceProvider::HOME;

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest')->except('logout');
    }
}

ログイン機能は、traitである AuthenticatesUsers が賄っているので、
こちらに書かれているfunctionをオーバーライドしていきます。

LoginController に以下を追加します。

LoginController.php
protected function validateLogin(Request $request)
{
    // cookieにtokenがあった場合は、通常のvalidationロジックを回避する
    if ($request->cookie('token')) {
        $authUser = UserModel::where('token', '=', $cookies['token'])
            ->first();
    }
    if (empty($authUser)) {
        $request->validate([
            $this->username() => 'required|string',
            'password'        => 'required|string|min:8',
        ]);
    }
}

// ログイン処理にemail, password認証だけでなく、cookie認証も追加
public function attemptLogin(Request $request)
{
    $attempted = $this->guard()->attempt(
        $this->credentials($request), $request->filled('remember')
    );
    if (! $attempted) {
        // 通常のemail,passwordログインがだめだった場合に、cookieでの認証を行う
        $authUser = UserModel::where('token', '=', $cookies['token'])
            ->first();
        }
        if (! empty($authUser)) {
            Auth::login($authUser);
            return true;
        } else {
            $attempted = false;
        }
    } 
}

// 認証後に新しいtokenを作成し、responseにcookieとして付与
public function authenticated($request, $user)
{
    // ハッシュ化のロジックは何でもOK
    $newToken = md5($user->token);
    $_res = UserModel::where('token', '=', $user->token)
        ->update(['token' => $newToken]);
    // 今あるcookieを削除
    setcookie('token');
    // 新しく発行したtokenでcookieを作成
    $cookie = Cookie::make('token', $newToken, 60*24, '/', '', true, true);
    if ($request->wantsJson()) {
            $response = response(['messages' => 'success', 200])->cookie($cookies);
            return $response;
    }
}

// ログアウトでcookieのtokenを削除
public function loggedOut(Request $request)
{
    setcookie('token', '', time() + 1, '/', '', true, true);
    if ($request->wantsJson()) {
        $response = response()->json(['message' => 'success'], 200)
            ->cookie('token', '', time() + 1, '/', '', true, true);
        return $response;
    }
}

$request->headers'Accept': 'application/json' が設定してあると、
$request->wantsJson()true になり、各return responseロジックに落ちます。

headersに設定していない場合は、通常のbladeで認証するロジックと同一の挙動になり、
RouteServiceProvider:HOMEへリダイレクトされたりします。

Next.js側の実装

Next.js側は大きく分けて2つの機能を実装します。

  • email,password入力のログインページ
  • cookieによるログイン処理

まずは、email,passwordを入力するログインページを作成し、 onSubmitで実行されるloginAuthを実装します。

const LoginPage: NextPage<LoginProps> = (props) => {
    const router = useRouter();

    const loginAuth = async (e: any) => {
        e.preventDefault();
        const email = e.target.email.value;
        const password = e.target.password.value;

        const res = await fetchUserLogin(email, password);
        console.log(res.status);
        // ログイン出来た場合は、ステータスコード200が返ってくる
        if (res.status === 200) {
            return router.push('/logged');
        }
    };

    const fetchUserLogin = async (email: string, password: string) => {
        const res = await fetch(`${BACKEND_URL}/login`, {
            method: 'POST',
            mode: 'cors',
            credentials: 'include',
            headers: {
                'Content-Type': 'application/json; charset=utf8',
                // headersに Accept: application/json が無いと jsonResponse を返してくれない
                'Accept': 'application/json',
            },
            body: JSON.stringify({ email, password }),
        });
        return res;
    };

return (
        <div>
            <main className="">
                <div className="content">
                    <form onSubmit={loginAuth}>
                        <ul>
                            <li>
                                <span className="inputTitle">メールアドレス</span>
                                <input type="email" name="email" className="email" required />
                            </li>
                            <li>
                                <span className="inputTitle">パスワード</span>
                                <input
                                    type="password"
                                    name="password"
                                    className="password"
                                    required
                                    minLength={8}
                                />
                            </li>
                        </ul>
                        <button type="submit">ログイン</button>
                    </form>
                </div>
            </main>
        </div>
    );

}

cookieによるログインはこんな感じ。

export const fetchCookieLoginUser = async () => {
    const res = await fetch(`${BACKEND_URL}/login`, {
        method: 'POST',
        mode: 'cors',
        // credentialsをincludeにするとリクエストにcookieを含められる
        credentials: 'include',
        headers: {
            'Content-Type': 'application/json; charset=utf-8',
            'Accept': 'application/json',
        },
    });
    return res;
};

ログインに成功した後のページは、
fetchCookieLoginUserを使ってcookie認証し、
認証を通ったもののみ表示するようなロジックを組みましょう。

const res = await fetchCookieLoginUser();
if (res.status !== 200) {
    router.push('/login');
}
----
ログイン後の処理
----

これで認証は出来るようになっているはずです。
登録処理においても、今回のログイン処理と同様にオーバーライドしながらresponse->jsonを返すことで、実装することができます。

4
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
4
Help us understand the problem. What is going on with this article?