Docs

Code examples

9 min readUpdated October 13, 2026

Ready-to-use snippets for the most common jobs. Each one is short and can be pasted into a child theme’s functions.php, a small custom plugin, or a code snippets plugin. Explanations of each function and hook are in Functions reference, Actions and filters, Passwordless API, REST API and JavaScript API (NAHNLSAuth).

Before you copy anything:

  • Guard calls with function_exists(), so your site keeps working if Nahnu Login Shield is ever deactivated.
  • Test on a staging copy first, with your CAPTCHA provider switched on.
  • Code that uses a feature only works when that feature is switched on in the plugin’s settings.
  • Replace example addresses, form names and page paths with your own.

Protect a form#

A custom contact form

The form, with the widget before the submit button:

php

<form method="post">
    <input type="text" name="your_name">
    <input type="email" name="your_email">
    <textarea name="message"></textarea>

    <?php
    if ( function_exists( 'nahnls_widget_html' ) ) {
        echo nahnls_widget_html( [ 'form_id' => 'contact' ] ); // phpcs:ignore
    }
    ?>

    <button type="submit">Send</button>
</form>
Code
<form method="post">
    <input type="text" name="your_name">
    <input type="email" name="your_email">
    <textarea name="message"></textarea>

    <?php
    if ( function_exists( 'nahnls_widget_html' ) ) {
        echo nahnls_widget_html( [ 'form_id' => 'contact' ] ); // phpcs:ignore
    }
    ?>

    <button type="submit">Send</button>
</form>

The handler that checks the submission:

php

add_action( 'init', function () {
    if ( empty( $_POST['your_name'] ) ) {
        return;
    }

    if ( function_exists( 'nahnls_check_submission' ) ) {
        $result = nahnls_check_submission( 'contact' );
        if ( is_wp_error( $result ) ) {
            wp_die( esc_html( $result->get_error_message() ) );
        }
    }

    // The submission passed. Process the form here.
} );
Code
add_action( 'init', function () {
    if ( empty( $_POST['your_name'] ) ) {
        return;
    }

    if ( function_exists( 'nahnls_check_submission' ) ) {
        $result = nahnls_check_submission( 'contact' );
        if ( is_wp_error( $result ) ) {
            wp_die( esc_html( $result->get_error_message() ) );
        }
    }

    // The submission passed. Process the form here.
} );

A form that prints its own CAPTCHA markup

If you don’t use nahnls_widget_html(), you must still include the hidden form nonce:

php

if ( function_exists( 'nahnls_form_nonce_field' ) ) {
    echo nahnls_form_nonce_field(); // phpcs:ignore
}

Without a valid nonce, nahnls_check_submission() returns “Security check expired. Please reload the page and try again.”

A form that sends data with AJAX

The handler checks the submission and answers with JSON:

php

add_action( 'wp_ajax_nopriv_my_form', 'my_form_handler' );
add_action( 'wp_ajax_my_form', 'my_form_handler' );

function my_form_handler() {
    if ( function_exists( 'nahnls_check_submission' ) ) {
        $result = nahnls_check_submission( 'my_form' );
        if ( is_wp_error( $result ) ) {
            wp_send_json_error( [ 'message' => $result->get_error_message() ] );
        }
    }

    // Process the form here.
    wp_send_json_success();
}
Code
add_action( 'wp_ajax_nopriv_my_form', 'my_form_handler' );
add_action( 'wp_ajax_my_form', 'my_form_handler' );

function my_form_handler() {
    if ( function_exists( 'nahnls_check_submission' ) ) {
        $result = nahnls_check_submission( 'my_form' );
        if ( is_wp_error( $result ) ) {
            wp_send_json_error( [ 'message' => $result->get_error_message() ] );
        }
    }

    // Process the form here.
    wp_send_json_success();
}

A form built by JavaScript (login drawer, modal)

On page load, make sure the widget script is there:

php

add_action( 'wp_enqueue_scripts', function () {
    if ( function_exists( 'nahnls_enqueue_widget_assets' ) ) {
        nahnls_enqueue_widget_assets();
    }
} );

In the handler that returns the form:

php

$widget = nahnls_widget_html( [ 'form_id' => 'nav_login', 'context' => 'ajax' ] );
wp_send_json_success( [
    'html' => $form_html . wp_kses( $widget, nahnls_markup_allowed_html() ),
] );

