メール認証による会員サインイン

a-blog cms では、ユーザーにサインイン用のリンク及びコードを含むメールを送信し、サインインしてもらうことができます。この認証方法では、ユーザーのメールアドレスの検証も行います。

メール認証によるサインインでは、次のようなメリットがあります。

  • パスワードが必要がないため、サインアップ・サインインが簡単になります。
  • パスワードが再利用されるリスクが低くなります。パスワードを再利用すると、適切なパスワードを選択していても、セキュリティが低下する恐れがあります。
  • サインイン認証で、ユーザーがメールアドレスの正当な所有者であることを確認できます。
  • アクセス可能なメール アカウントがあればそれだけでサインインできます。SNSアカウントは必要ありません。
  • パスワードを入力(あるいは記憶)しなくても、安全にサインインできます。特に、パスワードの入力や記憶が面倒なモバイルデバイスでの利用が多い場合に有効です。

メール認証による会員サインインを有効化する

メール認証でユーザーをサインインさせるには、コンフィグ > ログイン設定のメール認証サインイン(管理ログイン)から「メール認証でのサインイン」を有効にして保存します。

また、メール認証サインインの有効時間を設定することも可能です。セキュリティのため出来るだけ短い時間を設定するのを推奨します。


メール認証サインインの設定

メール認証サインインの設定


サインインの流れ

メール認証によるサインインの流れを説明します。

1. サインイン画面にアクセス

サインイン画面にアクセスします。メール認証では、パスワード認証とは違いメールアドレスの入力欄のみが表示されます。


メール認証サインイン画面

メール認証サインイン画面

2. 登録されているメールアドレスを入力

サインインするアカウントのメールアドレスを入力して「確認メールを送信」ボタンを押すと、サインイン用のリンク及びコードを含むメールが送信されます。


認証メール送信後の画面

認証メール送信後の画面

3. 届いたメールにあるリンクにアクセスするか、確認コードをサインインフォームに入力する

届いたメールからサインイン用のリンクにアクセスするか、確認コードをサインインフォームに入力して「認証する」ボタンを押すことで、サインインができます。


認証メール

認証メール

ユーザーのメールアドレスに認証メールを送信する

メール認証によるサインインを実装するには、Member_Signin モジュールを利用してユーザーにメールアドレスの入力をさせるためのフォームを用意します。

