🫠

個人開発勉強①:LLMと一緒に認証機能を実装してみる!

2025/02/27に公開

この記事では「個人開発を始めてみたい!」と思っている私自身が大きなプロジェクトを自走できるようになるために、部分的な要素(認証機能、API連携、UIなど)を単体で実装して記事にし、着実にステップアップしていくことを目的としたものです。(要は学習目的メインで書いていきます。)

LLMとの対話で開発を進めたり、たくさんの有益な技術記事が投稿されるようになった昨今ですが、それでもまだ「完全初心者」にとっては個人開発はとっつきにくいものであるかなと感じています。おそらくそれは、個人開発完全初心者の時点で技術記事を書いてきた人が少ないのも原因のひとつではないかと思います。なので、初心者ならではの視点で、詳細に今から言語化して記事として記録していくことは、私自身のためにもなると思いますし、私以外の初心者の方へ向けて何かしらの意味があるのではないかとも感じている次第です。

そういうモチベーションで今後は記事を書いていきますので、よろしくお願いいたします!

開発の流れ

  1. イントロ
  2. Firebaseプロジェクトのセットアップ
  3. VSCodeでプロジェクトの準備
  4. Firebase SDKのインストール
  5. HTML、CSS、JavaScriptファイルの作成
  6. HTML構造の設定
  7. CSSでページをスタイリング
  8. JavaScriptでFirebase認証を実装
  9. ローカルでアプリを動作確認
  10. GitHubでプロジェクトを管理
  11. まとめ

1.イントロ

今回実装したいのは「認証機能」だけになります。至ってシンプルです。そこでまずは、どのようにして認証機能を実装すればよいのかを確認していきます。(Grok3を適宜使用しながら私は開発を進めています。)

  • 自前で実装
  • サードパーティー認証サービス

まずは、この二つだけ把握しておけばよいかなと。自前で実装する力はもちろんまだないので除外。サードパーティー認証とは第三者(サードパーティ)が提供する認証サービスのことで、主に以下の選択肢があげられます。

  • Firebase
  • Auth0
  • Supabase

今回はYouTubeの動画を垂れ流しにしていたときに聞いたことがある『Firebase』を選択しました。

Firebaseプロジェクトのセットアップ

以下にFirebaseのリンクを張っておきます。
https://console.firebase.google.com/u/0/
そこから、Firebaseプロジェクト(私はAuthenticationAppと命名しました。プロジェクト名は自由にわかりやすく設定します。)を立ち上げて、サインイン方法をメール/パスワードの一つだけとしました。

VSCodeでプロジェクトの準備

まず始めにコマンドプロンプトを立ち上げます。(私はWindowsを使っているので、その想定で)
作業用のディレクトリへ移動します。

cd "任意のディレクトリへ移動"

次にプロジェクトを実装していくフォルダを作成します。(私はAuthenticationAppと命名しました。)

mkdir "任意のプロジェクト名"

プロジェクトのディレクトリへ移動します。

cd "任意のプロジェクト名"

プロジェクトのディレクトリでVScodeを立ち上げます。

code .

