Code examples
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> 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.
} ); 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();
} 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;
},
] );
} ); Passkeys and magic links#
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,
] );
} 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 ] ); 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)); 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>}
</>
);
} 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' })
}); 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?
Thanks for your feedback!
✓ Feedback received. Thank you!