以下の例では、{email_auth_signin} 変数を活用して、メールアドレス認証によるサインイン認証機能が有効のときだけパスワード入力欄とパスワードによるサインインボタンを非表示にしています。

  <form
    action=""
    method="post"
    class="acms-form"
    enctype="multipart/form-data"
  >
    <!-- BEGIN signin -->
    <div class="separate">
      <section class="separate-item">
        <h2 class="heading-md heading-margin-top-none acms-text-center">
          メールアドレスでサインイン
        </h2>

        <div class="validator-result-{mail:validator#restriction} acms-alert acms-alert-danger">
          お使いの端末(ブラウザ)からはログインが禁止されています。<br />管理者にお問い合わせ下さい。
        </div>
        <div class="validator-result-{pass:validator#auth} acms-alert acms-alert-danger">
          ユーザーIDまたはパスワードが違います
        </div>
        <div class="validator-result-{mail:validator#notFound} acms-alert acms-alert-danger">
          ユーザーが存在しません
        </div>
        <div class="validator-result-{mail:validator#send} acms-alert acms-alert-danger">
          認証メールの送信に失敗しました
        </div>
        <div class="validator-result-{mail:validator#lock} acms-alert acms-alert-danger">
          <!--$1-->{trialNumber}<!--/$1-->回ログインに失敗したため、アカウントがロックされました。
          <!--$1-->{lockTime}<!--/$1-->分後にもう一度試してください。
        </div>
        <div class="validator-result-{mailAuthSignin:validator#enable} acms-alert acms-alert-danger">
          メール認証によるサインイン機能は無効です
        </div>
        <div class="validator-result-{passwordSignin:validator#enable} acms-alert acms-alert-danger">
          パスワードによるサインイン機能は無効です
        </div>

       <!-- BEGIN badRequest -->
        <div class="acms-alert acms-alert-danger">
          不正なアクセスです。
        </div>
        <!-- END badRequest -->

        <!-- BEGIN expired -->
        <div class="acms-alert acms-alert-danger">
          有効期限切れのURLです。再度お試しください。
        </div>
        <!-- END expired -->

        <ul class="form-group-list">
          <li class="form-group">
            <div class="form-label">
              <label class="acms-form-block acms-clear" for="input-text-mail"
                >メールアドレス</label
              >
            </div>
            <div class="form-control">
              <input
                type="text"
                name="mail"
                value="{mail}"
                class="acms-form-large acms-form-block acms-form-width-full js-ready-focus"
                id="input-text-mail"
                placeholder=""
                autocomplete="username"
                data-validator="mail"
              />
              <input type="hidden" name="login[]" value="mail" />
            </div>
          </li>
          <!-- BEGIN_IF [{email_auth_signin}/neq/on] -->
          <li class="form-group">
            <div class="form-label">
              <label class="acms-form-block acms-clear" for="input-password"
                >パスワード</label
              >
            </div>
            <div class="form-control">
              <input
                type="password"
                name="pass"
                value="{pass}"
                id="input-password"
                class="acms-form-large acms-form-block acms-form-width-full"
                autocomplete="current-password"
              />
              <input type="hidden" name="login[]" value="pass" />
              <input type="hidden" name="redirect" value="%{redirect}" />
              <input type="hidden" name="login[]" value="redirect" />
            </div>
          </li>
          <!-- END_IF -->
        </ul>
        <!-- BEGIN_IF [{email_auth_signin}/neq/on] -->
        <p class="acms-text-center">
          <a
            href="%{BLOG_URL}%{RESET_PASSWORD_SEGMENT}/"
            class="text-link is-color-text is-bold"
            >パスワードをお忘れの方はこちらから</a
          >
        </p>

        <div class="form-button-group">
          <button
            type="submit"
            name="ACMS_POST_Member_Signin"
            class="button is-lg is-width-lg form-button"
          >
            サインイン
          </button>
        </div>
        <!-- END_IF -->

        <!-- BEGIN_IF [{email_auth_signin}/eq/on] -->
        <p class="form-button-group">
          <button
            type="submit"
            name="ACMS_POST_Member_SigninWithEmail"
            class="button is-lg is-width-lg form-button"
          >
            確認メールを送信
          </button>
        </p>
        <!-- END_IF -->
      </section>
    </div>
    <!-- END signin -->
  </form>

そして、認証用リンクが記載されたメールの送信には Member_SigninWithEmail というPOSTモジュールを活用します。

Member_SigninWithEmail モジュールによって、認証メールの送信が成功すると、Member_Signin モジュールの verifyCode ブロックが表示されます。また、verifyCode ブロック内では、認証メールの送信成功時に successSent ブロックが表示されます。

<!-- BEGIN_MODULE Member_Signin -->
<!-- BEGIN verifyCode -->
<div>
  <!-- BEGIN successSent -->
  <p class="form-message">本人確認のため、確認メールをお送りしました。<br>メールに記載されている確認リンクをクリックするか、下記に確認コードを入力してください。</p>
  <!-- END successSent -->
</div>
<!-- END verifyCode -->
<!-- END_MODULE Member_Signin -->

送信できる項目

Member_SigninWithEmail モジュールで送信できる項目については以下項目になります。

また、Member_SigninWithEmail モジュールで送信するフィールド(項目)は name 属性に login[] を、 value 属性に 送信したい項目名を指定した input タグを一緒に記述する必要があります。

<input
  type="text"
  name="mail"
  value="{mail}"
  id="input-text-mail"
  placeholder=""
  autocomplete="username"
/>
<input type="hidden" name="login[]" value="mail" />


項目名 概要 入力例
mail サインインするユーザーのメールアドレスです。 info@example.com

エラー時の表示

バリデーションはテンプレート側で任意に指定することができますが、いくつかの値はプログラム側で強制的にバリデーションを設定しているため、テンプレートにバリデーターを設定していなくても、バリデータのエラーが表示されます。

