Title: DollarMates U2 Secured Authenticator
Author: dollarmates
Published: <strong>2026 年 10 月 8 日</strong>
Last modified: 2026 年 10 月 8 日

---

搜索插件

![](https://ps.w.org/dollarmates-u2-secured-authenticator/assets/banner-772x250.
png?rev=3735355)

![](https://ps.w.org/dollarmates-u2-secured-authenticator/assets/icon-256x256.png?
rev=3735355)

# DollarMates U2 Secured Authenticator

 作者：[dollarmates](https://profiles.wordpress.org/dollarmates/)

[下载](https://downloads.wordpress.org/plugin/dollarmates-u2-secured-authenticator.0.3.1.zip)

 * [详情](https://cn.wordpress.org/plugins/dollarmates-u2-secured-authenticator/#description)
 * [评价](https://cn.wordpress.org/plugins/dollarmates-u2-secured-authenticator/#reviews)
 *  [安装](https://cn.wordpress.org/plugins/dollarmates-u2-secured-authenticator/#installation)
 * [开发进展](https://cn.wordpress.org/plugins/dollarmates-u2-secured-authenticator/#developers)

 [支持](https://wordpress.org/support/plugin/dollarmates-u2-secured-authenticator/)

## 描述

U2 Secured Authenticator adds push-based two-factor authentication to WordPress 
login. After a user’s
 password is verified, WordPress holds the session open (it
does not complete it) and challenges for a second factor:

 * **Tap-to-approve push** — a number-match approval sent to the user’s phone via
   the U2 Secured app.
 * **TOTP fallback** — a standard six-digit authenticator-app code, for when a push
   can’t reach the
    phone. Verified **on your own server**: the RFC 6238 check runs
   in PHP against the secret this site already holds (encrypted at rest), with the
   same ±1 window tolerance for clock drift, and no request leaves the site to do
   it. Requires the `sodium` PHP extension and an encryption key (see Installation);
   the plugin says so plainly on the profile screen when it isn’t available, rather
   than silently dropping the option. Wrong codes are rate-limited per user (five
   inside fifteen minutes) so a six-digit code cannot simply be ground through.
 * **Recovery codes** — ten single-use codes issued when a user links their device,
   verified entirely
    locally against WordPress’s own database.
 * Both fallbacks are what make the failure mode of an unreachable U2 API survivable:
   the push path is
    dead, but a linked user can still sign in with either, because
   neither makes an outbound call. They share one field on the fallback screen, 
   which accepts whichever the user has to hand.
 * **Role-based enforcement with a grace period** — require two-factor for chosen
   roles (e.g.
    Administrator) without locking out every matching user the instant
   it’s switched on. Each user gets a configurable number of days to link before
   they’re actually blocked.
 * **Step-up re-auth** — a small set of sensitive admin capabilities (e.g. installing
   plugins) can
    require a fresh tap before they’re usable, even mid-session.
 * **Optional signed webhook** — U2 Secured can POST the outcome of an approval 
   straight to this site
    the moment the user taps, so the login screen can answer
   from local state instead of asking again. It is an optimisation and nothing more:
   every delivery must carry a valid signature to be believed, and login works identically
   on a site that never receives one, because the browser asks U2 Secured for the
   outcome itself anyway. Sites that aren’t reachable from the public internet should
   simply skip it.
 * **XML-RPC / Application Passwords awareness** — XML-RPC authenticates with the
   raw password and never
    reaches the `wp_login` hook this plugin relies on, so
   it’s explicitly closed off. Application passwords are a legitimate WordPress 
   feature, so instead of being silently broken they’re surfaced to the administrator
   as an admin notice.

Every enrolment is opt-in until an administrator turns on role-based enforcement.
Every error path —
 an unreachable U2 API, a missing encryption key, an unrecognised
device, **a removed or rotated-out API key — is designed to fail closed: the safe
outcome is “ask again” or “refuse,” never “let the request through.” A user who 
requires a second factor on a site that can no longer perform one is refused and
told to ask an administrator; they are not quietly let in. See Verification status
in README.md for exactly what is and isn’t proven by the automated tests, and please
read it before enabling enforcement on a production site.

#### The escape hatch

If a user loses their phone _and_ their recovery codes, the only way back in is 
an administrator
 running, over SSH:

    ```
    wp u2auth disable <user>

    <user> accepts a WordPress user ID, login, or email address. This clears the plugin's local
    ```

two-factor state for that user (it does not touch anything on the U2 side, so it
works even when the
 U2 API is unreachable) and immediately admits them again — 
they can re-link from their profile once back in. `wp u2auth status <user>` reports
whether a user is currently linked and how many recovery codes they have left, without
changing anything.

### External services

This plugin cannot work on its own: approving a login means asking a phone, and 
that request
 travels through the U2 Secured Authenticator service at https://auth.
u2secured.com. Installing the plugin does not by itself send anything — nothing 
leaves your site until an administrator enters an API key and a user links a phone.

**When the site contacts the service**

 * When a user links their phone: to mint a one-time pairing code, then once every
   few seconds
    until the code is redeemed or expires, and once more to read the
   resulting link.
 * When a linked user opens their own profile screen, to read whether their phone
   is still
    linked and able to receive approvals.
 * When a linked user signs in, to ask their phone to approve it, and then once 
   every two
    seconds until they answer or the request expires.
 * When a linked administrator performs a sensitive action (installing a plugin,
   editing a
    user), for the same approval round trip.
 * When a user unlinks their phone, to remove the link on the U2 side as well.
 * When an administrator presses “Test connection” on the settings screen.

**What is sent**

 * An identifier for the user, in the form `<random site id>|<numeric WordPress 
   user ID>`. The
    site id is a random string generated once and stored in your 
   database. **Your users’ email addresses, usernames and passwords are never sent,
   and neither is your site’s URL.
 * Your site’s name (from Settings  General), so the person looking at their phone
   can see
    which site is asking.
 * For a sensitive admin action, the name of that action — for example “Install 
   a plugin”.
 * While a phone is being linked, the one-time pairing code the service itself just
   issued; while
    an approval is outstanding, the id of that approval. Neither is
   derived from anything about the user.
 * Your site’s API key, to authenticate the request.

**What is never sent**

 * **Recovery codes and TOTP codes, and the TOTP secret itself.** Both fallback 
   factors are
    checked inside PHP on your own server — recovery codes against hashes
   in your database, TOTP against the secret your site holds (RFC 6238, ±1 window)—
   so no request is made to check either, and the shared secret never leaves the
   site after enrolment. Earlier releases did send the TOTP secret and the submitted
   code to the service to be checked; 0.3.0 stopped.
 * Passwords, usernames, email addresses, and your site’s URL.

Service terms: https://auth.u2secured.com/terms
 Privacy policy: https://auth.u2secured.
com/privacy

## 安装

 1.  Upload the plugin to `/wp-content/plugins/dollarmates-u2-secured-authenticator`(
     or install the zip through
      Plugins  Add New), then activate it.
 2.  Create an app in the U2 Secured developer portal and copy its **server API key**
     (`
     rka_...`). This key is what lets the plugin request push approvals, mint pairing
     codes and read enrolments. It is not used to check TOTP or recovery codes — both
     are verified on your own server.
 3.  **Keep it in place once users have enrolled.** Removing or rotating it out does
     not switch
      two-factor off; it makes the push factor impossible, and a user who
     requires a second factor is then refused at login rather than let through. Use`
     wp u2auth disable <user>` (below) if you need to release someone.
 4.  Add the API key to `wp-config.php`. A `wp-config.php` constant is preferred over
     the plugin’s own
      settings screen because it isn’t copied into every database 
     backup:
 5.  define( ‘U2AUTH_API_KEY’, ‘rka_…’ );
 6.  (The settings screen at Settings  U2 Secured still accepts it as a fallback for
     sites that can’t
      edit `wp-config.php` — but the constant always wins, and the
     field is disabled on the settings screen once it’s set.)
 7.  Add a second constant, `U2AUTH_ENCRYPTION_KEY`. **This one gates a security control**:
     it is the key
      used to encrypt the site-held TOTP secret at rest, so that a database
     dump alone doesn’t hand over working two-factor secrets alongside the accounts
     they protect. Without it, the TOTP fallback is unavailable (the plugin says so
     on the profile screen and in an admin notice; push and recovery codes still work)
     rather than storing the secret unencrypted.
 8.  define( ‘U2AUTH_ENCRYPTION_KEY’, ‘<32 random bytes, base64>’ );
 9.  Generate a value with:
 10. php -r ‘echo base64_encode(random_bytes(32)), PHP_EOL;’
 11. Requires the `sodium` PHP extension, which ships enabled by default on PHP 7.2
     +.
 12. Visit **Settings  U2 Secured**. The **Getting started** panel there repeats these
     steps and
      reports which of them this site has actually completed, and **Test 
     connection** checks the API key and base URL against U2 Secured with a single 
     read-only request — so a wrong key is something you find out by pressing a button,
     not by being locked out at your next login. The same screen is where you optionally:
 13.  * pick which roles are required to use two-factor (leave every role unticked 
        to keep linking opt-in),
      * set the grace period, in days, a newly-required user gets before they’re actually
        blocked.
 14. **Optional:** set up the webhook. In the developer portal, point your application’s
     webhook at the
      URL shown in step 4 of the **Getting started** panel. Take it 
     from there rather than constructing it: the URL depends on your site address _and_
     your permalink settings, the panel shows the single form that is correct for your
     site as it is configured, and a stale URL in the portal fails silently. On most
     sites it reads:
 15. https://example.com/wp-json/u2auth/v1/webhook
 16. and on a site using plain permalinks, `https://example.com/?rest_route=/u2auth/
     v1/webhook`. If you
      change your permalink settings later, come back and copy 
     the new URL across.
 17. Then give this site the signing secret the portal shows for that webhook, so it
     can tell a real
      delivery from a forged one:
 18. define( ‘U2AUTH_WEBHOOK_SECRET’, ‘…’ );
 19. (Again, the settings screen accepts it as a fallback. An unsigned, wrongly signed,
     or stale
      delivery is rejected, and with no secret configured at all every delivery
     is rejected — “not configured” is never read as “accept anything”.)
 20. **Skip this step entirely if the site isn’t reachable from the public internet**—
     a local install,
      an intranet site, anything behind NAT or a VPN. Nothing breaks:
     the login screen asks U2 Secured for the approval outcome itself, and always did.
     All the webhook saves is an outbound call or two.
 21. Each user links their own device from their own profile screen (**Users  Profile**,
     or **Your
      Profile
      in the admin toolbar) — an administrator cannot link a device
     on another user’s behalf,
      since linking inherently needs that user’s own phone.
     Linking also issues ten single-use recovery codes, shown once and never shown 
     again, so tell people to save them somewhere they can reach without their phone.

## 常见问题

### What happens if a user loses their phone?

They fall back to a recovery code (issued, ten at a time, when they first linked,
and shown again any
 time they choose to regenerate them from their profile) or 
a TOTP code from a backup authenticator app, if one was set up. If they’ve lost 
the phone _and_ exhausted their recovery codes, an administrator runs `wp u2auth
disable <user>` over SSH — see “The escape hatch” above. This is a local, offline
operation: it works even if the U2 API itself is unreachable.

### Does turning on role-based enforcement lock everyone out immediately?

No. A user in a newly-enforced role gets the configured grace period (default seven
days) to link
 before they’re blocked — they’re nagged on their profile screen, 
not refused at login, until that window closes.

### What happens if the U2 API is unreachable?

The plugin fails closed, and there is no setting that changes that — opting out 
is not offered.

In practice that is less dramatic than it sounds. An already-linked user cannot 
use the push challenge
 while U2 is unreachable, but can still sign in with a recovery
code or a TOTP code: both are verified against this site’s own data with no outbound
call at all. The login is still gated, just via a different factor. If the phone
is gone _and_ the codes are exhausted, `wp u2auth disable <user>` over SSH is the
escape hatch, and it makes no network call either.

What the plugin will not do is let a session through with no second factor because
a network call
 failed. See “Fail-closed means a misconfiguration blocks logins”
under Verification status in README.md for what that means for your own rollout.

### What happens if the API key is removed, or rotated out?

Users who require a second factor are refused, with a message telling them to ask
an administrator to
 check the key. They are **not** let in.

This is worth stating plainly because the alternative is the more tempting behaviour
and it is a
 two-factor bypass: `wp_login` fires after WordPress has already set
the auth cookie, so a plugin that merely declines to challenge when it is unconfigured
has thereby completed the login. Up to and including 0.2.2 this plugin did exactly
that, and deleting the API key silently switched two-factor off for every user who
had it. Fixed in 0.3.0.

A site with the plugin installed and nobody enrolled is unaffected: it behaves exactly
as WordPress
 does, which is why the check asks “does this user need a second factor”
before it asks “can this site send one”.

### What stops someone guessing a TOTP code?

Five wrong codes for one user inside fifteen minutes, and the TOTP factor refuses
that user until the
 window passes. Recovery codes are checked before the limit 
applies, so a user who fumbled their authenticator app still has their codes — the
limit is a brake, never a lockout, and it clears itself.

Each guess also costs a full password sign-in, because the challenge is single-use:
a failed code
 invalidates the attempt and the attacker has to authenticate the 
password again to get another.

### Do I need the webhook?

No. It’s an optimisation, never a requirement. When a user taps Approve, U2 Secured
can POST that
 outcome to this site immediately; the login screen then answers from
local state instead of asking U2 Secured again. Without it the login screen simply
asks — which is what it does anyway while it waits. Every login flow, fallback and
failure mode is identical either way.

Set it up only if this site is reachable from the public internet. If it isn’t, 
deliveries would never
 arrive and nothing would tell you so, which is worse than
not configuring it. The URL to paste into the developer portal is printed on **Settings
U2 Secured**, under Getting started — take it from there rather than typing it out,
since it follows your site address and your permalink structure, and that screen
already resolves which of the two forms your site actually uses.

### What happens if someone forges a webhook delivery?

It’s rejected. Every delivery carries an HMAC signature over the exact bytes sent,
computed with the
 shared secret from `U2AUTH_WEBHOOK_SECRET` (or the settings screen),
and is verified before a single field of the payload is read. A wrong signature,
a signature computed over different bytes, a stale timestamp, or a malformed header
are all refused and change nothing. If no secret is configured at all, every delivery
is refused — “not configured” is never treated as “accept anything”. A rejected 
webhook can’t strand a login either: the browser’s own poll resolves the approval
regardless.

### Does this affect XML-RPC or Application Passwords logins?

XML-RPC authenticates with the raw password and never reaches the hook this plugin
challenges on, so
 the plugin closes it off outright rather than leaving a second,
unguarded front door. Application Passwords are a legitimate WordPress feature with
their own use cases, so instead of being blocked they’re surfaced as an admin notice—
decide for your site whether to disable them separately.

### Can an administrator link or unlink a device for another user?

No. Linking needs that user’s own phone, so there is no “link on behalf of” flow.
The only admin-side
 action is the `wp u2auth disable` / `wp u2auth status` escape
hatch described above.

## 评价

此插件暂无评价。

## 贡献者及开发者

「DollarMates U2 Secured Authenticator」是开源软件。 以下人员对此插件做出了贡献。

贡献者

 *   [ dollarmates ](https://profiles.wordpress.org/dollarmates/)

[帮助将「DollarMates U2 Secured Authenticator」翻译成简体中文。](https://translate.wordpress.org/projects/wp-plugins/dollarmates-u2-secured-authenticator)

### 对开发感兴趣吗?

您可以[浏览代码](https://plugins.trac.wordpress.org/browser/dollarmates-u2-secured-authenticator/)，
查看[SVN仓库](https://plugins.svn.wordpress.org/dollarmates-u2-secured-authenticator/)，
或通过[RSS](https://plugins.trac.wordpress.org/log/dollarmates-u2-secured-authenticator/?limit=100&mode=stop_on_copy&format=rss)
订阅[开发日志](https://plugins.trac.wordpress.org/log/dollarmates-u2-secured-authenticator/)。

## 更新日志

#### 0.3.1

 * **The TOTP attempt limit could be stretched by sending guesses in parallel.**
   The five-per-15-minutes
    counter was advanced by reading it, adding one, and 
   writing the result back. Two submissions that overlapped both read the same number
   and both wrote the same number, so one of the two guesses cost nothing — making
   the limit a function of how many requests were sent at once rather than how many
   guesses were made. The counter is now incremented atomically: through the object
   cache’s own increment where a persistent cache is available, and otherwise by
   a single self-referential SQL UPDATE that the database serialises. Reported by
   the plugin review team.

#### 0.3.0

 * **Security: a second factor could be skipped entirely on a site whose API key
   had been removed or
    rotated out.
    The login interception treated “this user 
   requires a second factor” and “this site can
    perform one” as the same condition,
   and answered both by returning — but `wp_login` fires after WordPress has already
   set the auth cookie, so returning there completed the login. Any already-linked
   user therefore signed in with a password alone once the key was gone, which is
   the opposite of what this readme promised. Entitlement is now decided first and
   on its own: a user who needs a second factor and cannot be given one is refused,
   with a message naming the fix. A site with nobody enrolled still signs users 
   in exactly as WordPress does.
 * **TOTP codes are now verified on your own server, and the TOTP secret is no longer
   transmitted.**
    Verification used to POST the shared secret _and_ the submitted
   code to the U2 Secured API on every fallback login — while this readme said, 
   in as many words, that TOTP codes are verified on your own server and never transmitted.
   The RFC 6238 check (same ±1 drift window, same answer) now runs in PHP against
   the secret this site already holds, so nothing goes on the wire, and a U2 outage
   no longer takes the TOTP factor down with push.
 * Wrong TOTP codes are rate-limited locally: five per user inside fifteen minutes.
   The remote endpoint
    applied a brute-force lockout shared across its callers,
   and moving the check in-house would otherwise have dropped that protection. Recovery
   codes are checked _before_ the limit, so it can never become a lockout, and a
   throttled user is told they are throttled rather than told their code was wrong.
 * The fallback screen now says that an authenticator-app code is accepted there
   too. It always was —
    the one field takes a recovery code or a TOTP code — but
   every string around it said only “Recovery code”, so anyone with a backup authenticator
   app had no way to know.
 * `README.md` is now included in the plugin package. `readme.txt` refers readers
   to it by name for the
    verification-status detail and the fail-closed rollout
   warning, and it was being stripped out of the distributed zip, so both references
   resolved to nothing.
 * The “External services” disclosure now lists every call the plugin makes, including
   the enrolment
    read on the profile screen, the pairing-code poll and the unlink,
   and states explicitly what is never sent.

#### 0.2.2

 * Fix: a refused second factor now answers HTTP 403 instead of 500. Every rejection
   in the login challenge —
    a mistyped or already-used recovery code, a lapsed 
   sign-in attempt, and every login attempt by a user in an enforced role who has
   not enrolled yet — called `wp_die()` with no arguments, and `wp_die()` defaults
   to
    1. The page shown was always correct; only the status line said “Internal Server
       Error” for what is an
        ordinary, expected refusal. It matters because managed
       hosts, firewalls and CDNs alarm and rate-limit on 5xx rates without reading 
       the page, so a user fumbling their recovery codes could trip a false outage 
       alert or be throttled before reaching the login screen again. The one path that
       genuinely cannot complete — a login fired from somewhere that cannot render 
       the challenge screen — still answers 500, now explicitly.

#### 0.2.1

 * Fix: the API key and webhook signing secret are stored exactly as entered. They
   were previously passed
    through `sanitize_text_field()`, which strips percent-
   encoded octets and tag-like sequences and collapses whitespace — so a credential
   containing any of those was silently stored as a different string and every later
   API call or webhook signature check failed with nothing on screen to explain 
   it. Neither value is ever rendered back into the settings form, so nothing was
   gained by sanitising them.

#### 0.2.0

 * Security: the API base URL is compiled into the plugin and is no longer an admin
   setting. The API key
    is sent to whatever host that URL names, so an editable
   field let anyone who reached wp-admin redirect the key to a server they control.`
   U2AUTH_BASE_URL` still overrides it on local and staging installs, and is ignored
   on production.
 * Signing in with a recovery code is now its own screen, reached from a “Use recovery
   code” link, rather
    than a code field sitting under “approve on your phone”.
 * Step-up approval offers a recovery code. Previously an outage, a revoked enrolment
   or a rotated API key
    sealed every gated capability behind a request that could
   never succeed, with no route back short of shell access to the server.
 * Failures say what actually went wrong. A rejected API key and an unenrolled account
   were both reported
    as “we could not reach U2 Secured”, which sent people looking
   for an outage that was not happening.
 * Fixed: updated CSS and JavaScript never reached browsers, because every asset
   URL carried a version
    string that never changed.
 * The step-up screen’s text is now translatable; it was previously hardcoded English.
 * Licence changed to GPLv2 or later.

#### 0.1.0

 * Initial release: tap-to-approve push challenge on login, TOTP and recovery-code
   fallback, role-based
    enforcement with a grace period, step-up re-authentication
   for sensitive admin actions, XML-RPC / Application Passwords handling, the optional
   signed `approval.resolved` webhook receiver, a guided setup panel with a connection
   test on the settings screen, and the `wp u2auth disable` / wp u2auth status WP-
   CLI escape hatch.

## 额外信息

 *  版本 **0.3.1**
 *  最后更新：**2 天前**
 *  活跃安装数量 **不到10**
 *  WordPress 版本 ** 6.0 或更高版本 **
 *  已测试的最高版本为 **7.1.3**
 *  PHP 版本 ** 7.4 或更高版本 **
 *  语言
 * [English (US)](https://wordpress.org/plugins/dollarmates-u2-secured-authenticator/)
 * 标签
 * [2FA](https://cn.wordpress.org/plugins/tags/2fa/)[login](https://cn.wordpress.org/plugins/tags/login/)
   [security](https://cn.wordpress.org/plugins/tags/security/)[two factor](https://cn.wordpress.org/plugins/tags/two-factor/)
 *  [高级视图](https://cn.wordpress.org/plugins/dollarmates-u2-secured-authenticator/advanced/)

## 评级

尚未提交反馈。

[您的评价](https://wordpress.org/support/plugin/dollarmates-u2-secured-authenticator/reviews/#new-post)

[查看全部评论](https://wordpress.org/support/plugin/dollarmates-u2-secured-authenticator/reviews/)

## 贡献者

 *   [ dollarmates ](https://profiles.wordpress.org/dollarmates/)

## 支持

有话要说吗？是否需要帮助？

 [查看支持论坛](https://wordpress.org/support/plugin/dollarmates-u2-secured-authenticator/)