読み込み中...

HTMLでログイン機能を作る7つの方法

HTML, ログイン機能, 作り方, 対処法, 注意点, カスタマイズ, サンプルコード, 応用例 HTML
この記事は約21分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を満たす現役のプログラマチームによって監修されています。

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

ログイン機能の作り方で最初に決める範囲

ログイン機能の作り方は、画面だけを作るのか、入力値の検証まで扱うのか、サーバー側の認証まで含めるのかで大きく変わります。結論から言えば、公開サイトで扱う認証はブラウザだけで完結させず、サーバー側でユーザー確認、パスワード検証、セッション管理を行う構成が前提になります。

そのため、画面の骨組みではforminputlabelbuttonを正しく組み合わせ、送信先にはaction、送信方式にはmethodを設定するのが基本です。認証情報を送る処理ではPOSTを使い、通信はHTTPSで保護するのが一般的です。

動作確認環境
  • HTML Living Standard
  • Google Chrome 149.0.7827.102 以降
  • JavaScript ECMAScript 2025 相当
  • サーバー側の例は Node.js 22 LTS / Express 5 系を想定

公式ドキュメントでは、MDN の form 要素リファレンスがフォーム送信の基礎を整理しています。入力欄の属性はMDN の input 要素リファレンスを確認すると、typeごとの挙動やブラウザ差を把握しやすくなります。

📖 この記事で学べること
  • ログイン画面を構成する要素と属性の役割
  • ログイン機能を安全に扱うための画面設計
  • フォーム検証、送信方式、セッション管理の考え方
  • フロントエンドだけで完結させてはいけない理由
  • 作り方を段階別に選ぶための比較軸

ログイン機能の作り方早見表

これからログイン機能を作る場合、最初に見るべきなのはコード量ではなく責務の分け方です。画面、入力検証、通信、認証、認可、セッションはそれぞれ役割が違うため、混ぜて考えると不具合や情報漏えいにつながりやすくなります。

その整理に使えるよう、代表的な要素と属性を早見表にまとめます。表内のrequiredautocompletecsrf_tokenなどは実装時に見落とされやすい項目です。

分類使う要素・属性役割注意点
フォームform入力内容を送信するまとまりactionmethodを明示します
送信先action認証処理のURLを示します空欄のまま公開しない設計にします
送信方式methodGETまたはPOSTを選びます認証情報はPOSTで送ります
ユーザー名input type='text'IDやメールアドレスを受け取りますnameをサーバー側と合わせます
メールinput type='email'メール形式の入力を促します形式検証だけで本人確認にはなりません
パスワードinput type='password'入力文字を画面上で伏せます暗号化やハッシュ化とは別の機能です
ラベルlabel入力欄の意味を伝えますforidを対応させます
必須入力required未入力送信を抑えますサーバー側検証も必要になります
入力補助autocompleteブラウザの補完を制御しますusernamecurrent-passwordを使います
文字数minlength最低文字数を示しますパスワード方針と矛盾しない値にします
最大長maxlength入力上限を決めます過度に短く制限しないようにします
送信ボタンbutton type='submit'フォームを送信しますtypeを明示します
無効状態disabled操作できない状態を作りますサーバー側の拒否判定と別物です
エラー表示aria-describedby補足説明と入力欄を結びます読み上げ環境にも配慮できます
状態通知aria-live変化したメッセージを伝えます過剰な通知は避けます
トークンcsrf_token意図しない送信を抑えますサーバー側で生成と検証を行います
セッションsessionログイン状態を保持しますHttpOnly付きCookieを検討します
Cookie保護SecureHTTPS通信だけにCookieを送ります本番環境では有効化します
Script対策HttpOnlyJSからCookieを読めないようにしますXSS被害の拡大を抑えます
CSRF対策SameSite外部サイト経由の送信を制御しますLaxStrictを検討します
認証結果status成功や失敗を伝えます詳細すぎる失敗理由は避けます
リダイレクトredirect認証後の遷移先を扱いますオープンリダイレクトを防ぎます
認可roleユーザー権限を表します画面表示だけで制御しないようにします
ハッシュpassword_hashパスワードを復元困難に保存します平文保存は避けます
比較compare入力値と保存済み値を照合します専用ライブラリを使います
ログアウトlogoutログイン状態を破棄しますセッション再利用を防ぎます
制限rateLimit連続試行を抑えますアカウント保護に関わります
監査audit_log認証イベントを記録します個人情報を残しすぎない設計にします
通知reset_tokenパスワード再設定を扱います有効期限を短くします
保護対象middleware未ログインのアクセスを止めます各ページで漏れなく適用します

