We’ve migrated our documentation to a new site, which means some URLs have changed. If you hit a broken link, submit a support ticket.
Subscriptions

Security Protection Against Account Takeover (Email Change)

Piano Identity Management now provides enhanced security for email address changes through the Security Protection feature. This feature helps prevent account access issues caused by incorrect email updates, protects users from unauthorized changes, and includes safeguards against account enumeration attacks.

Setup requirements

1. Update templates

Update My Account template

Navigate to Manage Templates Piano ID profile in My Account, and follow these steps:

Step 1: Add the code-resend flash message block

Insert this block above the .tp-form.form-horizontal div, alongside the existing flashProfileEmailResendConfirm block.

Before: (block did not exist)

After:

<div flashProfileEmailChangeCodeResend>
  <div class="complete-msg" data-e2e="email-change-code-resend-msg">
    <i class="complete-msg-icon"></i>
    <t>A new verification code has been sent. Please confirm your email.</t>
  </div>
</div>

Step 2: Add the "email change in progress" status row

Add a new control-group inside the form, before the existing *showIfResendConfirmationEmailNeeded row.

Before: (row did not exist)

After:

<div class="control-group">
  <div *showIfEmailChangeInProgress class="controls help email-status-message">
    <span class="title" data-e2e="emailChangeInProgressTitle"
    ><t>Email change in progress.</t>
      <a actionResumeEmailChange class="btn-link" data-e2e="emailChangeInProgressLink">
        <t>Continue confirmation</t>
      </a>
    </span>
  </div>
</div>

Step 3: Update the "resend confirmation email" row

The link is now inline within the <span> instead of a sibling <div>, and the class email-status-message is added to the controls wrapper.

Before:

<div class="control-group">
  <div *showIfResendConfirmationEmailNeeded class="controls help">
    <span class="title" data-e2e="resendTitle"><t>This email address is not confirmed.</t></span>
    <div>
      <a actionResendConfirmationEmail class="btn btn-link" data-e2e="resendLink">
        <t>Resend validation email</t>
      </a>
    </div>
  </div>
</div>

After:

<div class="control-group">
  <div *showIfResendConfirmationEmailNeeded class="controls help email-status-message">
    <span class="title" data-e2e="resendTitle"
    ><t>This email address is not confirmed.</t>
      <a actionResendConfirmationEmail class="btn-link" data-e2e="resendLink">
        <t>Resend validation email</t>
      </a>
    </span>
  </div>
</div>

Step 4: Remove the inline password field and replace with the password confirmation panel

Before:

<div class="control-group" *showIfEmailChanged>
  <label class="control-label" for="email-current_password">
    <t>Current Password</t>
  </label>
  <div class="controls">
    <input
      fieldProfileEmailCurrentPassword=""
      id="email-current_password"
      placeholder="{{ 'Current password' | t }}"
      type="password"
    />
    <span class="error-message" showIfPasswordInvalid><t>Password is required</t></span>
  </div>
</div>

After: (remove the above block entirely and add the password panel below the form fields)

<div *showIfEmailChangePasswordModal class="email-change-password-container">
  <div class="email-change-password-close">
    <button class="email-change-password-close-btn" actionCloseEmailChangePassword></button>
  </div>
  <div class="email-change-password-content">
    <div>
      <div *hideIfEmailChangePasswordFailed>
        <p class="email-change-password-hint">
          <t>To change your email, please enter your password for security reasons.</t>
        </p>
      </div>
      <p *showIfEmailChangePasswordFailed class="email-change-password-failed">
        <t>Failed to change email. Please, make sure that password is valid.</t>
      </p>
    </div>
    <div class="email-change-password-controls">
      <div class="email-change-password-icon-wrapper">
        <i class="email-change-password-icon"></i>
      </div>
      <input fieldProfileConfirmEmailChangePassword id="email-change_password"
             placeholder="{{ 'Password' | t }}" type="password">
      <button actionConfirmEmailChange class="btn">
        <t>Confirm</t>
      </button>
    </div>
  </div>
</div>

Step 5: Add the verification code confirmation panel

Add this block immediately after the password panel from Step 4.

Before: (block did not exist)

After:

<div *showIfEmailChangeCodeModal class="email-change-confirmation">
  <div class="email-change-confirmation-close">
    <button class="email-change-confirmation-close-btn" actionCloseEmailChangeCodeModal></button>
  </div>
  <div class="email-change-confirmation-content">
    <div *showIfConfirmingOldEmail class="email-change-confirmation-text">
      <p>
        <t>To confirm the changed email address</t>
        <b class="email-change-email" emailChangeNewEmail></b>,
        <t>please click the validation link or enter the confirmation code we sent to the old email address</t>
        <b class="email-change-email" emailChangeOldEmail></b>.
        <br>
        <t>If you can't access your old email, please contact our support team.</t>
      </p>
    </div>
    <div *showIfConfirmingNewEmail class="email-change-confirmation-text">
      <p>
        <t>Now confirm the new email address</t>
        <b class="email-change-email" emailChangeNewEmail></b>,
        <t>please click the validation link or enter the confirmation code we sent to the specified email.</t>
      </p>
    </div>
    <div class="email-change-confirmation-actions">
      <email-change-code-input></email-change-code-input>
      <a actionResendEmailChangeCode class="btn btn-link">
        <t>Resend</t>
      </a>
    </div>
  </div>