In your script, after you insert the form, and after a failed attempt:

js

drawerBody.innerHTML = loginFormHtml;
window.NAHNLSWidget.init( drawerBody );

// after a failed login:
window.NAHNLSWidget.reset( drawerBody );

In the handler that receives the login:

php

$check = nahnls_check_submission( 'nav_login' );
if ( is_wp_error( $check ) ) {
    wp_send_json_error( [ 'message' => $check->get_error_message() ] );
}

See CAPTCHA widget in JavaScript forms.

Wrap the widget in your own markup

php

add_filter( 'nahnls_widget_html_output', function ( string $html, array $args ): string {
    return '<div class="my-captcha-row">' . $html . '</div>';
}, 10, 2 );

Skip or react to checks#

Skip the check for logged-in editors

php

add_filter( 'nahnls_check_submission_enabled', function ( bool $enabled, array $context ): bool {
    if ( current_user_can( 'edit_posts' ) ) {
        return false; // Skip all checks for editors.
    }
    return $enabled;
}, 10, 2 );

Skip the check for one form only

php

add_filter( 'nahnls_check_submission_enabled', function ( bool $enabled, array $context ): bool {
    return 'internal_form' === $context['form_id'] ? false : $enabled;
}, 10, 2 );

Write blocked submissions to the Shield log

php

add_action( 'nahnls_custom_check_blocked', function ( WP_Error $error, array $context ) {
    nahnls_log_event( 'captcha_fail', $context['form_id'] . ' -- ' . $error->get_error_code() );
}, 10, 2 );

Log your own events

php

add_action( 'my_plugin_fraud_detected', function ( string $ip, string $user ) {
    nahnls_log_event( 'custom_block', 'Fraud detected by My Plugin', $ip, $user );
} );

The entry appears on the Log tab.

Keep forms working on cached pages

php

add_filter( 'nahnls_form_nonce_life', fn() => 2 * DAY_IN_SECONDS );

See Caching and login pages.

Block a custom REST endpoint from locked-out IPs#

php

add_action( 'rest_api_init', function () {
    register_rest_route( 'my-plugin/v1', '/submit', [
        'methods'             => 'POST',
        'callback'            => 'my_plugin_handle_submit',
        'permission_callback' => function () {
            if ( function_exists( 'nahnls_is_locked_out' ) && nahnls_is_locked_out() ) {
                return new WP_Error( 'locked', 'Too many failed attempts.', [ 'status' => 429 ] );
            }
            return true;
        },
    ] );
} );

A passkey button and a magic-link form in a custom login form

php

if ( function_exists( 'nahnls_passkey_button' ) ) {
    nahnls_passkey_button( [
        'label'    => __( 'Use a passkey' ),
        'redirect' => home_url( '/members/' ),
        'echo'     => true,
    ] );
}
if ( function_exists( 'nahnls_magic_link_form' ) ) {
    nahnls_magic_link_form( [
        'redirect' => home_url( '/members/' ),
        'echo'     => true,
    ] );
}
Code
if ( function_exists( 'nahnls_passkey_button' ) ) {
    nahnls_passkey_button( [
        'label'    => __( 'Use a passkey' ),
        'redirect' => home_url( '/members/' ),
        'echo'     => true,
    ] );
}
if ( function_exists( 'nahnls_magic_link_form' ) ) {
    nahnls_magic_link_form( [
        'redirect' => home_url( '/members/' ),
        'echo'     => true,
    ] );
}

In a page builder, use shortcodes in a Shortcode or HTML element:

[nahnls_passkey_button redirect="/members/"]
[nahnls_magic_link_form redirect="/members/"]

A passkey button inside the standard login markup

php

<form method="post" action="<?php echo esc_url( wp_login_url() ); ?>">
    <input type="text" name="log" autocomplete="username webauthn">
    <input type="password" name="pwd">
    <label><input type="checkbox" name="rememberme" value="forever"> Remember me</label>
    <button type="submit">Log in</button>
    <?php echo nahnls_passkey_button( [ 'redirect' => home_url( '/account/' ) ] ); // phpcs:ignore ?>
</form>

A link to the built-in magic-link page

php