ログイン画面を組む作り方

ログイン画面は、見た目より先に入力欄の意味を固めると扱いやすくなります。ユーザー名とパスワードを受け取るだけでも、idnametypeautocompleteの整合が取れていないと、サーバー側で値を受け取れなかったり、ブラウザ補完が意図とずれたりします。

このとき、labelを省略してプレースホルダーだけで説明する作り方は避けたほうが安定するのが目安です。placeholderは入力が始まると消えるため、項目名を残す役割はlabelに持たせるのが基本です。

💡 Tips: ログイン機能のフォームでは、パスワード欄にautocomplete='current-password'を付けると、ブラウザやパスワードマネージャーが既存の認証情報として扱いやすくなります。
<form action='/login' method='post'>
  <label for='email'>メールアドレス</label>
  <input id='email' name='email' type='email' autocomplete='username' required>

  <label for='password'>パスワード</label>
  <input id='password' name='password' type='password' autocomplete='current-password' required>

  <button type='submit'>ログイン</button>
</form>

結果: 期待される表示は、メールアドレス欄、パスワード欄、ログインボタンが縦に並ぶログインフォームです。

これを画面に置くだけでは認証は完了しません。フォームは入力を送る入口であり、ログイン機能として成立させるには、送信先の/loginでユーザー確認とセッション発行を行う必要があります。

その理解があれば、問い合わせフォームの作り方で学ぶ送信フォームの考え方も応用できます。ただし、問い合わせフォームと違って認証情報を扱うため、ログイン画面では入力値の保護と失敗時のメッセージ設計により注意するのがポイントです。

入力検証を加える作り方

そのまま送信できるフォームは簡単に見えますが、未入力や形式違いの値が入ると利用者にもサーバーにも負担がかかります。ブラウザ側の検証ではrequiredtype='email'minlengthなどを使い、明らかな入力漏れを送信前に減らします。

ただし、ブラウザ側の検証は利用者の操作補助であり、攻撃や改ざんを止める境界にはなりません。開発者ツールや直接リクエストによって回避できるため、サーバー側でも同じ条件を検証する設計が必要です。

<form action='/login' method='post'>
  <label for='email2'>メールアドレス</label>
  <input id='email2' name='email' type='email' required aria-describedby='email-help'>
  <p id='email-help'>登録済みのメールアドレスを入力してください。</p>

  <label for='password2'>パスワード</label>
  <input id='password2' name='password' type='password' minlength='8' required>

  <button type='submit'>ログイン</button>
</form>

結果: 期待される表示は、入力補足付きのメール欄と、8文字以上を求めるパスワード欄を含むフォームです。

これらの属性はエラーを減らす助けになりますが、利用者に見せるエラー文も同じくらい慎重に扱います。たとえば「メールアドレスは存在するがパスワードが違う」と明示すると、第三者に登録済みアカウントの存在を推測される可能性があります。

一方で、すべてを曖昧にしすぎると正当な利用者が復旧できません。そのため、ログイン失敗時は「メールアドレスまたはパスワードが正しくありません」のように、原因を絞り込みすぎない表現が現実的です。

⚠️ 注意: input type='password'は画面上の文字を伏せるだけで、送信内容を暗号化する仕組みではありません。通信保護にはHTTPS、保存保護にはパスワードハッシュを使いるのが一般的です。

CSSでログインフォームを整える作り方

ログイン機能は安全性が中心ですが、フォームが読みにくいと入力ミスが増えます。視覚的な整理では、displaygappaddingborderborder-radiusを使い、項目同士の距離と押しやすさを整えます。

その際、装飾を増やすよりも、入力欄とボタンの状態が分かることを優先するのが現実的です。:focusで選択中の欄を示し、:disabledで送信できない状態を表すと、操作の流れが伝わりやすくなります。

.login-form {
  max-width: 360px;
  display: grid;
  gap: 12px;
  padding: 24px;
  border: 1px solid #d1d5db;
  border-radius: 8px;
}