次にgitやgithubで作業記録をつけるために以下の下準備をしておきます。(この段階でしておくべき操作かは今の時点ではしっくりこない)
それぞれの操作を役割としては

  • フォルダ内でGitリポジトリを初期化
  • Gitのバージョン管理からこのフォルダを除外する(
git init
echo node_modules/ > .gitignore

Firebase SDKのインストール

Firebase SDKとは Firebase Software Development Kitのことを指し、Firebaseを利用するためのコードやライブラリの集合を意味する。開発者がアプリに機能を簡単に追加できるように設計されているとのこと。ここでは、それをコマンドプロンプトからインストールする。
npmとはNode Package Managerの略で、Node.jsアプリケーションで使用するパッケージ(ライブラリやツール)を管理するためのツールとのこと。

npm init -y
npm install firebase

HTML、CSS、JavaScriptファイルの作成

今回の実装は「認証機能」を実装するだけなので

  • HTML
  • CSS
  • JavaScript

だけで十分。
コマンドプロンプトから作成してもいいし、IDEからファイル作成してもOK。
以下はコマンドプロンプトからファイル作成するコマンド。(echoコマンドを使うのが一般的とのこと)

echo. > index.html & echo. > styles.css & echo. > app.js

HTML構造の設定

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Firebase Auth</title>
  <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300&display=swap" rel="stylesheet">
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <div class="container">
    <!-- タブ切り替え用ナビゲーション -->
    <div class="tabs">
      <button id="login-tab" class="tab-btn active">ログイン</button>
      <button id="register-tab" class="tab-btn">新規登録</button>
    </div>

    <!-- ログインフォーム -->
    <div id="login-form" class="auth-form">
      <h2>ログイン</h2>
      <input type="email" id="login-email" placeholder="メールアドレス"><br>
      <input type="password" id="login-password" placeholder="パスワード"><br>
      <button id="login-btn">ログイン</button>
      <p id="login-error" class="error-message"></p>
    </div>

    <!-- 登録フォーム -->
    <div id="register-form" class="auth-form" style="display: none;">
      <h2>新規登録</h2>
      <input type="email" id="register-email" placeholder="メールアドレス"><br>
      <input type="password" id="register-password" placeholder="パスワード"><br>
      <input type="password" id="confirm-password" placeholder="パスワード(確認)"><br>
      <button id="register-btn">登録</button>
      <p id="register-error" class="error-message"></p>
    </div>
  </div>

  <!-- ログイン後の画面 -->
  <div id="message" style="display: none;">
    <h1>What are you going to do today?</h1>
    <button id="logout-btn">ログアウト</button>
  </div>

  <!-- Firebase SDKs -->
  <script src="https://www.gstatic.com/firebasejs/9.6.1/firebase-app-compat.js"></script>
  <script src="https://www.gstatic.com/firebasejs/9.6.1/firebase-auth-compat.js"></script>
  <script src="app.js"></script>
</body>
</html>

CSSでページをスタイリング

認証フォームとメッセージを中央に配置する最小限のスタイルを追加した。今回はデザインの勉強がメインではないので!

styles.css
body {
  font-family: 'Roboto', sans-serif;
  background-color: #f5f5f5;
  margin: 0;
  padding: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
}

.container {
  background-color: white;
  border-radius: 8px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
  width: 350px;
  padding: 20px;
}

.tabs {
  display: flex;
  margin-bottom: 20px;
  border-bottom: 1px solid #e0e0e0;
}

.tab-btn {
  flex: 1;
  background: none;
  border: none;
  padding: 10px;
  font-size: 16px;
  cursor: pointer;
  transition: all 0.3s ease;
}

.tab-btn.active {
  color: #4285F4;
  border-bottom: 2px solid #4285F4;
}

.auth-form {
  text-align: center;
}

.auth-form h2 {
  margin-top: 0;
  color: #333;
}

input {
  width: 100%;
  padding: 12px;
  margin: 8px 0;
  border: 1px solid #ddd;
  border-radius: 4px;
  box-sizing: border-box;
  font-size: 14px;
}

button {
  background-color: #4285F4;
  color: white;
  border: none;
  border-radius: 4px;
  padding: 12px 20px;
  margin: 10px 0;
  cursor: pointer;
  font-size: 16px;
  transition: background-color 0.3s;
}

button:hover {
  background-color: #3367D6;
}

#message {
  display: none;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  text-align: center;
  background-color: white;
  padding: 40px;
  border-radius: 8px;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
}

#message h1 {
  font-size: 36px;
  font-weight: 300;
  color: #333;
  margin-bottom: 30px;
}

#logout-btn {
  background-color: #f44336;
}

#logout-btn:hover {
  background-color: #d32f2f;
}

.error-message {
  color: #f44336;
  font-size: 14px;
  margin: 8px 0;
  min-height: 20px;
}

JavaScriptでFirebase認証を実装

Firebase設定に関してはプロジェクトの設定から、マイアプリのなかでアプリを追加を選択。そこからウェブアプリに設定する。そうするとfirebaseConfigの設定値が全てコピペできるようになるので、確認。(細かいところは飛ばして後日、きっちりと勉強しよう。)

app.js
// Firebase設定(実際の値に置き換えてください)
const firebaseConfig = {
  apiKey: "YOUR_API_KEY",
  authDomain: "YOUR_AUTH_DOMAIN",
  projectId: "YOUR_PROJECT_ID",
  storageBucket: "YOUR_STORAGE_BUCKET",
  messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
  appId: "YOUR_APP_ID"
};