<a href="<?php echo esc_url( nahnls_magic_link_url( home_url( '/account/' ) ) ); ?>">Email me a login link</a>

Invite a new member by magic link

php

add_action( 'my_plugin_member_approved', function ( int $user_id ) {
    if ( ! function_exists( 'nahnls_send_magic_link' ) ) {
        return;
    }
    $r = nahnls_send_magic_link( $user_id, home_url( '/getting-started/' ), [ 'force' => true ] );
    if ( is_wp_error( $r ) ) {
        error_log( 'Invite failed: ' . $r->get_error_code() );
    }
} );

Brand the login-link email

php

add_filter( 'nahnls_magic_link_email', function ( array $email, WP_User $user, string $url ): array {
    $email['subject']   = sprintf( 'Your sign-in link for %s', get_bloginfo( 'name' ) );
    $email['message']   = my_theme_email_template( [
        'heading' => 'Hi ' . $user->display_name,
        'button'  => [ 'label' => 'Sign in', 'url' => $url ],
    ] );
    $email['headers'][] = 'Reply-To: support@example.com';
    return $email;
}, 10, 3 );

Add a support line to every plugin email

php

add_filter( 'nahnls_email_args', function ( array $args, string $type ): array {
    $args['intro'] .= "\n\nNeed help? support@example.com";
    return $args;
}, 10, 2 );

Only allow passkeys for paying members

php

add_filter( 'nahnls_passkey_user_allowed', function ( bool $allowed, WP_User $user ): bool {
    if ( ! $allowed ) {
        return false;
    }
    return function_exists( 'pmpro_hasMembershipLevel' )
        ? pmpro_hasMembershipLevel( [ 'pro', 'team' ], $user->ID )
        : $allowed;
}, 10, 2 );

Send people to different pages, and shorten staff links

php

add_filter( 'nahnls_passkey_login_redirect', fn( string $url, WP_User $u ) =>
    user_can( $u, 'edit_posts' ) ? admin_url() : home_url( '/account/' ), 10, 2 );

add_filter( 'nahnls_magic_link_redirect', fn( string $url, WP_User $u ) =>
    home_url( '/account/' ), 10, 2 );

add_filter( 'nahnls_magic_link_ttl', fn( int $s, WP_User $u ) =>
    user_can( $u, 'manage_options' ) ? 5 * MINUTE_IN_SECONDS : $s, 10, 2 );

Remind users who only have email codes

php

$st = nahnls_user_2fa_status( get_current_user_id() );
if ( $st['enabled'] && [ 'email' ] === $st['methods'] ) {
    echo '<a href="/account/security/">Add an authenticator app or passkey</a>';
}

React to passkey and magic-link sign-ins

php

add_action( 'nahnls_passkey_login', function ( WP_User $user, array $credential ) {
    // For example, record the sign-in method in your analytics.
}, 10, 2 );

add_action( 'nahnls_magic_link_login', function ( WP_User $user, string $ip ) {
    // A magic link was used.
}, 10, 2 );

A login that runs over AJAX, with two-factor authentication#

PHP handler for a login drawer or a headless form:

php

$check = nahnls_check_submission( 'nav_login' );
if ( is_wp_error( $check ) ) {
    wp_send_json_error( [ 'message' => $check->get_error_message() ], 400 );
}

$user = wp_authenticate( $login, $password ); // No cookie yet.
if ( is_wp_error( $user ) ) {
    wp_send_json_error( [ 'message' => $user->get_error_message() ], 401 );
}

if ( function_exists( 'nahnls_user_requires_2fa' ) && nahnls_user_requires_2fa( $user ) ) {
    $c = nahnls_start_2fa_challenge( $user, [
        'remember' => $remember,
        'redirect' => $redirect,
        'origin'   => 'wc',
    ] );
    if ( is_wp_error( $c ) ) {
        $data = $c->get_error_data();
        wp_send_json_error( [
            'message'       => $c->get_error_message(),
            'challenge_url' => $data['challenge_url'] ?? '',
        ], 500 );
    }
    wp_send_json_success( [ 'two_factor' => $c ] ); // The drawer shows the code step.
}

