Connect it to U2 Secured
Two constants in wp-config.php, then one button.
Create the application
In the developer portal, create an application for this site and copy its server API key from the app’s API Keys tab. It starts with rka_. That key is what lets this site ask a phone to approve a login.
Add the constants
define( 'U2AUTH_API_KEY', 'rka_...' );
define( 'U2AUTH_ENCRYPTION_KEY', '<32 random bytes, base64>' );wp-config.php is the preferred home for both, because it is not copied into every database backup the way the site’s settings are. For the API key — and for the API base URL and the webhook signing secret — the settings screen accepts a value as a fallback for sites that cannot edit wp-config.php; the constant always wins, and the matching field is disabled once it is set. U2AUTH_ENCRYPTION_KEY is the exception, and the difference matters: it has no field on the settings screen and no database fallback at all. Define it in wp-config.php or the TOTP fallback is simply unavailable on that site — there is nowhere else to put it.
Why the encryption key matters
U2AUTH_ENCRYPTION_KEY encrypts the TOTP secret this site has to hold, so a stolen database dump does not hand over working codes alongside the accounts they protect. Without it the TOTP fallback is simply not offered — the plugin says so on the profile screen rather than storing the secret in the clear. Generate a value with:
php -r 'echo base64_encode(random_bytes(32)), PHP_EOL;'Then put the output into the constant above.
What “available” means here
Once the key and sodium are both present, the plugin’s Getting started panel reports the TOTP fallback as available. That is a statement about this site’s configuration, not a statement that the factor has been proven: the TOTP path has not been exercised end to end against a live site, so treat push approvals and recovery codes as the factors with a verification run behind them, and TOTP as the convenience it is.
Test the connection
Go to Settings → U2 Secured. The Getting started panel reports which steps this site has actually completed — whether the API key came from wp-config.php or the database, and whether the encryption key and sodium are both present. Then press Test connection: it makes a single read-only call and answers either that U2 Secured accepted the key, or a specific reason it did not — rejected key, unreachable host, wrong base URL, server error. It tests what is in effect rather than what is typed into the fields above it, so save your changes first. A wrong key should be something you find out by pressing a button, not by being locked out at your next login.
The webhook (optional)
U2 Secured can POST the outcome of an approval to this site the moment the user taps, so the challenge screen answers from local state instead of asking again. That is the entire benefit. Login works identically without it, because the browser polls for the outcome anyway.
- 1Copy the webhook URL from step 4 of the Getting started panel. Take it from there rather than typing it out: the plugin derives it with rest_url(), so it follows both the site address and the permalink structure, and a stale URL in the portal fails silently.
- 2Paste that URL into your application in the developer portal.
- 3Give this site the signing secret the portal shows for that webhook, so it can tell a real delivery from a forged one.
define( 'U2AUTH_WEBHOOK_SECRET', '...' );Skip this if the site is not publicly reachable
A local install, an intranet site, anything behind NAT or a VPN — deliveries would never arrive and nothing would tell you so, which is worse than not configuring it at all. Nothing breaks: the browser poll is the path, and always was. With no secret configured every delivery is rejected, because “not configured” is never read as “accept anything”.