// Firebase初期化
firebase.initializeApp(firebaseConfig);
const auth = firebase.auth();

// 要素の取得
// タブ要素
const loginTab = document.getElementById('login-tab');
const registerTab = document.getElementById('register-tab');

// フォーム要素
const loginForm = document.getElementById('login-form');
const registerForm = document.getElementById('register-form');

// ログインフォーム
const loginEmail = document.getElementById('login-email');
const loginPassword = document.getElementById('login-password');
const loginBtn = document.getElementById('login-btn');
const loginError = document.getElementById('login-error');

// 登録フォーム
const registerEmail = document.getElementById('register-email');
const registerPassword = document.getElementById('register-password');
const confirmPassword = document.getElementById('confirm-password');
const registerBtn = document.getElementById('register-btn');
const registerError = document.getElementById('register-error');

// その他UI要素
const messageDiv = document.getElementById('message');
const logoutBtn = document.getElementById('logout-btn');

// タブ切り替え機能
loginTab.addEventListener('click', () => {
  loginTab.classList.add('active');
  registerTab.classList.remove('active');
  loginForm.style.display = 'block';
  registerForm.style.display = 'none';
});

registerTab.addEventListener('click', () => {
  registerTab.classList.add('active');
  loginTab.classList.remove('active');
  registerForm.style.display = 'block';
  loginForm.style.display = 'none';
});

// ユーザー登録処理
registerBtn.addEventListener('click', () => {
  const email = registerEmail.value;
  const password = registerPassword.value;
  const passwordConfirm = confirmPassword.value;
  
  // 入力値の検証
  if (!email || !password || !passwordConfirm) {
    registerError.textContent = 'すべての項目を入力してください';
    return;
  }
  
  if (password !== passwordConfirm) {
    registerError.textContent = 'パスワードが一致しません';
    return;
  }
  
  // エラーメッセージをクリア
  registerError.textContent = '';
  
  // Firebaseで新規ユーザー登録
  auth.createUserWithEmailAndPassword(email, password)
    .then((userCredential) => {
      console.log('ユーザー登録成功:', userCredential.user);
      // 登録成功後、メッセージ画面に遷移する(onAuthStateChangedで処理)
    })
    .catch((error) => {
      console.error('登録エラー:', error);
      // エラーメッセージの表示
      switch(error.code) {
        case 'auth/email-already-in-use':
          registerError.textContent = 'このメールアドレスは既に使用されています';
          break;
        case 'auth/invalid-email':
          registerError.textContent = '無効なメールアドレスです';
          break;
        case 'auth/weak-password':
          registerError.textContent = 'パスワードは6文字以上必要です';
          break;
        default:
          registerError.textContent = `エラーが発生しました: ${error.message}`;
      }
    });
});

// ログイン処理
loginBtn.addEventListener('click', () => {
  const email = loginEmail.value;
  const password = loginPassword.value;
  
  // 入力値の検証
  if (!email || !password) {
    loginError.textContent = 'メールアドレスとパスワードを入力してください';
    return;
  }
  
  // エラーメッセージをクリア
  loginError.textContent = '';
  
  // Firebaseでログイン
  auth.signInWithEmailAndPassword(email, password)
    .then((userCredential) => {
      console.log('ログイン成功:', userCredential.user);
      // ログイン成功後、メッセージ画面に遷移する(onAuthStateChangedで処理)
    })
    .catch((error) => {
      console.error('ログインエラー:', error);
      // エラーメッセージの表示
      switch(error.code) {
        case 'auth/invalid-email':
          loginError.textContent = '無効なメールアドレスです';
          break;
        case 'auth/user-disabled':
          loginError.textContent = 'このアカウントは無効になっています';
          break;
        case 'auth/user-not-found':
        case 'auth/wrong-password':
          loginError.textContent = 'メールアドレスまたはパスワードが正しくありません';
          break;
        default:
          loginError.textContent = `エラーが発生しました: ${error.message}`;
      }
    });
});

// ログアウト処理
logoutBtn.addEventListener('click', () => {
  auth.signOut()
    .then(() => {
      console.log('ログアウト成功');
      // ログアウト後の処理はonAuthStateChangedで行われる
    })
    .catch((error) => {
      console.error('ログアウトエラー:', error);
    });
});