wp_set_auth_cookie( $user->ID, $remember );
do_action( 'wp_login', $user->user_login, $user );
wp_send_json_success( [ 'redirect' => $redirect ] );
Code
$check = nahnls_check_submission( 'nav_login' );
if ( is_wp_error( $check ) ) {
    wp_send_json_error( [ 'message' => $check->get_error_message() ], 400 );
}

$user = wp_authenticate( $login, $password ); // No cookie yet.
if ( is_wp_error( $user ) ) {
    wp_send_json_error( [ 'message' => $user->get_error_message() ], 401 );
}

if ( function_exists( 'nahnls_user_requires_2fa' ) && nahnls_user_requires_2fa( $user ) ) {
    $c = nahnls_start_2fa_challenge( $user, [
        'remember' => $remember,
        'redirect' => $redirect,
        'origin'   => 'wc',
    ] );
    if ( is_wp_error( $c ) ) {
        $data = $c->get_error_data();
        wp_send_json_error( [
            'message'       => $c->get_error_message(),
            'challenge_url' => $data['challenge_url'] ?? '',
        ], 500 );
    }
    wp_send_json_success( [ 'two_factor' => $c ] ); // The drawer shows the code step.
}

wp_set_auth_cookie( $user->ID, $remember );
do_action( 'wp_login', $user->user_login, $user );
wp_send_json_success( [ 'redirect' => $redirect ] );

Browser side, for the second step:

js

NAHNLSAuth.twoFactorVerify(c.nonce_key, { code: input.value })
    .then(({ redirect }) => window.location.assign(redirect))
    .catch((e) => {
        if (e.code === 'nahnls_2fa_invalid') {
            showError(e.message + ' (' + e.attempts_left + ' left)');
        } else if (e.code === 'nahnls_2fa_expired' || e.code === 'nahnls_2fa_locked') {
            backToPassword(e.message);
        } else {
            showError(e.message);
        }
    });

// "Email me a code"
NAHNLSAuth.twoFactorEmail(c.nonce_key).then(({ email_masked }) => showMessage('Sent to ' + email_masked));

// Passkey button
NAHNLSAuth.twoFactorPasskey(c.nonce_key).then(({ redirect }) => window.location.assign(redirect));
Code
NAHNLSAuth.twoFactorVerify(c.nonce_key, { code: input.value })
    .then(({ redirect }) => window.location.assign(redirect))
    .catch((e) => {
        if (e.code === 'nahnls_2fa_invalid') {
            showError(e.message + ' (' + e.attempts_left + ' left)');
        } else if (e.code === 'nahnls_2fa_expired' || e.code === 'nahnls_2fa_locked') {
            backToPassword(e.message);
        } else {
            showError(e.message);
        }
    });

// "Email me a code"
NAHNLSAuth.twoFactorEmail(c.nonce_key).then(({ email_masked }) => showMessage('Sent to ' + email_masked));

// Passkey button
NAHNLSAuth.twoFactorPasskey(c.nonce_key).then(({ redirect }) => window.location.assign(redirect));

Load the script on that page first:

php

add_action( 'wp_enqueue_scripts', function () {
    if ( defined( 'NAHNLS_API_VERSION' ) && NAHNLS_API_VERSION >= 2 && function_exists( 'nahnls_enqueue_auth_script' ) ) {
        nahnls_enqueue_auth_script();
        nahnls_enqueue_widget_assets();
    }
} );

JavaScript and headless#

Sign in with a passkey

js

NAHNLSAuth.passkeyLogin({ redirect: '/members/' })
    .then(({ redirect }) => window.location.assign(redirect))
    .catch((e) => showError(e.message));

Request a magic link

js

NAHNLSAuth.requestMagicLink('user@example.com', '/members/')
    .then(() => showMessage('Check your inbox.'))
    .catch((e) => showError(e.message));

Listen for events

js

document.addEventListener('nahnls:passkey:login', function (e) {
    analytics.track('login', { method: 'passkey', user: e.detail.user_id });
});

A React button

jsx