メール認証による会員サインインフォームで何らかのエラーが起きた場合には以下のフィールド(項目)がバリデーションエラーとして表示されます。



フィールド(項目)名 オプション名 概要
pass auth コンフィグ > アクセス設定 > ログイン制限で設定した制限に引っかかった場合及び、リファラーがサイトのドメインと違う場合に発生するエラー
mail notFound 送信したメールアドレスに一致するユーザーが存在しない場合及び、複数存在する場合に発生するエラー
mail lock コンフィグ > ログイン設定 > アカウントロックで設定した、パスワード認証の再試行制限に引っかかった場合に発生するエラー
mail restriction ログイン拒否端末からログインした場合に発生するエラー
mailAuthSignin enable メール認証によるサインイン機能が無効の場合に発生するエラー

認証メール

ユーザーは認証メールに記載されているURLへのアクセス及び、コードの入力によりサインインを完了するすることができます。この認証メールはカスタマイズすることが可能です。メールテンプレートについては、コンフィグ > メール設定から変更することができます。


コンフィグ > メール設定から認証メールのテンプレートを設定できます。

コンフィグ > メール設定から認証メールのテンプレートを設定できます。


認証メールでは以下の変数が利用できます。



変数名 概要
authUrl 認証用のURL
verifyCode 認証用の確認コード

認証メールに記載されているURLへアクセスして、メール認証サインインを完了する

認証メールに記載されているURLへアクセスすることで、メール認証サインインを完了してサインインすることができます。

メール認証が成功した場合は設定に従って適切なページにリダイレクトされますが、失敗した場合は、Member_Signin モジュールで以下のブロックが表示できます。



ブロック名 概要
badRequest 認証URLの形式が不正であるために、認証が失敗した場合に表示されるブロックです。
expired 認証URLが有効期限切れの場合に表示されるブロックです。

テンプレート上では、以下のように表示できます。

<!-- BEGIN_MODULE Member_Signin -->
    <!-- BEGIN badRequest -->
    <div class="acms-alert acms-alert-danger">
      不正なアクセスです。
    </div>
    <!-- END badRequest -->

    <!-- BEGIN expired -->
    <div class="acms-alert acms-alert-danger">
      有効期限切れのURLです。再度お試しください。
    </div>
    <!-- END expired -->
<!-- END_MODULE Member_Signin -->

認証メールに記載されている確認コードを入力して、メール認証サインインを完了する

認証メールに記載されている確認コードをサインインフォームに入力して送信することで、メール認証サインインを完了してサインインすることができます。

メール認証サインインを完了すると、設定に従って適切なページにリダイレクトされます。

認証メールに記載されている確認コードを利用するには、Member_Signin モジュールを利用してユーザーに確認コードの入力をさせるためのフォームを用意します。

認証メールの送信に成功すると、Member_Signin モジュールの verifyCode ブロックが表示されます。そのため、確認コードの入力フォームは verifyCode ブロック内にテンプレートを記述します。

<!-- BEGIN_MODULE Member_Signin -->
<!-- BEGIN verifyCode -->
<div>
  <!-- BEGIN successSent -->
  <p class="form-message">本人確認のため、確認メールをお送りしました。<br>メールに記載されている確認リンクをクリックするか、下記に確認コードを入力してください。</p>
  <!-- END successSent -->

  <div class="form-group-list is-narrow">
    <div class="form-group">
      <div class="validator-result-{mail:validator#lock} acms-alert acms-alert-danger">
        {trialNumber}回ログインに失敗したため、アカウントがロックされました。
        {lockTime}分後にもう一度試してください。
      </div>
      <div class="validator-result-{code:validator#auth} acms-alert acms-alert-danger">
        確認コードが違います
      </div>
      <div class="validator-result-{code:validator#request} acms-alert acms-alert-danger">
        不正なアクセスです
      </div>
      <div class="validator-result-{code:validator#expired} acms-alert acms-alert-danger">
        確認コードの有効期限が切れています
      </div>
    </div>
  </div>

  <ul class="form-group-list is-narrow">
    <li class="form-group">
      <div class="form-label">
        <label for="input-text-code" class="acms-form-block acms-clear">確認コード</label>
      </div>
      <input
        type="text"
        name="code"
        value="{code}"
        id="input-text-code"
        class="acms-form-large acms-form-block acms-form-width-full"
        placeholder=""
        data-validator="code"
      >
      <input type="hidden" name="login[]" value="code" />
      <input type="hidden" name="takeover" value="{signin:takeover}" />
    </li>
  </ul>
  <div class="form-button-group">
    <button
      type="submit"
      name="ACMS_POST_Member_SigninWithVerifyCode"
      class="button is-lg is-width-lg form-button"
    >
      サインイン
    </button>
  </div>
  <div class="form-button-group">
    <a href="%{BLOG_URL}%{SIGNIN_SEGMENT}/" class="button is-bordered is-lg is-width-lg form-button">サインイン画面に戻る</a>
  </div>
