Foundations

Why Front-End AI Is Different From Admin-Facing Agents

⏱ 13 min

Connect AI Assistants & Agents to WordPress built an assistant for one person: an authenticated admin, connected through Claude Desktop or Cursor, using an Application Password tied to a real WordPress user account. Every safeguard in that course, permission checks, audit logs, rate limits, assumes that one identifiable person on the other end of the connection. A public chat widget has none of that. The person typing into it arrived from a search engine, has no WordPress account, and never logs in. This lesson is about what changes when the person on the other end isn’t a user at all, just a visitor.

What you'll learn in this lesson
Why Application Passwords don't apply here
They authenticate as a real WordPress user, and an anonymous visitor is not one.
The core architecture principle for public AI
The browser never talks to your AI provider directly, and never sees the API key.
What actually sits between the visitor and the model
A custom REST endpoint whose PHP callback is the only thing with real credentials.
The new abuse surface this creates
Anyone, or anything, can hit a public endpoint, so the defenses have to be different too.
Prerequisites

WordPress AI Foundations, specifically a working wp_ai_client_prompt() setup with a real provider configured. Having read Course 3 helps for contrast but isn’t required, this lesson recaps the relevant parts.

Step 1: the authenticated-admin model, briefly recapped

In Course 3, the trust chain looked like this: a specific person runs Claude Desktop on their own machine, that client authenticates to your site with an Application Password generated for their WordPress account, and every ability call that follows is checked against what that account can do. There’s always exactly one identifiable user on the other end, and WordPress’s existing permission system, current_user_can(), does most of the security work for free.

None of that exists for a visitor reading a blog post and clicking a chat bubble in the corner of the page.

Step 2: what’s actually different for a public visitor

Admin-facing agent (Course 3) vs. public-facing chat widget (this course)
DimensionAdmin-facing agentPublic chat widget
Who’s talkingOne known person, a site admin or editorAny anonymous visitor, unlimited in number
CredentialApplication Password tied to a real WP userNone, visitors don’t have WordPress accounts
ClientClaude Desktop, Cursor, ChatGPT, on the person’s own machineJavaScript running in the visitor’s browser, on your page
Where the AI call happensClient-side, the MCP client itself calls the modelServer-side only, inside a WordPress REST callback
Permission modelcurrent_user_can() against a real accountNo user to check, needs a different kind of gate entirely

That third row is the one that changes everything downstream. An MCP client like Claude Desktop holds its own copy of whatever credential it authenticates with, and that’s fine because it’s running on a trusted person’s own machine. A chat widget’s JavaScript runs in a stranger’s browser. Anything that JavaScript can read, anyone visiting your site can read, using nothing more than their browser’s developer tools.

Step 3: the rule that makes this safe, the API key never reaches the browser

This is the one principle everything else in this course is built around: your AI provider’s API key must never be sent to, or be readable by, the browser. Not in the page source, not in a JavaScript variable, not in a network request the visitor’s browser makes directly to the provider.

The way this works in practice is a proxy pattern you’ll build over the next two lessons:

  1. The widget’s JavaScript sends the visitor’s message to a custom WordPress REST endpoint on your own site, registered with register_rest_route().
  2. That endpoint’s PHP callback, running on your server, is the only piece of code that calls wp_ai_client_prompt(). It holds the provider credentials because PHP AI Client SDK configuration lives in server-side options, never in anything shipped to the browser.
  3. The callback returns the model’s reply as a plain JSON response. The visitor’s browser only ever talks to your own domain.
If your JavaScript imports an AI provider's SDK, stop

A common mistake porting patterns from other stacks is calling an AI provider’s API directly from client-side JavaScript, with an API key embedded in the script or fetched from a “public” config endpoint. Any key readable by the browser is readable by every visitor, and will be scraped and abused within hours of a widget going live publicly. The provider call belongs entirely on the server, behind your own REST endpoint.

Step 4: the abuse surface is different too, and bigger

An internal admin tool is reached by a handful of trusted, known accounts. A public REST endpoint is reachable by literally anyone with your site’s URL, including bots, scrapers, and anyone deliberately trying to run up your AI provider bill or extract a system prompt. There’s no current_user_can() gate available, because there’s no user.

That doesn’t mean the endpoint is unprotected, it means the protections have to be different: a nonce for basic request legitimacy rather than full authentication, and transient-based rate limiting keyed by IP address or a client-generated session ID instead of a user ID. Lessons 3, 4, and 6 build all of this. It also means answer quality matters differently: an admin assistant’s user can tell when it’s wrong, a public visitor often can’t, which is exactly why grounding the widget’s answers in real site content, covered in Grounding AI Answers in a WordPress Knowledge Base, matters more here than it did for Course 3’s internal tools.

Test it: sketch your architecture before writing code

Before Lesson 2, write down the answer to three questions for your own site:

Architecture check
Where will the AI provider API key be stored?
It should be a server-side option or constant, never anything sent to the browser.
What is the only piece of code allowed to call wp_ai_client_prompt()?
It should be exactly one REST callback, not the widget's JavaScript.
What happens if a bot sends 10,000 requests to your chat endpoint in a minute?
If you don't have an answer yet, that's exactly what Lessons 3 and 6 fix.

If you can answer all three without mentioning JavaScript touching the provider directly, you’re ready for the next lesson.

Recap

A public chat widget faces an anonymous visitor with no WordPress account, so Application Passwords and current_user_can() don’t apply the way they did for Course 3’s admin-facing agents. The fix is architectural: the browser only ever talks to your own custom REST endpoint, and only that endpoint’s server-side PHP callback holds the credentials to call wp_ai_client_prompt(). The API key never reaches the browser, full stop. That constraint, plus a public endpoint’s much larger abuse surface, shapes every lesson that follows.

Resources & further reading

Building the Chat Widget UI →