Halloween Sale 15% off Ends in See the plans
PHP

Emailed Login Code for Staff Accounts

Adds a second step for administrators and editors: after the correct password, WordPress emails a 6-digit code that must be entered to finish logging in.

Don’t have WPIDE yet? Install it on your site

Code

// Roles that need the emailed code.
$wpide_code_roles = array( 'administrator', 'editor' );
$wpide_code_minutes = 10;

add_action( 'login_form', function () {
    if ( isset( $_POST['log'] ) || isset( $_GET['wpide_code'] ) ) {
        echo '<p><label for="wpide_login_code">Login code (if we emailed you one)</label><input type="text" name="wpide_login_code" id="wpide_login_code" class="input" inputmode="numeric" autocomplete="one-time-code" pattern="[0-9]*" maxlength="6"></p>';
    }
} );

add_filter( 'authenticate', function ( $user ) use ( $wpide_code_roles, $wpide_code_minutes ) {
    if ( ! $user instanceof WP_User || ! array_intersect( $wpide_code_roles, (array) $user->roles ) ) {
        return $user;
    }

    // Only the interactive login form gets the second step (not XML-RPC or application passwords).
    if ( ! isset( $_POST['log'], $_POST['pwd'] ) ) {
        return $user;
    }

    $entered = isset( $_POST['wpide_login_code'] ) ? preg_replace( '/\D/', '', (string) wp_unslash( $_POST['wpide_login_code'] ) ) : '';
    $stored  = get_user_meta( $user->ID, 'wpide_login_code', true );

    if ( $entered && is_array( $stored ) && time() < (int) $stored['expires'] && (int) $stored['tries'] < 5 ) {
        if ( hash_equals( $stored['hash'], wp_hash( $entered ) ) ) {
            delete_user_meta( $user->ID, 'wpide_login_code' );
            return $user;
        }

        $stored['tries']++;
        update_user_meta( $user->ID, 'wpide_login_code', $stored );

        return new WP_Error( 'wpide_code_wrong', '<strong>Error:</strong> That login code is not correct. Enter your password and the code again.' );
    }

    $code = (string) wp_rand( 100000, 999999 );
    update_user_meta( $user->ID, 'wpide_login_code', array( 'hash' => wp_hash( $code ), 'expires' => time() + $wpide_code_minutes * MINUTE_IN_SECONDS, 'tries' => 0 ) );

    wp_mail(
        $user->user_email,
        sprintf( '[%s] Your login code: %s', wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ), $code ),
        sprintf( "Your login code is %s\n\nIt expires in %d minutes. If you did not try to log in, change your password now.", $code, $wpide_code_minutes )
    );

    return new WP_Error( 'wpide_code_sent', '<strong>Check your email.</strong> We sent you a 6-digit login code. Enter your password again together with the code.' );
}, 99 );

Notes

Make sure staff can receive email from the site before turning this on. Codes expire after 10 minutes and are locked after 5 wrong tries.

How to add this snippet

  1. Install WPIDE on your WordPress site (it is free) and open Code Snippets.
  2. Use snippet above adds it from the library in one click. Or copy the code into a new snippet with the same code type.
  3. It is added inactive: review the code and settings, then switch it on.

Have a snippet worth sharing?

Publish it to the library from WPIDE or right here. Sign in with any WordPress site that runs WPIDE, free or Pro. Public snippets are reviewed by the WPIDE team before they appear.

Which site should get this snippet?

Enter the address of your WordPress site. WPIDE opens there in a new tab with the snippet ready to review and save.

Remembered in this browser only, so you are asked once.

Report this snippet

Tell the WPIDE team what is wrong: broken code, something harmful, spam or copied work.