</div>
<!-- END verifyCode -->
<!-- END_MODULE Member_Signin -->

確認コードによる認証には、Member_SigninWithVerifyCode というPOSTモジュールを活用します。

送信できる項目

Member_SigninWithVerifyCode モジュールで送信できる項目については以下項目になります。

Member_SigninWithVerifyCode モジュールで送信するフィールド(項目)は name 属性に login[] を、 value 属性に 送信したい項目名を指定した input タグを一緒に記述する必要があります。

<div>
  <label for="input-text-code">確認コード</label>
</div>
<input
  type="text"
  name="code"
  value="{code}"
  id="input-text-code"
  placeholder=""
  data-validator="code"
>
<input type="hidden" name="login[]" value="code" />


項目名 概要 入力例
mail サインインするユーザーのメールアドレスです。 info@example.com
code 認証メールに記載されている確認コード 0MIJKI

SigninWithVerifyCode モジュールはユーザーのメールアドレスの情報を必要としますが、以下のように takeover の値を一緒に送信することで認証メール送信のために入力したメールアドレスの値を利用できるため、メールアドレスの入力は不要です。

<input type="hidden" name="takeover" value="{signin:takeover}" />

エラー時の表示

バリデーションはテンプレート側で任意に指定することができますが、いくつかの値はプログラム側で強制的にバリデーションを設定しているため、テンプレートにバリデーターを設定していなくても、バリデータのエラーが表示されます。

認証コード入力フォームで何らかのエラーが起きた場合には以下のフィールド(項目)がバリデーションエラーとして表示されます。



フィールド(項目)名 オプション名 概要
pass auth コンフィグ > アクセス設定 > ログイン制限で設定した制限に引っかかった場合及び、リファラーがサイトのドメインと違う場合に発生するエラー
code auth コード認証に失敗した場合に発生するエラー
code request 認証コードが不正な場合に発生するエラー
code expired 認証コードが有効期限切れの場合に発生するエラー
mail lock コンフィグ > ログイン設定 > アカウントロックで設定した、パスワード認証の再試行制限に引っかかった場合に発生するエラー
mail restriction ログイン拒否端末からログインした場合に発生するエラー

URLの末尾に .(ドット)含むURLでカスタムフィールド検索をするとサーバーの404エラーが表示される

カスタムフィールド検索で.(ドット)含む文字列で検索する場合、POST_2GETモジュールを利用して検索フォームを作成すると、検索ページのURLが以下のようになることがあります。

https://example.com/search.html/field/kataban/hoge.hoge

このようなURLの場合、Ver. 3.0.28 及び、Ver. 3.1.5 以前に a-blog cmsをダウンロードしたときに同梱されている .htaccess をそのまま利用している場合、a-blog cms が動作せず、サーバーの404エラーが表示されてしまう場合があります。

原因としては、.htaccess でCMSを実行するための mod_rewrite の記述にて、URLの末尾が特定の拡張子または、/(スラッシュ)の場合のみ a-blog cms のプログラムを実行するような指定がされている点です。

# ---------------------------
# CMSを実行
# ---------------------------
RewriteRule ((\.(html|htm|php|xml|txt|js|json|css|yaml|csv))|/)$ index.php [L]

これにより、POST_2GETモジュールを利用した検索フォームから遷移する、検索ページのURLの末尾に .(ドット)が含まれている場合、a-blog cms が動作せず、サーバーの404エラーが表示されてしまいます。