</div>

Step 6: Wrap the Save/Cancel buttons with conditional visibility directives

The Save/Cancel actions must be hidden while either modal panel is open.

Before:

<div class="tp-data-block-actions">
  <button actionUpdateProfileEmail class="btn btn-small btn-primary">
    <t>Save</t>
  </button>
  <button actionRestoreProfileEmail class="btn btn-small">
    <t>Cancel</t>
  </button>
</div>

After:

<div *hideIfEmailChangeCodeModal>
  <div *hideIfEmailChangePasswordModal class="tp-data-block-actions">
    <button actionUpdateProfileEmail class="btn btn-small btn-primary">
      <t>Save</t>
    </button>
    <button actionRestoreProfileEmail class="btn btn-small">
      <t>Cancel</t>
    </button>
  </div>
</div>

Update Piano ID template

Navigate to Manage Templates Piano ID Digital code, and follow these steps:

Step 1: Update the registration digital code template

Remove the hardcoded user@test.com placeholder from the registration confirmation text. The user's actual email is rendered by the userEmail directive on the adjacent <span>.

Before:

<p *showIfDigitalCodeAndMagicLinkWithSecurityProtectionEnabledOnRegistration class="pn-email-confirmation__text">
  <t>
    To complete registration, please click the validation link or enter the confirmation code we emailed to
    user@test.com
  </t>
  <span class="pn-email-confirmation__email" userEmail></span>
</p>

After:

<p *showIfDigitalCodeAndMagicLinkWithSecurityProtectionEnabledOnRegistration class="pn-email-confirmation__text">
  <t>
    To complete registration, please click the validation link or enter the confirmation code we emailed to
  </t>
  <span class="pn-email-confirmation__email" userEmail></span>
</p>

Breaking Changes

  1. Inline password field removed. The <div class="control-group" *showIfEmailChanged> block containing fieldProfileEmailCurrentPassword and showIfPasswordInvalid must be removed from custom templates. Retaining it will cause a duplicate/conflicting form control. Replace it with the password panel described in Step 4.

  2. Save/Cancel buttons must be wrapped. Without the *hideIfEmailChangeCodeModal / *hideIfEmailChangePasswordModal wrappers added in Step 6, the Save and Cancel buttons will remain visible while the confirmation panels are open, allowing conflicting user actions.

  3. Resend link markup changed. The <a> inside *showIfResendConfirmationEmailNeeded has moved from a sibling <div> to inside the <span>. Custom CSS targeting .controls.help > div > a will stop matching. Use the updated structure from Step 3.


Notes

  • Close buttons for both panels (.email-change-confirmation-close-btn, .email-change-password-close-btn) inherit shared styles from the phone confirmation and delete-account close button rules. No additional icon markup is required beyond the button element itself.

  • The flashProfileEmailChangeCodeResend directive renders its host for 3 seconds on each resend event and sets role="alert" automatically for screen reader accessibility.

  • The popover will display:

    • For password users: Password confirmation field before initiating email change

    • For passwordless users: Confirmation code/link flow

    • Status messages for pending email changes

    • Options to resend confirmation or cancel pending changes

2. Configure email templates

Go to Manage Email Manager, activate and customize three new templates:

image-20260727-095853.png

Template

ID Email Change Alert

ID Email Change Verification

ID Email Change Confirmation

Sent to

Old/previous address

Old/previous address

New address

When sent

Password user changes email; Security Protection enabled

Passwordless user changes email; Security Protection enabled

After old-email verification

Subject

"Confirmation of your email address change"

"Confirmation of your email address change"

"Email Change Confirmation Required"

Variables

user_first_name, user_last_name, business_name

user_first_name, user_last_name, confirm_email_change_link, digital_code, business_name

confirm_email_change_link, digital_code, expiration_time, support_email, business_name

Expiration: Pending email changes expire after 72 hours if unconfirmed.

  • Resending the confirmation code resets the full 72h window (the pending record is rewritten on resend).

  • The 6-digit code and the confirmation link live in the same pending record, so they expire together. The email copy pulls the expiration value dynamically, so the emails always match the TTL.

3. Enable security protection

Navigate to Piano ID settings, under Edit BusinessUser Provider, and toggle on Enable Security Protection against account takeover

image-20260716-081550.png

How it works

For users with passwords:

  1. User enters new email address in My Account

  2. System prompts for current password confirmation

    image-20260716-081035.png


  3. Notification email sent to old address

  4. Confirmation email sent to new address

  5. Email changes only after user confirms via link or code

    image-20260716-081121.png


For passwordless users:

  1. User enters new email address in My Account

    image-20260716-081052.png


  2. Confirmation code or link sent to old address

    image-20260716-081352.png


  3. User confirms via old email

  4. Confirmation email sent to new address

  5. Email changes only after user confirms via link or code

    image-20260716-081420.png


Configuration options:

  • DOI off / Security off: Legacy behavior, immediate email change without confirmation.

  • DOI off / Security on: Anti-enumeration protection without DOI.

  • DOI on / Security off: Email change requires confirmation, without anti-enumeration protection.

  • DOI on / Security on: Full security with email confirmation and anti-enumeration protection.


Last updated: