LoginSignup
0
0

[ServiceNow] Utah新機能 - MFAの二要素認証でメール認証利用時の初回認証アプリ設定を回避する

Last updated at Posted at 2023-09-29

はじめに

ServiceNowのUtahバージョンから、Multi-factor Authentication (MFA) の機能が拡張され、二要素目の認証として、Software token (Authenticator app) だけでなく、UserのE-mail宛にOnetime Password (OTP) を送ることも選択できるようになりました。
しかし、ServiceNowの仕様では、初回の認証においては必ずSoftware tokenとの紐づけが求められ、2回目以降の認証でないとメール送信が選択できません。
ここでは、この仕様を回避して、Software tokenとの紐づけを必要としないカスタマイズの例を紹介します。

このカスタマイズは、ServiceNowのOOTBのログインセキュリティ仕様を変更するものです。
変更は、自己責任で行ってください。

初期設定

まずは、OOTBのMFAが動作するように設定します。

  1. Multi-factor Authentication pluginをインストールします。
    1. [参照] https://docs.servicenow.com/bundle/utah-platform-security/page/integrate/authentication/task/t_ActivateMultifactorAuthenticator.html
  2. Multi-factor criteriaを設定します。Role-basedとUser basedがあり、特定のRoleが付与されているユーザーにMFAを適用する方法と、ユーザー単位にMFAを適用する方法に分かれています。Role-basedの場合、デフォルトでadmin、user-admin、itilが指定されているので、これらを削除して、専用のRoleを作成して、指定したほうが良いかもしれません。
    1. [参照] https://docs.servicenow.com/bundle/utah-platform-security/page/integrate/authentication/task/t_RequireMultifactorAuthForAUser.html
    2. [参照] https://docs.servicenow.com/bundle/utah-platform-security/page/integrate/authentication/task/mfa-role-criteria.html
  3. MFAを有効にします。All > Multi-factor Authentication > Propertiesを開き、Enable Multi-factor authenticationをtrueにします。このとき、併せて、Enable email OTP for Multi-factor authenticationがtrueになっていることを確認します。
    1. [参照] https://docs.servicenow.com/bundle/utah-platform-security/page/integrate/authentication/reference/mfa-properties.html

カスタマイズ

初回ログイン時に、手動でSoftware tokenがServiceNowインスタンスに紐づけることなく、正常に完了したと誤認させるため、All > Multi-factor Authentication > User Multi-factor Setupからから開けるUser Multifactor Authentication (user_multifactor_auth) テーブルのValidated (is_validated)フィールドのDefault Valueをtrueにします。

Validated_Default_True.png

次に、初回ログイン時にSoftware tokenが誤認された形で紐づいたときに、そのログインセッションを強制的に切るために、Multifactor Authentication (user_multifactor_auth) テーブルに新規Buriness Ruleを作成します。強制的にセッションを切らないと、インスタンスURLにアクセスしても、Software tokenの紐づけを促す画面に強制的にリダイレクトされ、User nameとPasswordを入力する通常のログイン画面に遷移できないためです。

User_MFA_BR1.png
User_MFA_BR2.png

Script
(function executeRule(current, previous /*null when async*/ ) {

    // Add your code here
    gs.log("SessionLockOutOnInitialLogin runs for: " + current.user.user_name);
    GlideSessions.lockOutSessionsInAllNodes(current.user.user_name);
})(current, previous);

最後に、初回ログイン時にSoftware tokenを紐付けために、OOTBの画面で表示されるQRコードと6 digits OTPの入力フィールドをを無効化して、適切な案内に変更します。
All > System UI > UI Pagesを開き、OOTBのUI Pageであるmulti_factor_auth_setup_pageのHTMLをカスタマイズします。

UIPage_MFA_Setup.png

HTML
<?xml version="1.0" encoding="utf-8" ?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
    <link href="84f0346b87120300cfab6dd207cb0b72.cssdbx?" rel="stylesheet" type="text/css"/>
	<g:evaluate jelly="true">  
      var title = gs.getMessage('Enable multi-factor authentication');
      gs.getSession().getHttpSession().setAttribute("isPartialSession", "true");
      var totp = new GlideRecord('user_multifactor_auth');
      totp.addQuery('user', gs.getUserID());
      totp.addQuery('is_validated', true);
      totp.query();        
      totp;
   </g:evaluate>

    <g:ui_form>
        <div data-form-title="$[title]"></div>
        <input type="hidden" id="mfa_setup_completed" name="mfa_setup_completed" value="${totp.hasNext()}" />

        <j:choose>
            <!-- Polarisberg -->
            <j:when test="${sn_ui.PolarisUI.canUsePolarisTemplates()}">
                <div class="login-wrapper">
                    <g:inline template="polarisberg_login_wrapper_svg.xml"/>

                    <div class="login-container wide">
                        <div class="login-logo">
                            <g:inline template="polarisberg_login_logo.xml"/>
                        </div>
                        <div class="login-card ${jvar_hide_illustrations}">
                            <g:inline template="polarisberg_login_card_svg.xml"/>

                            <div class="login-form">
								<!-- Customization from here: Display the alternative contents to bypass a software token -->  
                                <h1>${gs.getMessage('Login setting initialized')}</h1>
								<ol>
									<li>
										${gs.getMessage('Your login setting is successfully initialized.')}
									</li>
									<li>
										${gs.getMessage('Reload the page from your browser button.')}
									</li>
									<li>
										<a href="login.do">${gs.getMessage('Alternatively, access the login page again.')}</a>
									</li>
								</ol>
								<!-- Customization to here: Display the alternative contents to bypass a software token -->  
                                
								<!-- Customization from here: Hide the original contents by the comment out -->
								<!--
								<div class="notification notification-failure" id="divNotification" style="display:none;">
                                    <button data-dismiss="alert" class="btn-icon close icon-cross">
                                        <span class="sr-only">Close</span>
                                    </button>
                                </div>

                                <h1>${gs.getMessage('Enable multi-factor authentication (MFA)')}</h1>
                                <h2><a href="https://docs.servicenow.com/search?q=CSHelp:MFA-Authenticator" target="_blank">${gs.getMessage('More information')}</a></h2>
								-->

                                <!-- <a href="https://docs.servicenow.com/search?q=CSHelp:multifactor-authentication" target="_blank" id="linkLearnMore">${gs.getMessage('Learn more')}</a> -->

                                <!-- Customization from here: Hide the original contents by the comment out -->
								<!--
								<j:if test="${!totp.hasNext()}">
                                    <ol>
                                        <li>
                                            ${gs.getMessage('Download an authenticator app that supports Time Based One-Time Password (TOTP) on your mobile device.')}
                                        </li>
                                        <li>
                                            ${gs.getMessage('Open the app and scan the QR code below to pair your mobile device')}
                                        </li>
                                    </ol>

                                    <div class="login-form-field">
                                        <img id="imgCode" tabindex="0" alt="${gs.getMessage('Scan this QR code to pair your mobile device')}" />
                                    </div>
                                    <div class="login-form-field">
                                        <label tabindex="0">${gs.getMessage('Or enter this code in your app:')}</label>
                                        <div class="mfa-setup-code" tabindex="0" title="">
                                            <div id="lblCode"></div>
                                            <span id="copy_code" aria-label="${gs.getMessage('Click to copy code')}" data-original-title="${gs.getMessage('Click to copy code')}" data-toggle="tooltip" class="btn btn-default sn-tooltip-basic icon icon-copy" title="" onclick="copyCodeToClipboard()" />
                                        </div>
                                        <label id="cod_cop_msg" style="color: rgb(31, 132, 118); margin-top: 5px; display: none;" tabindex="0"></label>
                                    </div>

                                    <ol class="mfa-setup-step-3" start="3">
                                        <li>
                                            ${gs.getMessage('Enter the code generated by the Authenticator app below')}
                                        </li>
                                    </ol>

                                    <div class="login-form-field">
                                        <label tabindex="0">${gs.getMessage('6-digit verification code')}</label>
                                        <input id="txtResponse" class="form-control" type="text" placeholder="${gs.getMessage('XXX - XXX')}" aria-label="${gs.getMessage('Enter the 6 digit code generated by the authenticator app')}" name="txtResponse" autocomplete="off"/>
                                    </div>

                                    <button class="btn btn-primary"  data-toggle="modal" id="btnValidate" onclick="return validateResponse('${jvar_form_id}');">${gs.getMessage('Pair device and Login')}</button>
                                </j:if>
								-->
								
                                <j:if test="${totp.hasNext()}">
                                      <div id="tdSuccessDownload">
                                          <p tabindex="0">${gs.getMessage('You have successfully configured the authenticator app in your mobile device. If you have lost your device, please go to your user profile section to pair a new device.')}</p>
                                      </div>
                                </j:if>
                            </div>
                            <div class="login-card-footer text-center">
                                <a id="linkBypassSetup" class="btn btn-link" onclick="bypassSetup();" style="display:none;" href="#">
                                    ${gs.getMessage('Postpone setup')}
                                </a>
                                <p id="remainingBypassCountPara" style="display:none;">${gs.getMessage('Number of times MFA setup can be postponed is: ')}<span id="remainingBypassCount"></span></p>
                                <input type="hidden" name="hdnBypassSetup" id="hdnBypassSetup" />
                            </div>
                        </div>
                    </div>
                </div>
            </j:when>
            <!-- Heisenberg -->
            <j:otherwise>
                <g:requires name="styles/heisenberg/heisenberg_all.css" includes="true" />

                <div class="notification notification-failure" id="divNotification" style="display:none;">
                    <button data-dismiss="alert" class="btn-icon close icon-cross">
                        <span class="sr-only">Close</span>
                    </button>
                </div>

                <div class="ga-outer-container">
                    <div class="ga-header-container">
                        <h1 class="ga-label-header-large" tabindex="0">${gs.getMessage('Enable multi-factor authentication(MFA)')}</h1>
                        <a href="https://docs.servicenow.com/search?q=CSHelp:multifactor-authentication" target="_blank" id="linkLearnMore">${gs.getMessage('Learn more')}</a>
                        <a id="linkBypassSetup" onclick="bypassSetup();" style="display:none; padding-left: 1em;" href="#">${gs.getMessage('Postpone Setup')}</a>
                        <input type="hidden" name="hdnBypassSetup" id="hdnBypassSetup" />

                        <p class="ga-label" id="remainingBypassCountPara" style="display:none;">${gs.getMessage('Number of times MFA setup can be postponed is: ')}<span id="remainingBypassCount"></span></p>

                    </div>
                    <j:if test="${!totp.hasNext()}">
                    <div class="ga-flex-container" >
                        <div class="ga-flex-content">
						<p class="ga-label" tabindex="0">
							<g:no_escape>${ALLOW_JELLY:gs.getMessage("mfa_setup_step1")}</g:no_escape>
						</p>
                        </div>
                        <div class="ga-flex-content ga-flex-content-center">
						<label class="ga-label" tabindex="0">${gs.getMessage('mfa_setup_step2')}</label>			
                            <img id="imgCode" tabindex="0" alt="${gs.getMessage('Scan this QR code to pair your mobile device')}" />
						<label class="ga-label" tabindex="0" style=" text-align: center;">Or type in</label>
						<div style="float: right;">
							<div style="width: 100%;" tabindex="0" title="">
								<div id="lblCode" style="font-size: small;display: inline;padding-right: 20px;"></div>
								<span id="copy_code" aria-label="${gs.getMessage('Click to copy code')}" data-original-title="${gs.getMessage('Click to copy code')}" data-toggle="tooltip" class="btn btn-default sn-tooltip-basic icon icon-copy" title="" onclick="copyCodeToClipboard()" />
							</div>
						</div>
						<label class="ga-label" id="cod_cop_msg" style="text-align: center; color: rgb(31, 132, 118); margin-top: 5px; display: none;" tabindex="0"></label>
                        </div>

                        <div class="ga-flex-content">
						<label class="ga-label" tabindex="0">${gs.getMessage('mfa_setup_step3')}</label>
                            <span class="modal-footer flex">
                                <input id="txtResponse" class="col-sm-9 form-control" type="text" placeholder="${gs.getMessage('6-digit code')}" aria-label="${gs.getMessage('Enter the 6 digit code generated by the authenticator app')}" name="txtResponse" autocomplete="off"/>
                            </span>
                            <span class="modal-footer flex">
                                <button class="btn btn-primary"  data-toggle="modal" id="btnValidate" onclick="return validateResponse('${jvar_form_id}');">${gs.getMessage('Pair device and Login')}</button>
                            </span>
                        </div>
                    </div>
                        </j:if>
                </div>
            
            
                <j:if test="${totp.hasNext()}">
                    <div class="ga-outer-container">
                        <div class="ga-header-container">
                            <div class="ga-flex-content" id="tdSuccessDownload">
                                <p class="ga-label" tabindex="0">${gs.getMessage('You have successfully configured the authenticator app in your mobile device. If you have lost your device, please go to your user profile section to pair a new device.')} </p>
                            </div>
                        </div>
                    </div>
                </j:if>
            </j:otherwise>
        </j:choose>
    </g:ui_form>