対応方法

.htaccess にて、CMSを実行するための mod_rewrite の記述を以下のように変更してください。

# ---------------------------
# CMSを実行
# ---------------------------
RewriteRule . index.php [L]

POSTモジュールについて

ポストインクルードでは通常「ACMS_POST_2GET」モジュールを利用します。

<form action="" method="post" class="js-post_include-ready">
  <input type="text" name="keyword" value="" size="15" />
  <input type="hidden" name="bid" value="%{BID}" />
  <input type="hidden" name="cid" value="%{CID}" />
  <input type="hidden" name="tpl" value="include/search.html" />
  <input type="submit" name="ACMS_POST_2GET" />
</form>

ACMS_POST_2GETモジュールは、POSTする情報(ブログ情報、エントリ情報、テンプレート、キーワードなど)から、URLに変換してリダイレクトします。ポストインクルードではこのリダイレクトされたURLのHTMLを取得して表示します。

POST_2GET_Ajax

Ver. 3.1.17 から新しいモジュール「ACMS_POST_2GET_Ajax」が利用できるようになりました。

ACMS_POST_2GETモジュールとの違い

tpl指定した場合の挙動が異なります。「ACMS_POST_2GET」モジュールの場合、tpl指定されていても、URLにtplコンテキストが入らない場合があります。

以下のポストインクルードを例に実際に取得するHTMLのURLの違いを見てみます。ここで「%{CID}」は「news」カテゴリーを指定しているものとします。

<form action="" method="post" class="js-post_include-ready">
  <input type="text" name="keyword" value="" size="15" />
  <input type="hidden" name="bid" value="%{BID}" />
  <input type="hidden" name="cid" value="%{CID}" />
  <input type="hidden" name="tpl" value="search.html" />
  <input type="submit" name="ACMS_POST_2GET" />
</form>

ACMS_POST_2GETの場合

https://example.com/news/search.html?keyword=xxxxxx

ACMS_POST_2GET_Ajaxの場合

https://example.com/news/tpl/search.html?keyword=xxxxxx

この場合「ACMS_POST_2GET」の場合は「themes/ご利用テーマ/news/search.html」テンプレートがないと404になりますが、「ACMS_POST_2GET_Ajax」の場合は、tpl指定されているので「themes/ご利用テーマ/search.html」があれば取得できることになります。

「ACMS_POST_2GET」の場合でも、テンプレートをカテゴリディレクトリに設置すれば解決しますが、多くのカテゴリがある場合は同じテンプレートをカテゴリー毎に設置しないといけなくなります。

「ACMS_POST_2GET_Ajax」であれば、必ずURLに「tplコンテキスト」が入るので、1つのテンプレートで済みます

Ver. 3.1.17 以降のバージョンの場合、ポストインクルードで指定するモジュールは「ACMS_POST_2GET_Ajax」をご利用ください。

CSRF保護

CSRF(Cross-Site Request Forgery:クロスサイトリクエストフォージェリ)は、ユーザーが意図しないリクエストを第三者が送信する攻撃手法です。攻撃者は、認証されたユーザーを利用してそのユーザーの権限で悪意ある操作を行わせることを狙います。

a-blog cms を利用すればCSRF攻撃からサイトを簡単に保護できます。

CSRF攻撃の防止

a-blog cms では、CSRFトークンを使用してCSRF攻撃に対応しています。

システムによって管理されているユーザーセッションごとにCSRF「トークン」を自動的に生成します。 このトークンはユーザーのセッションに保存され、CMSが生成するHTMLにも自動的に埋め込まれるようになっております。

ユーザーが何かリクエストする際には、テンプレートに埋め込まれたCSRFトークンも一緒に送信することで、ユーザーセッションに保存されているトークンと比較し、悪意あるリクエストを排除することが出来るようになります。

以下では、CSRFトークンが使用されるリクエストや、CSRFトークンの生成方法について解説します。

CSRFトークンが必要な操作

a-blog cms で CSRFトークンが必要な操作について列挙します。

POST操作

