{"id":135669,"date":"2021-01-27T06:40:35","date_gmt":"2021-01-27T06:40:35","guid":{"rendered":"https:\/\/de.wordpress.org\/plugins\/a-little-more-secure\/"},"modified":"2026-08-03T12:39:05","modified_gmt":"2026-08-03T12:39:05","slug":"a-little-more-secure","status":"publish","type":"plugin","link":"https:\/\/ast.wordpress.org\/plugins\/a-little-more-secure\/","author":14172526,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"1.1.0","stable_tag":"1.1.0","tested":"6.7.9","requires":"5.0","requires_php":"8.2","requires_plugins":null,"header_name":"A little more secure","header_author":"Palasthotel <edward.bock@palasthotel.de>","header_description":"Prevent brute force login attacks on wp-login.php","assets_banners_color":"","last_updated":"2026-08-03 12:39:05","external_support_url":"","external_repository_url":"","donate_link":"http:\/\/palasthotel.de\/","header_plugin_uri":"https:\/\/github.com\/palasthotel\/a-little-more-secure","header_author_uri":"https:\/\/palasthotel.de","rating":5,"author_block_rating":0,"active_installs":100,"downloads":3239,"num_ratings":1,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"1.0.0":{"tag":"1.0.0","author":"EdwardBock","date":"2021-01-27 06:48:23"},"1.0.1":{"tag":"1.0.1","author":"EdwardBock","date":"2021-03-03 12:51:30"},"1.0.2":{"tag":"1.0.2","author":"EdwardBock","date":"2021-03-16 07:22:11"},"1.0.3":{"tag":"1.0.3","author":"EdwardBock","date":"2023-07-11 05:01:49"},"1.0.4":{"tag":"1.0.4","author":"EdwardBock","date":"2024-04-29 04:43:30"},"1.1.0":{"tag":"1.1.0","author":"palasthotel","date":"2026-08-03 12:39:05"}},"upgrade_notice":[],"ratings":{"1":0,"2":0,"3":0,"4":0,"5":1},"assets_icons":{"icon-128x128.png":{"filename":"icon-128x128.png","revision":2463341,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.png":{"filename":"icon-256x256.png","revision":2463341,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256}},"assets_banners":[],"assets_blueprints":{"blueprint.json":{"filename":"blueprint.json","revision":3632907,"resolution":false,"location":"assets","locale":"","contents":"{\"landingPage\":\"\\\/wp-login.php\",\"preferredVersions\":{\"php\":\"8.2\",\"wp\":\"latest\"},\"phpExtensionBundles\":[\"kitchen-sink\"],\"features\":{\"networking\":true},\"steps\":[{\"step\":\"installPlugin\",\"pluginData\":{\"resource\":\"wordpress.org\\\/plugins\",\"slug\":\"a-little-more-secure\"},\"options\":{\"activate\":true}}]}"}},"all_blocks":[],"tagged_versions":["1.0.0","1.0.1","1.0.2","1.0.3","1.0.4","1.1.0"],"block_files":[],"assets_screenshots":{"screenshot-1.png":{"filename":"screenshot-1.png","revision":2463341,"resolution":"1","location":"assets","locale":"","width":1208,"height":764},"screenshot-2.png":{"filename":"screenshot-2.png","revision":2463341,"resolution":"2","location":"assets","locale":"","width":1208,"height":764}},"screenshots":{"1":"<p>Wait a few seconds for the login to be secured<\/p>","2":"<p>Your login is a little bit more secure<\/p>"}},"plugin_section":[],"plugin_tags":[600],"plugin_category":[54],"plugin_contributors":[85158,274076,138992],"plugin_business_model":[],"class_list":["post-135669","plugin","type-plugin","status-publish","hentry","plugin_tags-security","plugin_category-security-and-spam-protection","plugin_contributors-edwardbock","plugin_contributors-janaeggebrecht","plugin_contributors-palasthotel","plugin_committers-edwardbock","plugin_committers-palasthotel"],"banners":[],"icons":{"svg":false,"icon":"https:\/\/ps.w.org\/a-little-more-secure\/assets\/icon-128x128.png?rev=2463341","icon_2x":"https:\/\/ps.w.org\/a-little-more-secure\/assets\/icon-256x256.png?rev=2463341","generated":false},"screenshots":[{"src":"https:\/\/ps.w.org\/a-little-more-secure\/assets\/screenshot-1.png?rev=2463341","caption":"<p>Wait a few seconds for the login to be secured<\/p>"},{"src":"https:\/\/ps.w.org\/a-little-more-secure\/assets\/screenshot-2.png?rev=2463341","caption":"<p>Your login is a little bit more secure<\/p>"}],"raw_content":"<!--section=description-->\n<p>Stop bots from brute force hacking your wp-login.php<\/p>\n\n<p>A request to wp-login.php without an unlock parameter answers with a 404 and a\nholding page. JavaScript counts down a few seconds, then redirects to the same\nURL with the parameter appended. Only that request renders a usable login form,\nand only it carries the nonce that a login POST has to contain \u2014 a POST without\na valid nonce is rejected.<\/p>\n\n<p>This raises the cost of naive automation. It is not a lockout mechanism: whoever\nrequests the unlock URL first can read the nonce and post with it, which is why\nthe plugin is called <em>a little<\/em> more secure.<\/p>\n\n<p>There is nothing to configure. Activating the plugin is enough.<\/p>\n\n<h3>Customising<\/h3>\n\n<p>Three filters, for a theme or a small plugin of your own.<\/p>\n\n<pre><code>a_little_more_secure_is_unlocked decides whether a request counts as unlocked.\n<\/code><\/pre>\n\n<p>By default that is the presence of the parameter; override it to implement your\nown rule, for example a one-time token or an office IP allowlist:<\/p>\n\n<pre><code>add_filter( 'a_little_more_secure_is_unlocked', function ( $is_unlocked ) {\n    return $is_unlocked || my_own_check();\n} );\n\na_little_more_secure_get_param_name changes the name of the unlock parameter,\na-little-more-secure by default.\n\na_little_more_secure_redirect_wait_seconds changes the delay before the\n<\/code><\/pre>\n\n<p>redirect, 3 seconds by default.<\/p>\n\n<p>A theme with its own login form that posts to wp-login.php has to render the\nnonce itself, otherwise the POST is rejected \u2014 call\n    a_little_more_secure_nonce_field() inside the form. Forms built with\n    wp_login_form() get it automatically.<\/p>\n\n<h4>Rotating the unlock parameter<\/h4>\n\n<p>The default parameter name is public knowledge, so a bot written for this plugin\ncan hardcode it. If you want a name that changes over time, put the token in the\nname itself: the parameter name filter runs both when the redirect URL is built\nand when the request is checked, so both sides agree without storing anything.<\/p>\n\n<pre><code>function my_alms_token( int $bucketsAgo = 0 ): string {\n    $ttl    = 15 * MINUTE_IN_SECONDS;\n    $bucket = (int) floor( time() \/ $ttl ) - $bucketsAgo;\n\n    return 'alms_' . substr( hash_hmac( 'sha256', 'alms|' . $bucket, wp_salt( 'nonce' ) ), 0, 20 );\n}\n\nadd_filter( 'a_little_more_secure_get_param_name', function () {\n    return my_alms_token();\n} );\n\nadd_filter( 'a_little_more_secure_is_unlocked', function ( $is_unlocked ) {\n    return $is_unlocked || isset( $_GET[ my_alms_token( 1 ) ] );\n} );\n<\/code><\/pre>\n\n<p>Accepting the previous bucket as well keeps a request that crosses a bucket\nboundary from being rejected, so the effective validity is 15 to 30 minutes.\nKeep the token alphanumeric \u2014 PHP rewrites dots and spaces in parameter names.<\/p>\n\n<p>Be aware of what this does and does not do. It stops bots that hardcode the\nparameter name. It does not stop anything that fetches the page and reads the\nname out of it \u2014 the value has to be handed to the browser before anyone is\nlogged in, so a scraper can always obtain it too.<\/p>\n\n<p>Three things to expect: bookmarked unlock URLs stop working once the token\nexpires, though an expired token lands on the holding page and is redirected\nwith a fresh one, so it costs one extra request. Keep the lifetime well above\nthe redirect delay, otherwise the token can expire during the countdown. And be\ncareful with page caching \u2014 a short-lived token in cached HTML means logins that\nare rejected until the cache is refreshed.<\/p>\n\n<!--section=installation-->\n<ol>\n<li>Upload <code>a-little-more-secure.zip<\/code> to the <code>\/wp-content\/plugins\/<\/code> directory<\/li>\n<li>Extract the Plugin to a <code>a-little-more-secure<\/code> Folder<\/li>\n<li>Activate the plugin through the 'Plugins' menu in WordPress<\/li>\n<\/ol>\n\n<!--section=faq-->\n<dl>\n<dt id=\"do%20i%20have%20to%20configure%20anything%3F\"><h3>Do I have to configure anything?<\/h3><\/dt>\n<dd><p>No. Activate the plugin and it works. Everything that can be changed is changed\nwith the filters described above, in a theme or a small plugin of your own.<\/p><\/dd>\n<dt id=\"why%20does%20my%20login%20page%20return%20a%20404%3F\"><h3>Why does my login page return a 404?<\/h3><\/dt>\n<dd><p>That is intentional. A request to wp-login.php without the unlock parameter is\nanswered with a 404 so that automated scanners see a missing page. Your browser\nstill shows the holding page and is redirected to the real form a few seconds\nlater. Uptime monitors and scanners pointed at wp-login.php will report it as\nmissing \u2014 point them at a different URL, or exclude wp-login.php.<\/p><\/dd>\n<dt id=\"i%20cannot%20log%20in.%20it%20says%20%22sorry%2C%20this%20feels%20not%20very%20secure%22.\"><h3>I cannot log in. It says \"Sorry, this feels not very secure\".<\/h3><\/dt>\n<dd><p>That message means the login form was submitted without a valid nonce. The usual\ncauses:<\/p>\n\n<ol>\n<li>Something is caching wp-login.php. It must not be cached \u2014 the page carries a\nnonce that goes stale.<\/li>\n<li>Your theme renders its own login form that posts to wp-login.php without the\nnonce field. Call <code>a_little_more_secure_nonce_field()<\/code> inside the form.<\/li>\n<li>The login page sat open in a tab for more than a day. Nonces expire after at\nmost 24 hours. Reload the page and log in again.<\/li>\n<\/ol><\/dd>\n<dt id=\"does%20it%20work%20without%20javascript%3F\"><h3>Does it work without JavaScript?<\/h3><\/dt>\n<dd><p>No. The redirect to the unlocked form is done in JavaScript, and only the\nunlocked form carries the nonce a login needs. Without JavaScript you will see\nthe login page but the submission is rejected. If that is a problem for you, add\nyour own rule with the <code>a_little_more_secure_is_unlocked<\/code> filter.<\/p><\/dd>\n<dt id=\"does%20it%20protect%20xml-rpc%20and%20the%20rest%20api%3F\"><h3>Does it protect XML-RPC and the REST API?<\/h3><\/dt>\n<dd><p>No. The plugin only guards wp-login.php. Brute force attempts against\nxmlrpc.php, the REST API or application passwords are unaffected, and those are\ncommon targets. If you do not use XML-RPC, disable it separately.<\/p><\/dd>\n<dt id=\"can%20i%20keep%20using%20a%20bookmarked%20login%20url%3F\"><h3>Can I keep using a bookmarked login URL?<\/h3><\/dt>\n<dd><p>Yes. The unlock parameter is a fixed name by default, so a bookmark such as\nexample.com\/wp-login.php?a-little-more-secure keeps working and skips the wait.\nThat changes only if you set up a rotating parameter as described above.<\/p><\/dd>\n<dt id=\"does%20it%20work%20on%20multisite%3F\"><h3>Does it work on multisite?<\/h3><\/dt>\n<dd><p>Yes, network activated or per site. The plugin stores nothing and has no\nper-site setup, so both work the same way.<\/p><\/dd>\n<dt id=\"does%20the%20plugin%20store%20any%20data%20about%20my%20visitors%3F\"><h3>Does the plugin store any data about my visitors?<\/h3><\/dt>\n<dd><p>No. It writes no options, sets no cookies and creates no database tables. The\nonly thing it looks at is whether the unlock parameter is present in the request.<\/p><\/dd>\n<dt id=\"is%20my%20login%20secure%20now%3F\"><h3>Is my login secure now?<\/h3><\/dt>\n<dd><p>More secure than before, but this is a speed bump, not a lock. The unlock\nparameter and the nonce both have to be handed to a browser that is not logged\nin yet, so anything that fetches the page can read them too. It defeats bots\nthat post blindly at wp-login.php, which is most of them. It does not defeat a\ndetermined attacker, and it does nothing about weak passwords or repeated\nattempts from the same source \u2014 combine it with strong passwords and rate\nlimiting.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>1.1.0<\/h4>\n\n<p><strong>Features<\/strong>\n* let sites decide for themselves when the login is unlocked (5d16264)<\/p>\n\n<p><strong>Bug Fixes<\/strong>\n* block direct access to the DEV wrapper too (45550fe)\n* declare GPL-3.0 and the PHP version the plugin actually needs (141c4a2)\n* harden the login gate and make the translations extractable (33f28cf)\n* restore the public API function dropped in the rewrite (188482d)\n* set the 404 before the login page starts printing (73b7856)\n* stop the Playground blueprint from pinning an old version (1dd10cc)<\/p>\n\n<h4>1.0.4<\/h4>\n\n<ul>\n<li>Check compatiblility with 6.5.2<\/li>\n<\/ul>\n\n<h4>1.0.3<\/h4>\n\n<ul>\n<li>Optimization: load textdomain properly<\/li>\n<li>Optimization: 404 if no valid login parameter<\/li>\n<\/ul>\n\n<h4>1.0.2<\/h4>\n\n<ul>\n<li>Bugfix: Not redirected properly if hash in url<\/li>\n<\/ul>\n\n<h4>1.0.1<\/h4>\n\n<ul>\n<li>WP5.7 compatible<\/li>\n<\/ul>\n\n<h4>1.0.0<\/h4>\n\n<ul>\n<li>First release<\/li>\n<\/ul>","raw_excerpt":"Stop bots from brute force hacking your wp-login.php","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/ast.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/135669","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ast.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/ast.wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/ast.wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=135669"}],"author":[{"embeddable":true,"href":"https:\/\/ast.wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/edwardbock"}],"wp:attachment":[{"href":"https:\/\/ast.wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=135669"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/ast.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=135669"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/ast.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=135669"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/ast.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=135669"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/ast.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=135669"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/ast.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=135669"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}