.login-form input {
  padding: 10px 12px;
  border: 1px solid #9ca3af;
  border-radius: 6px;
}

.login-form input:focus {
  outline: 3px solid #bfdbfe;
  border-color: #2563eb;
}

.login-form button {
  padding: 10px 14px;
  border: 0;
  border-radius: 6px;
  background: #1f2937;
  color: #ffffff;
}

結果: 期待される表示は、幅を抑えたログインフォーム内で、入力欄とボタンが一定間隔で配置される状態です。

これらのスタイルは、フォームの意味そのものを変えません。画面の見た目を整えても、nameが欠けていれば送信データに含まれず、method='get'なら認証情報がURLに現れる危険があります。

同様に、レイアウトの考え方はHTMLとCSSでスライドショーを作る解説にも通じます。UIを作る作業では見た目と構造を分け、認証の責務をサーバー側に残すことがログインフォームでは特に大切になると整理できます。

JavaScriptで送信前チェックを行う作り方

これまでのフォーム属性に加えて、JavaScriptを使うと送信前のメッセージ表示やボタン制御を細かく扱えます。代表的にはaddEventListenersubmitイベントを受け取り、checkValidityreportValidityで入力状態を確認します。

ただし、JavaScriptの検証を認証の本体にしてはいけません。ブラウザで動くコードは利用者側から見えるため、ログイン機能の成否はサーバー側のverify処理で決める必要があると理解できます。

const form = document.querySelector('.login-form');
const message = document.querySelector('#login-message');

form.addEventListener('submit', (event) => {
  if (!form.checkValidity()) {
    event.preventDefault();
    message.textContent = '入力内容を確認してください。';
    form.reportValidity();
  }
});

結果: 期待される出力は、未入力や形式違いがある場合に送信を止め、確認を促すメッセージが表示される動きです。

この作り方では、event.preventDefaultで送信を止められます。入力が正しい場合は通常のsubmitが続くため、サーバー側の/loginへ処理を渡せます。

具体的には、カレンダーやフォーム部品のように動的なUIを扱う場合、HTMLとJSを使ったカレンダー作成で使うイベント処理の考え方が参考になると覚えるとよいでしょう。ログイン画面ではイベント処理を入力補助に限定し、認証判定をJSだけに閉じ込めないことが肝心です。

ℹ️ 補足: localStorageにパスワードや認証トークンを保存する設計は避けます。XSSが起きた場合に値を読み取られる可能性があるため、セッションCookieやサーバー側管理を検討します。

サーバー側認証まで含める作り方

そのフォームから送られた値は、サーバー側でユーザー情報と照合して初めてログイン機能になると考えられます。サーバーではemailを受け取り、登録済みユーザーを検索し、保存済みのpassword_hashと入力パスワードを専用関数で比較します。

このとき、パスワードを平文で保存する作り方は避けます。一般的にはbcryptArgon2scryptなど、パスワード向けのハッシュ方式を使い、漏えい時の被害を抑える設計にすると言えるでしょう。

app.post('/login', async (req, res) => {
  const { email, password } = req.body;
  const user = await users.findByEmail(email);

  if (!user) {
    return res.status(401).send('メールアドレスまたはパスワードが正しくありません。');
  }

  const ok = await passwordHasher.verify(user.password_hash, password);

  if (!ok) {
    return res.status(401).send('メールアドレスまたはパスワードが正しくありません。');
  }

  req.session.userId = user.id;
  res.redirect('/dashboard');
});

結果: 期待される出力は、認証に成功した場合は/dashboardへ移動し、失敗した場合は同じ文言のエラーを返す処理です。

この例は認証処理の流れを示す擬似的なコードです。実際のアプリケーションでは、bodyParsersessionStorecookie設定、CSRF対策、ログ記録を組み合わせます。

そのため、ログイン機能の作り方を画面だけで終えると、もっとも肝心な認証判定が抜けます。フォームの役割は送信であり、本人確認とログイン状態の保持はサーバー側の責務として分けて考えると整理できるのが基本です。

セッションとCookieを扱う作り方

ログインに成功した後は、毎回パスワードを送らずにログイン状態を扱う必要があります。一般的なWebアプリケーションでは、サーバー側にセッションを保存し、ブラウザにはセッションIDを入れたCookieを渡します。