POSTモジュールは基本的にCSRFのチェックを行いますが、例外として以下のPOSTモジュールはCSRFトークンをチェックしません。

  • ACMS_POST_2GET
  • ACMS_POST_2GET_Ajax
  • ACMS_POST_Download
  • ACMS_POST_Login_Check
  • ACMS_POST_Member_SigninRedirect

Ajax(ポストインクルード)によるGETリクエスト

通常GETリクエストは、CSRFトークンをチェックしませんが、Ajaxリクエストの場合、CSRFトークンによる認証をする場合があります。

URLコンテキストで「tpl」が指定されている場合

通常以下のようなURLコンテキストにtplが入ったURLは404になり取得できません。

https://example.com/news/tpl/search.html

ただAjaxの場合は、CSRFトークンを使用して取得することが可能です。

取得HTMLが部分HTMLだった場合

通常a-blog cmsは、部分的なHTMLの取得を許可しませんが、Ajaxの場合は、CSRFトークンを使用することで部分的なHTML取得が可能になります。

CSRFトークンによるチェックをするには、private/config.system.yaml で「ajax_security_level」が「2」に設定されている必要があります。

ajax_security_level: 2 # ajaxリクエストのセキュリティレベルを設定します。(0: チェックなし 1: RefererとHttpヘッダーを確認 2: CSRFトークン確認)

CSRFトークンの生成条件

以下の条件の場合にユーザーセッションをスタートさせ、CSRFトークンを生成します。

  • ログインしている時
  • POSTリクエストの時
  • 「ACMS_POST_Form_」から始まる文字列がテンプレートにある時
  • 「ACMS_POST_Comment_」から始まる文字列がテンプレートにある時
  • 「ACMS_POST_Shop」から始まる文字列がテンプレートにある時
  • 「ACMS_POST_2GET_Ajax」から始まる文字列がテンプレートにある時
  • テンプレートに「check-csrf-token」文字列がある時
  • シークレットブログ・カテゴリーだった時
  • ログインページなど認証系の画面の時
  • phpで「IS_OTHER_LOGIN」定数が定義されている時

CSRFトークンを生成によりセッションスタートしてしまうことで、ブラウザキャッシュやCDNなどでキャッシュが利用できなくなってしまいます。必要な場合のみCSRFトークンを生成するようにしています。

CSRFトークンの埋め込み場所

生成されたCSRFトークンはレスポンスされるHTMLに埋め込まれます。

form要素

HTMLのform要素内の最後に、自動でinput要素が埋め込まれます。

<form method="post">
  ...
  <input type="hidden" name="formToken" value="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx">
</form>

metaタグ

HTMLのメタタグとして、head要素内に自動で埋め込まれます。

<meta name="csrf-token" content="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx">

a-blog cms の基本機能を利用する場合、基本的には何も設定しなくてもCSRFトークンを含めた形でリクエストする形になっているため対応は不要です。

標準機能外でCSRFトークンを利用する

標準機能は、特に何もしなくてもCSRF対策がされていますが、独自にJavaScriptでリクエストを送ってレスポンスを受け取る場合などは、自身でCSRFトークンをリクエストに含める必要があります。

POSTリクエストの場合

POSTデータに以下情報を含める必要があります。

formToken: xxxxxxxxxxxxxxx

独自に開発したPOSTモジュールにJavaScriptからリクエストする例

const csrfToken = document.querySelector('meta[name="csrf-token"]').content; // メタタグからCSRFトークンを取得
const formData = new FormData();
formData.append('ACMS_POST_CustomModule', 'exec');
formData.append('formToken', csrfToken);

const response = await fetch(url, {
  method: 'POST',
  body: formData,
});

GETリクエストの場合

HTTPヘッダーに以下情報を必要があります。

Referer: https://example.com/xxxxxxx
X-Requested-With: XMLHttpRequest
X-Csrf-Token: xxxxxxxxxxxxxxxxxxx

HTMXライブラリ にCSRFトークンをセットする例

document.addEventListener("htmx:configRequest", function(event) {
  const csrfToken = document.querySelector('meta[name="csrf-token"]').content; // メタタグからCSRFトークンを取得
  event.detail.headers['X-CSRF-Token'] = csrfToken;
});