やりたいこと
-
Next.js
をフロント側として、Laravel/ui
で認証したい。 - 一度ログインしたら、cookieに認証情報を持たせて楽したい。
Laravelの認証は、バージョン8.0から Jetstream
などを推奨していますが、既存サービスとの兼ね合いから今回はこちらを選定します。
どうやるか
Laravel/ui
に response()->json
で返却してくれる機能があるので基本はそれに乗っかってカスタマイズしていきます。
最初のログインは、Next.js
側で用意したログインページに email
password
を入力、
cookie認証は、Users
テーブルにtoken
カラムを用意してcookie
に付与したtoken
とで判別する形で進めます。
前提
-
Laravel/ui
のインストールが完了している。 -
cors
の設定が済んでいる(config/cors.php
,Middleware/Cors.php
など)
Laravel側の実装
まず、LoginController
を見ていきましょう。
<?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
に以下を追加します。
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
を返すことで、実装することができます。