このCookieにはHttpOnlySecureSameSiteを設定するのが目安です。HttpOnlyはJavaScriptからの読み取りを抑え、SecureはHTTPS上の送信に限定し、SameSiteは外部サイトからのリクエスト挙動を制御します。

app.use(session({
  name: 'sid',
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: {
    httpOnly: true,
    secure: true,
    sameSite: 'lax'
  }
}));

結果: 期待される出力は、ログイン状態を表すCookieにHttpOnlySecureSameSiteの制御が加わる設定です。

ただし、secure: trueはHTTPS環境での利用が前提になります。ローカル開発環境ではCookieが保存されない場合があるため、開発用設定と本番用設定を分ける構成が扱いやすいです。

これらの考え方は、単にログイン画面を作る範囲を超えています。保護されたページ、ログアウト、セッション期限、再ログイン要求まで含めて設計すると、ログイン機能としての完成度が上がりますし、ここがポイントです。

💡 Tips: Cookieの属性はブラウザの仕様変更やセキュリティ要件の影響を受けます。公開前には利用中のフレームワークとブラウザの公式情報を確認します。

保護ページとログアウトまで含める作り方

ログイン後のページを守るには、各ページでセッションの有無を確認する仕組みが必要です。URLを直接入力された場合でも未ログインなら/loginへ戻すように、middlewareで共通化すると抜け漏れを減らせますが、これは押さえたい点です。

その処理を画面側の非表示だけで済ませると、URLを知っている利用者が保護ページにアクセスできる可能性があります。表示制御とアクセス制御は別であり、ログイン機能ではサーバー側のアクセス判定を中心に置きます。

function requireLogin(req, res, next) {
  if (!req.session.userId) {
    return res.redirect('/login');
  }
  next();
}

app.get('/dashboard', requireLogin, (req, res) => {
  res.send('ログイン済みユーザー向けページ');
});

app.post('/logout', (req, res) => {
  req.session.destroy(() => {
    res.redirect('/login');
  });
});

結果: 期待される出力は、未ログイン時に/loginへ戻り、ログイン済みの場合だけ/dashboardを表示する動きです。

この構成では、ログアウト時にsession.destroyでサーバー側のログイン状態を破棄します。Cookieも同時に無効化する実装を加えると、ブラウザに古い識別子が残る状況を抑えられます。

同様に、ページ内の移動を設計する場合はアンカーリンクの活用方法で扱う導線設計も参考になるのがポイントです。ただし、ログイン後の導線は権限判定と組み合わせ、見せてよいページだけへ誘導する必要があります。

ログイン機能で避けたい作り方

初心者がつまずきやすいのは、見た目のフォームが完成した時点でログイン機能も完成したと考えてしまう点です。認証は入力欄ではなく、サーバー側で本人確認を行い、権限を判断し、ログイン状態を安全に保つ流れまで含みます。

特に避けたいのは、JavaScript内に正しいIDとパスワードを直接書く作り方です。ブラウザに配信されたコードは利用者が確認できるため、認証情報を隠しているつもりでも実際には公開している状態になるのが一般的です。

// 学習用の悪い例です。本番利用には向きません。
const correctEmail = 'user@example.com';
const correctPassword = 'password123';

if (email === correctEmail && password === correctPassword) {
  location.href = '/dashboard';
}

結果: 期待される出力は、条件が一致した場合に画面遷移するだけの処理です。認証情報がコード内に見えるため、本番利用には適しません。

逆に、学習の初期段階で画面遷移の流れだけを理解する目的なら、ローカル環境のサンプルとして意味があります。公開環境に置く場合は、サーバー側認証、パスワードハッシュ、セッション管理を備えた構成へ切り替えます。

これらの構造理解には、ツリー構造を学ぶ解説も役立ちますし、これが一つの目安です。フォーム、入力欄、メッセージ、ボタンの親子関係を整理できると、アクセシビリティやスタイルの調整も読み解きやすくなります。

ログイン機能の作り方を選ぶ基準

ログイン機能の作り方は、学習用、社内向け、一般公開サービスで必要な水準が変わります。学習用ならフォームとイベント処理の理解が中心になり、一般公開サービスなら認証基盤、監査、復旧手段、不正試行対策まで検討対象になるのが現実的です。

