FluentAuth is built on WordPress hooks and exposes its own. All are prefixed fluent_auth/,
with one exception noted below. This page lists the ones meant for other code to use; the
settings screens cover everything else.
Rows marked 3.0 were added in FluentAuth 3.0.0. Everything else has been available for longer.
Login decisions
| Filter | Arguments | Purpose |
|---|---|---|
can_user_login | $allowed, $user, $provider | Refuse a login. $provider is google, github or facebook, and empty for the designed login page |
login_security_enabled | $enabled | Turn the attempt limit off, for a staging site with automated logins. 3.0 |
account_attempt_limit | $limit, $user | Failures against one account, across addresses, before a challenge is required. Default 3 × the per-IP limit. 3.0 |
user_ip | $ip | Override the resolved visitor address |
trusted_proxies | $proxies | Add trusted proxies in code. 3.0 |
cloudflare_ip_ranges | $ranges | Replace the built-in Cloudflare ranges. 3.0 |
low_level_user_roles | $roles, $raw_roles | Roles kept out of wp-admin |
can_user_login runs on the paths that set the session themselves, which is where the normal
login chain cannot be used. The ordinary login form and magic links do not reach it; refuse
those with WordPress’s own authenticate filter. A WP_Error return shows its own message on
a provider login. Everywhere else only the truthiness is read, so return false and the
visitor sees the generic refusal.
// Only allow administrators in through a social provider during a maintenance window.
add_filter('fluent_auth/can_user_login', function ($allowed, $user, $provider) {
if (get_option('site_maintenance') && !user_can($user, 'manage_options')) {
return new WP_Error('maintenance', 'The site is under maintenance. Try again later.');
}
return $allowed;
}, 10, 3);
Two-factor
| Hook | Type | Purpose |
|---|---|---|
2fa_challenge_required | filter $required, $user | Require a second factor for a user whose role does not. It cannot waive one: an enrolled method, or a role that requires one, is resolved before this is consulted. 3.0 |
2fa_methods | filter $methods | Register another method. Each entry is a BaseTwoFaMethod instance. 3.0 |
enforce_2fa_on_auth_cookie | filter $enforce, $user_id | Exempt a programmatic login from the second factor. 3.0 |
ask_to_set_up_totp | filter $ask, $user | Show or hide the post-login enrolment nudge. 3.0 |
2fa_code_request_limit, 2fa_code_request_timing | filter $value, $user | Rate limit for emailed codes. Both default to the login attempt-limit settings. 3.0 |
totp_enabled | filter $enabled, $user | Offer the authenticator app to this user, or not. 3.0 |
totp_activated, totp_disabled | action $user_id | Enrolment lifecycle. 3.0 |
recovery_codes_generated | action $user_id, $count | A fresh set of recovery codes was issued. 3.0 |
recovery_code_used | action $user_id, $remaining | One was spent. $remaining is what is left. 3.0 |
secret_key_material | filter $material | Supply the encryption key from somewhere other than wp-config.php. 3.0 |
fls_send_2fa_code | action $data, $user, $auto_login_url | A code was issued; send it by another channel too. The code is $data['two_fa_code'] |
fls_send_2fa_code carries no fluent_auth/ prefix, for compatibility with code written
against 1.x. It fires even when the plugin’s own email was held back by the rate limit above.
Passkeys and WebAuthn
Everything runs on your own server; there is no service to configure. These are for sites that need to change how the ceremony is framed, usually a subdomain or a headless front end. All added in 3.0.
| Hook | Type | Purpose |
|---|---|---|
passkey_enabled | filter $enabled, $user | Offer passkeys to this user, or not |
passkey_allow_without_fallback | filter $allow, $user | Let a user hold a passkey as their only factor, with no recovery codes behind it. Off by default for good reason; see Passkeys |
webauthn_rp_id | filter $host | The relying-party ID. The registrable domain, so a passkey made on www. still answers on the bare host |
webauthn_rp_name | filter $name | The site name shown in the browser prompt |
webauthn_allowed_origins | filter $origins | Origins an assertion may come from. Add one for a headless front end |
webauthn_user_verification | filter $requirement | Default required, so the device asks for a biometric or PIN |
webauthn_creation_options, webauthn_request_options | filter $options, $user | The raw options handed to the browser |
passkey_registered | action $user_id, $credential_id | A passkey was enrolled |
passkey_removed, passkeys_cleared | action $user_id[, $id] | One removed, or all of them |
passkey_verified | action $user, $credential | A sign-in was proved with a passkey |
passkey_verification_failed, passkey_registration_failed | action $user, $message | The ceremony failed |
webauthn_sign_count_reused | action $credential, $presented, $stored | The authenticator’s counter did not advance, which can mean a cloned credential |
Magic login
| Hook | Type | Purpose |
|---|---|---|
magic_login_can_use | filter $can, $user | Per-user availability |
will_disable_magic_form | filter $disable | Hide the form on wp-login |
default_token_validity | filter $minutes, $user | Link lifetime; default 10 |
login_token_by_user_id | filter '', $user_id, $minutes | Returns a single-use token, not a URL |
login_token_by_user_email | filter '', $email, $minutes | Same, by address |
Both token filters return an empty string when magic login is off or the user does not exist.
The token goes in the fls_al query argument:
// A sign-in link valid for 15 minutes.
$token = apply_filters('fluent_auth/login_token_by_user_id', '', $user_id, 15);
$link = $token ? add_query_arg('fls_al', $token, site_url('index.php')) : '';
Signup and forms
| Hook | Type | Purpose |
|---|---|---|
registration_form_fields | filter $fields | Add, remove or reorder signup fields |
signup_default_role | filter $role, $form_data | Role for new accounts through the forms |
user_role | filter $role | Role for an account created by a social sign-up |
signup_enabled | filter $enabled | Override the WordPress “anyone can register” check for FluentAuth forms |
signup_form_data | filter $data | Sanitised submission before the account is created |
before_signup_validation, after_signup_validation | action $form_data | Around validation |
before_creating_user | action $form_data | Before wp_insert_user() |
after_creating_user | action $user_id, $data | After it |
auto_login_after_signup | filter $auto, $user | Sign the new user in immediately, or not |
verify_signup_email | filter $verify, $form_data | Require email verification for this signup |
signup_verification_email_body | filter $body, $code, $form_data | The verification email |
signup_complete_response | filter $response, $user | The message and redirect after signup |
signup_policy_url | filter $url | The terms link under the form |
auth_shortcode_defaults | filter $defaults | Default attributes for the shortcodes |
login_form_args | filter $args | Arguments passed to wp_login_form() |
already_logged_in_message | filter $html | What a signed-in visitor sees instead of a form. Return an empty string to render nothing |
validate_password_length | filter $check | Return false to drop the built-in six-character minimum and apply your own |
reset_password_form | filter $fields | The reset form |
reset_password_message | filter $message, $user, $link | The reset email |
reset_password_mail_subject | filter $subject | Its subject |
extra_login_page_wrap_css_class | filter $classes | Classes on the designed login page. End the string with a space; the layout class is appended directly to it |
Redirects
| Hook | Type | Purpose |
|---|---|---|
login_redirect_url | filter $url, $user, $request | Destination after a login through the shortcode forms. Every other route uses WordPress’s own login_redirect |
validated_redirect | filter $url, $requested, $fallback | Allow a destination the same-site check refused. Only fires when the check changed the URL |
respect_front_login_url | filter $respect | Whether a front-end form’s page counts as the origin |
social_redirect_to | filter $url | Where a social login lands |
Login lifecycle
| Action | Arguments | Fires |
|---|---|---|
before_logging_in_user | $user_id | Before the session is issued by a signup auto-login |
after_logging_in_user | $user_id | After it |
user_login_success | $user | A login was recorded |
login_attempts_checked | $user | After the limit check, before the second factor |
login_media_labels (filter) | $labels | Rename the methods in the log. 3.0 |
The first two fire only for the auto-login that follows a signup through the shortcode forms.
For every login by any route, hook user_login_success, or WordPress’s own wp_login.
Social login
| Hook | Type | Purpose |
|---|---|---|
social/rendering_button_{provider} | action $button | Fires just before a provider’s button is printed. Echo your own markup here; the button itself is still printed afterwards |
is_google_one_tap_enabled | filter | Listened on, not fired. apply_filters('fluent_auth/is_google_one_tap_enabled', false) tells you whether One-Tap is on |
init_google_popup_auth | action | Listened on, not fired. do_action('fluent_auth/init_google_popup_auth', ['type' => 'inline', 'delay' => 2]) loads One-Tap on a page of your own |
The last two are the reverse of the usual arrangement: FluentAuth registers callbacks on them so that other code can call One-Tap rather than reach into the handler class.
Security checks and scanning
All added in 3.0.
| Hook | Type | Purpose |
|---|---|---|
security_checks | filter $checks | Add checks to the findings screen |
recommended_settings | filter $settings | What “Apply recommended” applies |
guessable_usernames | filter $names | The list the admin-username check uses |
dormant_admin_days | filter $days | Idle days before an administrator is dormant; default 365 |
backup_file_patterns | filter $patterns | What counts as a backup file in the web root |
is_local_site | filter $is_local, $host, $environment | Whether to treat this install as local, which stands down the HTTPS finding |
integrity_scan_budget, baseline_scan_budget | filter $seconds | Time per cron run; both default to 20 |
integrity_plugin_targets, integrity_theme_targets | filter $slugs | What the scanner covers |
integrity_extension_ignore_patterns | filter $patterns | Paths to skip inside extensions |
baseline_extensions | filter $exts | File types the baseline hashes |
uploads_probe_ttl | filter $seconds, $result | How long the “PHP in uploads” result is cached |
report_extension_inventory | filter $inventory | The plugin and theme list sent with a scan report |
alerts_api_url | filter $url | The scanning service endpoint |
wp_config_path | filter $path | Where wp-config.php lives, for an unusual layout |
Emails
| Hook | Type | Purpose |
|---|---|---|
fluentcrm_auth/email_smartcodes | filter $codes | The smartcodes offered in the system-email editor. Applied to the user group and again to the site group |
smartcode_fallback | filter $raw_code, $user | Value for an unknown smartcode. $raw_code is the tag as written |
wp_system_email_head | action | Extra <head> content in the email template. Echo it; no arguments |
Admin
| Hook | Type | Purpose |
|---|---|---|
app_permission | filter $cap | Capability needed to open FluentAuth; default manage_options |
fluent_security/app_vars | filter $vars | Data passed to the admin app |
onboarding_steps | filter $steps | Add or remove setup-wizard steps. 3.0 |
remote_auth_response_data | filter $data | What a remote auth server returns about a user |
Cron events
fluent_auth_daily_tasks (log retention, digests, scheduled scans) and
fluent_auth_hourly_tasks (hourly scans). Since 3.0, fluent_auth_recovery_resets runs the
batched password resets.