// 認証状態の監視
auth.onAuthStateChanged((user) => {
  if (user) {
    // ログイン済み
    console.log('ログイン状態:', user);
    
    // フォームを非表示にし、メッセージを表示
    document.querySelector('.container').style.display = 'none';
    messageDiv.style.display = 'block';
    
    // フォームをリセット
    loginForm.reset();
    registerForm.reset();
    
  } else {
    // ログアウト状態
    console.log('ログアウト状態');
    
    // メッセージを非表示にし、フォームを表示
    document.querySelector('.container').style.display = 'block';
    messageDiv.style.display = 'none';
    
    // 初期表示ではログインタブを表示
    loginTab.click();
  }
});

ローカルでアプリを動作確認

アプリをテストするには、ローカルサーバーが必要とのこと。live-server をグローバルにインストールする。
コマンドプロンプトで以下を実行。

npm install -g live-server

ローカルサーバーにアクセス

live-server

自動で立ち上がった画面がこちら。


ログイン画面と新規登録画面が同じタブ内で切り替わる仕様になっている。
次に試しに新規登録をし、ログインしてみる。

ちゃんとログインできて、最初に決めておいた「What are you going to do today?」が表示された。成功!
次にログアウトして、もう一度、新規登録を同じメールアドレスでやってみる。

GitHubでプロジェクトを管理

GitHubで新しいリポジトリを作成する。私の場合は今回のアプリ実装をもとにいろんな機能を枝付けして様々なアプリを作成していきたいので、FoundationAppと命名してみた。(このときの設定はまだprivateにしておいた。)
まだ機能的には改善しておきたいなと思うところもある(アカウント削除機能など)が、まずはここで記録しておく。gitにも慣れておきたい。

まだローカルサーバーが立ち上がっているなら、タブを一旦閉じて、コマンドプロンプトで「Ctrl + C」で抜け出しておく。

VSCodeでの変更をステージング(コマンドプロンプトで操作)

git add .

するとこのような警告が表示されたが、いまのところは重要ではなさそうなので、そのままコミットしてみる。

コミットする

git commit -m "初回コミット"

GitHubリポジトリ(リモートリポジトリ)をgitに追加。(言い方があっているのかはあとで確認)
このときのURLは、githubのリポジトリ画面で緑色になっているcodeボタンからGETできる。

git remote add origin YOUR_REPO_URL

GitHubにプッシュする。

git push -u origin main


するとリジェクトされたとメッセージがきた。
原因を詳細に調べていくと(Grok3と一緒に)

  • ローカルとリモートが独立して初期化された:
    • ローカルで git init を使ってリポジトリを作成し、初回コミットを作った。
    • 一方、リモートでリポジトリを作成した際に「Initialize with a README」などを選んで別の初回コミットが作られた。
  • 結果、ローカルとリモートの履歴が全く別物に。
  • 履歴のつながりがない:
    • ローカルとリモートの main ブランチが、それぞれ異なる起点から始まっているため、Gitがそれらを結びつけられない。
      原因が分かったので、今回は反省してごり押しの強硬策で突破する。
      この操作はここでは割愛。何やってるのかさっぱりわからなかった。

      無事、プッシュ完了。
      今回はここまで。

まとめ

今回は初めてのWeb開発(個人開発の第一歩)だった。今回のことをするまでに、どのようにしたら効率的に大きなプロジェクトを着手する見通しを自分で建てられるのだろうかと考えていた。質の高いオンライン教材に手を出すことも一つだが、その分、脇道へそれた学習も発生することだろうし、まずは自分の興味のある要素要素の開発を行い、そこから専門的な学習へ入っていくのが最適解だと今は考えている。

実際に動くWebアプリが作れたことで、モチベーションもかなり上がってきている。おそらく、文法から入っていたら長くは続かなかっただろう。LLMのコーディング支援がまともになってきた(Web開発の簡単な機能実装までなら、枠組みとして提供できるレベル)時代だからこそ、形から入って細部へと詰めていく学習プロセスが効率的に回せるようになってきているのかもしれない。

次回以降のWeb開発学習として、不足していた機能、あったらいいなと思う機能をメモしておく。

個人開発のために、頑張っていくぞい!ぞい!ぞいー!

Discussion