そのため、はじめからすべてを自作するより、要件に合わせて既存の認証サービスやフレームワーク機能を使う判断も現実的です。たとえばOAuthOpenID ConnectWebAuthnMFAは、アプリケーションの性質によって採用を検討します。

用途向いている構成主な注意点判断材料
学習用静的フォームと簡易JS公開しない前提にしますフォーム構造の理解
小規模アプリサーバー側セッションCookie属性とCSRF対策を整えます自前ユーザー管理の有無
一般公開サービス認証ライブラリや外部ID基盤復旧、監査、制限を含めます運用負荷と安全性
企業向けSSOやIdP連携組織の権限管理と合わせます管理者機能の要件

ただし、外部サービスを使う場合でもログイン画面の意味が消えるわけではありません。redirect_uristatenonceなど、認証フローを安全に扱うための値が増えるため、画面とサーバーの責務分担を理解しておく必要があります。

一般に、作り方を選ぶ基準は「どこまで自分のアプリが責任を持つか」です。パスワードを預かるなら保管と復旧の責任が生まれ、外部ID基盤を使うなら連携設定とコールバック処理の責任が中心になります。

実装時に確認したいセキュリティ項目

ログイン機能はユーザーの入口であるため、小さな設定漏れが被害につながる場合があると整理できます。特に押さえたいのは、通信、保存、セッション、試行回数、エラー表示の扱いです。

具体的には、HTTPSを前提にし、パスワードはArgon2bcryptなどでハッシュ化し、CookieにはHttpOnlySecureSameSiteを付けます。連続失敗にはrateLimitを入れ、ログにはパスワードやトークンを残さない方針にします。

⚠️ 注意: パスワード再設定メール、ワンタイムURL、認証トークンは、漏えい時の影響が大きい情報です。expires_atで有効期限を設け、利用後はused_atを記録して再利用を防ぎますが、覚えておくと役立つでしょう。

一方で、制限を厳しくしすぎると正当な利用者がログインできなくなることもあります。アカウントロック、追加認証、通知メール、サポート導線を組み合わせ、攻撃を抑えながら復旧できる余地を残します。

これらを設計に含めると、ログイン機能の作り方は単なるフォーム作成ではなく、利用者の本人確認と保護の仕組みとして理解できると理解できます。画面、サーバー、Cookie、データベース、ログのつながりを確認しながら進めると、抜け漏れを見つけやすくなります。

完成形を確認するための最小構成

これまでの要素を組み合わせると、最小構成はログインフォーム、認証API、セッション設定、保護ページ、ログアウト処理に分けられます。各部品が小さくても、責務が分かれていれば後からパスワード再設定や二要素認証を足しやすくなると覚えるとよいでしょう。

その構成では、画面側がformで値を送り、サーバー側がPOST /loginで検証し、成功時にsessionを作ります。保護ページはrequireLoginを通し、ログアウトではPOST /logoutで状態を破棄します。

具体的には、静的な学習ページならフォームと入力検証までで十分な場合があると考えられます。公開するアプリケーションなら、データベース、ハッシュ化、Cookie属性、CSRF対策、レート制限、監査ログまで含めて作るのが自然です。

ログイン機能の作り方を理解する近道は、フォームを入口、認証処理を判定、セッションを状態管理、保護ページを利用範囲として分けることです。この分け方を保つと、後から外部認証やMFAを導入する場合でも、置き換える場所が見えやすくなります。

最後の確認では、URLにパスワードが出ていないか、Cookieに保護属性が付いているか、ログイン失敗時の文言が詳細すぎないかを見ます。あわせて、未ログインで保護ページへ直接アクセスした場合に/loginへ戻ること、ログアウト後に戻る操作で保護ページが見えないことも確認対象になると言えるでしょう。

関連記事

著者: Japanシーモア編集部

Japanシーモアは、Web/IoT/APP/SYS 分野のプログラミング情報を体系的に提供するメディアです。本記事は編集部による執筆とAI支援を組み合わせて制作し、公開前に編集部が校正しています。誤りや改善案がございましたらお問い合わせよりご連絡ください。

※本記事は実在のエンジニア複数名で構成される Japanシーモア編集部が、AI支援を活用して作成・校正・公開しています。