37
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

MetapsAdvent Calendar 2023

Day 21

🐣🥚🔰🐣🥚とりあえずログイン機能を作ってみたい(laravelとnext.js)🐣🥚🔰🐣🥚

Last updated at Posted at 2023-12-20

はじめに

社内ではlaravelとnuxt.jsや、laravelとnext.jsを組み合わせた開発が行われています。
ただ、私自身業務でcodeigniterを使って開発しているので知見がありません。
そこでログイン機能を作って知見を深めたいと思いました。

また、laravelとnext.jsでの組み合わせで調べると記事が少ないような気がしたので、少しでも貢献できれば嬉しいです☺️

環境構築

macOS Ventura 13.4.1で開発します。

事前作業

事前にインストールが必要なもの

docker Version4.19.0
node.js v18.17.x(next.js)
php 8.x
composer 2.6.x(laravel 10.x)

事前に作成が必要なもの

事前にdockerのコンテナを作成しておいてください。
また、この中で以下のようにnodeをインストールしておいてください。

dockerfile
# node install
RUN curl -fsSL https://rpm.nodesource.com/setup_18.x | bash -
RUN yum install -y nodejs

next.jsの環境構築

next.jsをローカル環境にインストールしたいと思います。
公式の通りにコマンドを実行してみます

$ npx create-next-app@latest
Need to install the following packages:
create-next-app@14.0.4
Ok to proceed? (y)
✔ What is your project named? … src
✔ Would you like to use TypeScript? … No / Yes
✔ Would you like to use ESLint? … No / Yes
✔ Would you like to use Tailwind CSS? … No / Yes
✔ Would you like to use `src/` directory? … No / Yes
✔ Would you like to use App Router? (recommended) … No / Yes
✔ Would you like to customize the default import alias (@/*)? … No / Yes

アプリ名をsrcにしたこと以外はデフォルト設定です。

さて、コンテナに入ってnextアプリが立ち上がることを確認します

$ docker container exec -it app bash
# npm run dev

> src@0.1.0 dev
> next dev

   ▲ Next.js 14.0.4
   - Local:        http://localhost:3000

 ✓ Ready in 4.4s

ブラウザで確認してみます
スクリーンショット 2023-12-12 17.12.57.png

表示できました🥳

laravelの環境構築

laravelをローカル環境にインストールしたいと思います。
公式の通りにコマンドを実行してみます

$ composer create-project laravel/laravel src

アプリ名をsrcにしたこと以外はデフォルト設定です。

アプリが立ち上がることを確認します
スクリーンショット 2023-12-20 3.52.22.png

表示できました🥳

認証機能の実装

laravel側

sanctumを使って認証システムを作りたいと思います。
変更点は以下です

routes/api.php
+ use App\Http\Controllers\API\AuthController;

+ Route::post('register', [AuthController::class, 'register']);
+ Route::get('login', [AuthController::class, 'login'])->name('login');
+ 
+ Route::middleware('auth:sanctum')->group(function() {
+     Route::post('logout', [AuthController::class, 'logout']);
+ });
config/cors.php
- 'supports_credentials' => false,
+ 'supports_credentials' => true,
app/Http/Kernel.php
- // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
+ \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
app/Http/Controllers/API/AuthController.php
<?php

namespace App\Http\Controllers\API;

use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Validator;

class AuthController extends Controller
{
    public function register(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'name'=>'required|max:191',
            'email'=>'required|email|max:191|unique:users,email',
            'password'=>'required|min:8',
        ]);

        if ($validator->fails()) {
            return response()->json([
                'validation_errors' => $validator->messages(),
            ]);
        } else {
            $user = User::create([
                'name' => $request->name,
                'email' => $request->email,
                'password' => Hash::make($request->password),
            ]);

            return response()->json([
                'status' => 200,
                'username' => $user->name,
                'message' => 'register success',
            ]);
        }
    }

    public function login(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'email' => 'required',
            'password' => 'required',
        ]);

        if ($validator->fails()) {
            return response()->json([
                'validation_errors' => $validator->messages(),
            ]);
        } else {
            $user = User::where('email', $request->email)->first();
            if (!$user || !Hash::check($request->password, $user->password)) {
                return response()->json([
                    'status' => 401,
                    'message' => '入力情報が不正です',
                ]);
            } else {
                $token = $user->createToken($user->email . '_Token')->plainTextToken;

                return response()->json([
                    'status' => 200,
                    'username' => $user->name,
                    'token' => $token,
                    'message' => 'ログインにせいこうしました。',
                ]);
            }
        }
    }

    public function logout()
    {
        auth()->user()->currentAccessToken()->delete();
        return response()->json([
            'status' => 200,
            'message' => 'ログアウトせいこう',
        ]);
    }

insomniaで認証できているか確認してみます
アカウント登録
スクリーンショット 2023-12-20 5.47.05.png

ログイン
スクリーンショット 2023-12-20 5.49.56.png

ログアウト
スクリーンショット 2023-12-20 5.50.23.png

laravel側の認証機能はできていそうです🥳

next側

フロント側で行うことは以下です。

  • localstorageにlaravelで発行したapiトークンを保存する
  • api通信時にはトークンを設定したaxiosを使う
app/login/page.tsx
'use client'
import axios from "@/app/api/axios"

const clickSignUp = () => {
    const email = document.querySelector<HTMLInputElement>('input[name="email"]')
    const password = document.querySelector<HTMLInputElement>('input[name="password"]')

    if (email && password) {
        const data = {
            email: email.value,
            password: password.value,
        }

        axios.post('api/register', data).then(res => {
            if (res.data.status !== 200) {
                alert('アカウント登録しっぱい')
            }
        })
    } else {
        alert('emailかpasswordが存在してません')
    }
}

const clickLogin = () => {
    const email = document.querySelector<HTMLInputElement>('input[name="email"]')
    const password = document.querySelector<HTMLInputElement>('input[name="password"]')

    if (email && password) {
        const data = {
            email: email.value,
            password: password.value,
        }

        const urlSearchParam = new URLSearchParams(data).toString()

        axios.get('api/login?' + urlSearchParam).then(res => {
            if (res.data.status === 200) {
                console.log('000000000000000000')
                console.log(res.data)
                localStorage.setItem('auth_token', res.data.token);
            } else {
                alert('ログインしっぱい')
            }
        })
    } else {
        alert('emailかpasswordが存在してません')
    }
}

const clickLogout = () => {
    axios.post('api/logout').then(res => {
        if (res.data.status === 200) {
            localStorage.removeItem('auth_token')
            console.log('aaaaaaaaaaa')
        } else {
            alert('ログアウトしっぱい')
        }
    })
}

const login = () => {
    return (
        <>
            <header>
                <nav>
                    <ul>
                        <li>
                            <button onClick={clickSignUp}>新規登録</button>
                        </li>
                        <li>
                            <button onClick={clickLogin}>ログイン</button>
                        </li>
                        <li>
                            <button onClick={clickLogout}>ログアウト</button>
                        </li>
                    </ul>
                </nav>
            </header>
            <main>
                <div className='login_form'>
                    <label>
                        メールアドレス
                        <input type='input' className='login_form__input' name='email' />
                    </label>
                    <label>
                        パスワード
                        <input type='input' className='login_form__input' name='password' />
                    </label>
                </div>
            </main>
        </>
    )
}

export default login
app/api/axios.tsx
import axios from 'axios'

const new_axios = axios.create({
  baseURL: 'http://localhost',
  withCredentials: true,
  headers: {
    'Content-Type': 'application/json',
    'Accept': 'application/json',
  }
})

new_axios.interceptors.request.use(
  (config) => {
    const token = localStorage.getItem('auth_token')
    config.headers.Authorization = token ? 'Bearer ' + token : ''
    return config
  }
)

export default new_axios
app/globals.css
@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
  --foreground-rgb: 0, 0, 0;
  --background-start-rgb: 214, 219, 220;
  --background-end-rgb: 255, 255, 255;
}

@media (prefers-color-scheme: dark) {
  :root {
    --foreground-rgb: 255, 255, 255;
    --background-start-rgb: 0, 0, 0;
    --background-end-rgb: 0, 0, 0;
  }
}

header nav {
  max-width: 1200px;
  margin: 0 auto;
  padding: 30px;
}

header nav ul {
  display: flex;
}

header nav ul li {
  &:not(:last-child) {
    margin-right: 20px;
  }
}

header nav ul li button {
  width: 150px;
  height: 40px;
  border: solid 1px;
  border-radius: 30px;
  cursor: pointer;
}

.login_form {
  margin: 0 auto;
  padding: 0 30px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-width: 1200px;
}

.login_form .login_form__input {
  border: solid 1px black;
}

課題

nextauth.jsを使い、認証をセッションで管理したい🐣

cookieだとセキュリティ的に値が丸見えなため、insomniaなどのツールからAPIトークンを誰でも簡単に設定して値を取ってこれるので、セッション管理したいと思いました。

おわり

laravelとnext.jsに興味があったので今回勉強するための良いきっかけになったと思います。引き続き情報を収集して面白そうなことがあったら自分の環境で開発してみるのも良いのではないかと思いました。

37
24
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
37
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?