// Load nahnls-auth.js and set window.NAHNLSAuthConfig = { restUrl: 'https://site.example/wp-json/nahnls/v1/' }.
function PasskeyButton() {
    const [error, setError] = React.useState('');
    const signIn = () =>
        window.NAHNLSAuth.passkeyLogin({ redirect: '/app' })
            .then(({ redirect, pending_2fa }) => {
                window.location.assign(pending_2fa ? redirect : '/app');
            })
            .catch((e) => setError(e.message));
    return (
        <>
            <button onClick={signIn}>Sign in with a passkey</button>
            {error && <p role="alert">{error}</p>}
        </>
    );
}
Code
// Load nahnls-auth.js and set window.NAHNLSAuthConfig = { restUrl: 'https://site.example/wp-json/nahnls/v1/' }.
function PasskeyButton() {
    const [error, setError] = React.useState('');
    const signIn = () =>
        window.NAHNLSAuth.passkeyLogin({ redirect: '/app' })
            .then(({ redirect, pending_2fa }) => {
                window.location.assign(pending_2fa ? redirect : '/app');
            })
            .catch((e) => setError(e.message));
    return (
        <>
            <button onClick={signIn}>Sign in with a passkey</button>
            {error && <p role="alert">{error}</p>}
        </>
    );
}

Straight to the REST routes

js

const { publicKey } = await fetch('/wp-json/nahnls/v1/passkeys/login/options', { method: 'POST' })
    .then(r => r.json());

const cred = await navigator.credentials.get({
    publicKey: NAHNLSAuth.util.decodeRequestOptions(publicKey)
});

await fetch('/wp-json/nahnls/v1/passkeys/login/verify', {
    method: 'POST',
    credentials: 'include',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ credential: NAHNLSAuth.util.encodeAssertion(cred), redirect: '/app' })
});
Code
const { publicKey } = await fetch('/wp-json/nahnls/v1/passkeys/login/options', { method: 'POST' })
    .then(r => r.json());

const cred = await navigator.credentials.get({
    publicKey: NAHNLSAuth.util.decodeRequestOptions(publicKey)
});

await fetch('/wp-json/nahnls/v1/passkeys/login/verify', {
    method: 'POST',
    credentials: 'include',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ credential: NAHNLSAuth.util.encodeAssertion(cred), redirect: '/app' })
});

Allow a front end on another host

php

add_filter( 'nahnls_rest_allowed_origins', fn( array $hosts ) => [ ...$hosts, 'app.example.com' ] );

You also need CORS headers for the REST API, and credentials: 'include' on your requests. See REST API.

Integrations#

Leave one SureForms form unprotected

php

add_filter( 'nahnls_sureforms_form_enabled', function ( bool $enabled, int $form_id ): bool {
    return 42 === $form_id ? false : $enabled;
}, 10, 2 );

Skip signup email verification for trusted accounts

php

add_filter( 'nahnls_signup_verify_skip', function ( bool $skip, int $user_id ): bool {
    return $skip || user_can( $user_id, 'manage_options' );
}, 10, 2 );

WooCommerce, MemberPress and other login forms

With Show on WooCommerce turned on in the Passwordless tab, the passkey button and “Email me a login link” appear on the WooCommerce My Account login form automatically, and the Login & security tab is added. For MemberPress, Ultimate Member and similar plugins, put [nahnls_ passkey _button] or [nahnls_ magic_link _form] in the plugin’s login template, and give members a page with [nahnls_ account _security].

*Added spaces to shortcodes so they do not load

Appearance#

Brand the Login & security panel

css

.nahnls-ap,
.nahnls-2fa-wc {
    --nahnls-primary: #7c3aed;
    --nahnls-radius: 12px;
}

Turn the plugin’s front-end styling off

php

add_filter( 'nahnls_front_css_enabled', function ( $enabled, $handle ) {
    return in_array( $handle, [ 'nahnls-account-panel', 'nahnls-2fa-front' ], true ) ? false : $enabled;
}, 10, 2 );

See Front-end theming.

Check that the plugin is there#

php

if ( function_exists( 'nahnls_widget_html' ) ) {
    echo nahnls_widget_html(); // phpcs:ignore
}

if ( defined( 'NAHNLS_API_VERSION' ) && NAHNLS_API_VERSION >= 2 ) {
    // nahnls_feature_enabled(), nahnls_user_requires_2fa(), nahnls_start_2fa_challenge() ...
}

#

Was this helpful?

This website uses cookies to enhance your browsing experience and ensure the site functions properly. By continuing to use this site, you acknowledge and accept our use of cookies.

Accept All Accept Required Only