</j:jelly>

ログイン時の振る舞い

実際に、Userがどのようにログインできるか見てみます。ここでは、test.core.mfa.user01というTest Userを用意します。UserのEnable Multifactor Authenticationフィールドをtrueにして、User-basedでMFAを適用しています。
次のように、User nameとPasswordでログインします。

MFA_User_Login.png

ログインすると、カスタマイズした画面が表示されます。
このとき、ServiceNowインスタンスでは、Software tokenが自動的に誤認された形でひも付き、かつログインセッションが切られています。

MFA_Setup.png

画面の案内に沿って、ログイン画面に遷移します。
User nameとPasswordで、再度ログインします。

MFA_User_Login.png

無事、Software tokenを紐付けることなく、Software tokenか、Email送信かを選択する画面に遷移しました。
このあとは、Email送信を選択して、通常通りダイアログを進めるだけで、UserのEmail宛にOTPを受け取り、それを入力することでログインに成功します。
なお、Software tokenを選択しても、OTPを入力する画面に遷移はしますが、実際に紐づいているTokenがないため、ログインすることはできません。この画面に戻ってくるしかありません。
ここで、Software token選択肢をなくせることが理想ですが、この画面はServiceNowインスタンスに編集できない形で埋め込まれているため、現時点ではカスタマイズできないようです。

Verify_Your_Identity.png

0
0
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
0
0