AJAX and REST endpoints
Every endpoint reachable without signing in, what token each needs, and which of them can be used to find out whether an account exists.
- Advanced
- 9 min read
- Applies to 2.0
The shape of it
Everything the front end does goes through admin-ajax.php. REST is used for
two things only: the Form Builder's admin screen, and the return leg of an OAuth
handshake.
The six REST routes
| Route | Method | Who may call it |
|---|---|---|
attrua_pro/v2/form-fields/{target} | GET, POST | manage_options |
attrua_pro/v2/form-fields/{target}/reorder | POST | manage_options |
attrua_pro/v2/form-fields/{target}/{field_id} | PATCH, DELETE | manage_options |
attrua_pro/v2/form-field-types | GET | manage_options |
attributes-pro/v1/smtp-oauth/{provider} | GET | anyone |
attributes-pro/v1/social/{provider} | GET | anyone |
{target}, {field_id} and {provider} are written above the way you would
type them. WordPress holds them as patterns, which is the form wp rest route list prints and the form to match against if you are filtering routes:
attrua_pro/v2/form-fields/(?P<target>[a-z0-9_-]+)
attrua_pro/v2/form-fields/(?P<target>[a-z0-9_-]+)/reorder
attrua_pro/v2/form-fields/(?P<target>[a-z0-9_-]+)/(?P<field_id>[a-z0-9_]+)
attrua_pro/v2/form-field-types
attributes-pro/v1/smtp-oauth/(?P<provider>[a-z0-9_-]+)
attributes-pro/v1/social/(?P<provider>[a-z0-9_-]+)
There is no REST API for member data. The four attrua_pro/v2 routes all
run the same manage_options check and exist to let the Form Builder screen
talk to its own storage. If you are looking for a way to read accounts, profiles
or submissions over REST, this plugin does not offer one.
The two attributes-pro/v1 routes are genuinely public — their
permission_callback returns true for everyone, and that is not an oversight.
They are where Google, Microsoft, Facebook and GitHub send the browser back
after a sign-in or a mailbox connection, and a provider's redirect carries no
WordPress cookie or nonce. Asking for a capability there would refuse every real
return.
What proves such a request instead is the state token checked inside the handler: single-use, minted when the administrator pressed Connect, and carrying the user id so the callback can insist the person finishing the handshake is the one who started it.
The practical consequence is for your firewall, not your code: if you filter
/wp-json/, these two paths must stay reachable from the outside or social
sign-in and SMTP OAuth both break at the last step, with no error on your side.
Endpoints reachable without signing in
Sixteen in Pro, two in the free plugin. Every one verifies a nonce, named in the table below.
| Action | Nonce | For |
|---|---|---|
attrua_auth_request | attrua-auth-request | dispatching an alternative sign-in method |
attrua_pro_check_username | attrua_pro_check_username | live "is this username taken?" |
attrua_pro_check_email | attrua_pro_check_email | live "is this email registered?" |
attrua_pw2fa_verify | attrua_pw2fa_verify | submitting a second-factor code |
attrua_pw2fa_resend | attrua_pw2fa_resend | resending that code |
attrua_verify_2fa | attrua_2fa_nonce | submitting a 2FA code on the other flow |
attrua_qr_check_user | attrua_qr_check_user | step 1 of QR sign-in |
attrua_qr_login_generate | attrua_qr_generate | producing the QR challenge |
attrua_qr_login_poll | attrua_qr_poll | polling for approval |
attrua_request_magic_link | attrua-magic-link-nonce | "email me a sign-in link" |
attrua_social_redirect | attrua_social_redirect | getting the social provider's URL |
attrua_step_login_check_user | attrua_step_login_check_user | step 1 of two-step sign-in |
attrua_totp_pl_check_user | attrua_totp_pl_check_user | step 1 of passwordless authenticator sign-in |
attrua_step_navigate | attrua_step_navigate | next/back on multi-step registration |
attrua_check_username (free) | attrua_register | live username check |
attrua_check_email (free) | attrua_register | live email check |
One deliberate exception, attrua_refresh_login_nonce, verifies nothing —
because its whole job is to hand out a fresh nonce to a page served from cache.
Requiring a token to get a token would be circular, and it returns only a
logged-out visitor's nonce, which the sign-in form already contains in plain
sight.
Coming from version 1.x? Three public endpoints have been removed, and you should check that nothing of yours called them.
attrua_ajax_loginandattrua_ajax_registerverified no nonce, no capability and no rate limit, and their only handlers wrote the submitted values straight into the audit log. No flow ever used them — anyone could fill the audit table with invented attempts under invented usernames, corrupting the record an administrator reads after an incident.ip_manager_bridge_emergency_initwas a debugging escape hatch, registered for logged-out visitors, that force-initialised the IP manager and returned a status string.
One name you may find in the source but never on the wire.
attrua_pro_validate_field is written as a public AJAX action in
src/Pro/Forms/Validation/AjaxValidator.php, and a scan of the source will
report it. It never registers: the class that would call add_action() is not
instantiated, having been dropped from the forms service provider along with two
others whose clients did not exist. There is no such endpoint on a running site,
and nothing of yours should call it.
What a nonce does and does not prove
A nonce on a public endpoint proves the request came from a page this site served. It does not prove who is asking, because the page it came from is public and anyone may fetch it.
So treat a nonce here as a defence against cross-site request forgery — which is what it is — and not as authorisation. Where a public endpoint reveals something, the nonce does not stop a determined caller from asking.
Account enumeration
Several endpoints necessarily answer "does this account exist?" — a live availability check on a registration form cannot work otherwise, and a two-step sign-in has to know whether to show the second step.
| Endpoint | What it reveals |
|---|---|
attrua_pro_check_username, attrua_check_username | whether a username is taken |
attrua_pro_check_email, attrua_check_email | whether an email is registered |
attrua_step_login_check_user | on success, the account's ID and display name |
attrua_qr_check_user, attrua_totp_pl_check_user | on success, display name and avatar |
None of these is rate limited. The sign-in throttle counts failed authentication attempts; it does not apply to a lookup that never tries to authenticate.
This is the usual trade-off, and most membership plugins make it the same way —
but make it knowingly. If your member list is itself sensitive, put a rate limit
in front of admin-ajax.php at the web-server or WAF level, because the plugin
does not do it for you.
The one exception is attrua_request_magic_link, which does limit requests
per email address per hour — though the limit is applied after the account
lookup, so probes for addresses that do not exist are not counted.
Second-factor codes
attrua_pw2fa_verify allows five incorrect codes before the pending sign-in
is discarded and the member must start again. The count travels with the pending
session, so clearing a cookie does not reset it.
Coming from version 1.x? There was no limit. A six-digit code with unlimited attempts inside its ten-minute life is a million guesses against an attacker who already has the password, which is to say the second factor bought nothing.
Admin endpoints
The rest of the surface — 62 further AJAX actions in Pro, six in the free plugin
— requires a signed-in user, and each checks both a nonce and a capability,
usually manage_options. Those are implementation details of the admin screens
rather than an interface to build against; the hooks
are the supported extension point.
Something missing or out of date? Tell support.