Contents
Overview
This article explains safe, practical approaches for detecting in JavaScript whether a visitor is logged into a WordPress site. It covers multiple methods (inline data, localized script data, REST API), shows secure example code for both PHP and JavaScript, explains security and caching pitfalls, and gives recommendations for production usage.
Principles and constraints
- Do not expose sensitive data. Only expose minimal, non-sensitive fields such as a boolean isLoggedIn or a sanitized display name. Never expose email, capabilities, roles, authentication cookies or secrets.
- Sanitize and escape. Any server-side data embedded into JavaScript must be safely escaped (use wp_json_encode(), esc_js(), wp_strip_all_tags(), etc.).
- Respect caching. Full-page caches (CDN, Varnish) can serve a cached page for many users. Do not embed user-specific state in pages that will be cached as shared content unless caching is varied by cookie.
- Prefer authenticated REST calls for per-user info. For robust per-user state behind caches, have client-side JavaScript fetch a small authenticated REST endpoint with a nonce or via same-origin cookies.
Common detection methods (summary)
Method | Data exposed | Pros | Cons / Notes |
---|---|---|---|
Body class (.logged-in) | none (derived from server-side) | Simple, zero extra code | Not reliable with caches depends on server-rendered class |
wp_localize_script / inline JSON | isLoggedIn boolean, nonce, minimal user info | Fast, accessible without extra requests | Must consider caching escape carefully |
Authenticated REST call | server-determined boolean / sanitized fields | Works well with caches per-user accurate | Requires nonce or cookie auth one extra request |
Cookie presence check | cookie names | Simple | Unreliable and brittle not recommended as sole check |
Recommended approach
For most modern setups, use a combination:
- Expose a minimal JavaScript object on the page with a nonce and a cached-safe boolean if your caching allows per-user variation (via wp_localize_script or wp_add_inline_script). This gives fast access for simple UX without further requests.
- If you need accurate per-user state in cached setups, make a small authenticated REST request from JavaScript to a server endpoint that returns a tiny JSON payload (isLoggedIn sanitized display name). Protect it with the WP REST nonce and mark responses as non-cacheable for privacy.
Implementation details and examples
1) Server: Enqueue script and expose safe data (wp_localize_script)
This example enqueues a front-end script and exposes a safe JSON object. Escape and sanitize values. Use wp_create_nonce(wp_rest) to allow authenticated REST calls.
// In your themes functions.php or a plugin file add_action(wp_enqueue_scripts, myplugin_enqueue_scripts) function myplugin_enqueue_scripts() { // Register / enqueue your script wp_register_script( myplugin-frontend, get_template_directory_uri() . /js/myplugin-frontend.js, array(), // deps filemtime( get_template_directory() . /js/myplugin-frontend.js ), true ) // Safe, minimal data to expose is_logged_in = is_user_logged_in() user_display = if ( is_logged_in ) { user = wp_get_current_user() // Strip tags and limit length dont expose email or login user_display = wp_strip_all_tags( user->display_name ) user_display = mb_substr( user_display, 0, 80 ) } // Data object (escaped by wp_localize_script under the hood, but prefer wp_json_encode for complex objects) data = array( isLoggedIn => is_logged_in ? true : false, nonce => wp_create_nonce( wp_rest ), // for REST calls userDisplay => user_display, ) wp_localize_script( myplugin-frontend, MySiteData, data ) wp_enqueue_script( myplugin-frontend ) }
Notes:
- wp_localize_script is commonly used to pass simple data. For complex objects prefer wp_add_inline_script with wp_json_encode() to avoid unintended escaping.
- Keep the object minimal (only what the client needs).
2) Server: Use wp_add_inline_script with JSON-encoded data
Alternative approach that uses JSON encoding explicitly and avoids surprises from wp_localize_script escaping semantics.
add_action(wp_enqueue_scripts, myplugin_inline_data) function myplugin_inline_data() { wp_enqueue_script( myplugin-frontend, get_template_directory_uri() . /js/myplugin-frontend.js, array(), null, true ) data = array( isLoggedIn => is_user_logged_in() ? true : false, nonce => wp_create_nonce( wp_rest ), ) // Safely encode JSON and inject as a small inline script exposing a single global json = wp_json_encode( data ) wp_add_inline_script( myplugin-frontend, window.MySiteData = {json}, before ) }
3) Client: Read the localized data in JavaScript
Read the global object (from wp_localize_script or inline script) and use it. This snippet assumes MySiteData exists.
// myplugin-frontend.js (function () { const data = window.MySiteData {} if (data.isLoggedIn) { console.log(User is logged in.) if (data.userDisplay) { // safe to show sanitized display name console.log(Display name:, data.userDisplay) } } else { console.log(User is not logged in.) } })()
4) Body class check (quick, but not always reliable)
WordPress adds the class logged-in to the body element for logged-in users. This is a simple test from JS, but it is unreliable if your pages are served from a shared cache.
// Quick check - works if the rendered page is personalized and not cached const isLoggedInByBodyClass = document.body document.body.classList ? document.body.classList.contains(logged-in) : false
Do not rely on this if your site uses a caching layer that does not vary content by cookies.
5) More robust: Create a small authenticated REST endpoint
When pages are cached for anonymous users, the most reliable approach is to make a client-side request to a small server endpoint that determines authenticated state using WordPress authentication (cookies nonce). The REST API is a good choice.
// Plugin or theme: register a tiny REST route that returns minimal info add_action(rest_api_init, function () { register_rest_route( myplugin/v1, /status, array( methods => GET, callback => myplugin_rest_status, // Require authentication: ensure only authenticated requests return user info permission_callback => function () { return is_user_logged_in() }, )) }) function myplugin_rest_status( request ) { user = wp_get_current_user() data = array( isLoggedIn => user->ID ? true : false, userDisplay => user->ID ? wp_strip_all_tags( user->display_name ) : , ) // Ensure response is not cached by shared caches to avoid leaking per-user data response = rest_ensure_response( data ) response->header( Cache-Control, private, no-store, must-revalidate ) return response }
Notes:
- permission_callback above requires is_user_logged_in() the REST request will be authenticated using the logged-in cookie combined with the X-WP-Nonce for same-origin requests.
- For public endpoints that must be called without authentication, only return generic flags (avoid per-user info).
6) Client: Call the REST endpoint securely with nonce
Fetch the REST endpoint from your front-end script using the X-WP-Nonce header that wp_create_nonce(wp_rest) generated. Include credentials for same-origin cookie transmission.
// myplugin-frontend.js: request /wp-json/myplugin/v1/status (async function () { const data = window.MySiteData {} try { const r = await fetch(/wp-json/myplugin/v1/status, { method: GET, credentials: same-origin, // send cookies headers: { X-WP-Nonce: data.nonce } }) if (!r.ok) { // For non-authenticated responses, the endpoint may return 401/403 console.log(Not authenticated or other error, r.status) return } const json = await r.json() if (json.isLoggedIn) { console.log(User is logged in (from REST):, json.userDisplay) } else { console.log(User not logged in (from REST)) } } catch (err) { console.error(REST request failed, err) } })()
Security considerations
- Never expose sensitive user fields such as email, user_login, capabilities, or numeric IDs unless absolutely required and sanitized.
- Escape everything when injecting data into inline scripts: wp_json_encode is the best approach. Avoid manual string concatenation.
- Use the REST nonce for authenticated REST calls. Generate via wp_create_nonce(wp_rest) and pass to client. Do not use nonces as long-term credentials nonces have limited lifetime and purpose.
- Protect REST endpoints with permission callbacks and minimal responses. If the endpoint returns per-user data, restrict to authenticated requests and set cache headers appropriately.
- Cache headers are critical. For personalized responses, add Cache-Control: private, no-store or similar to avoid proxies serving a logged-in page to another visitor.
- Consider SameSite and secure cookies — WP core manages cookies but be mindful if you change cookie behavior or domains.
Performance and caching notes
- Embedding a per-user flag server-side is fastest, but only safe if your caching layer varies by authentication cookies or you disable caching for logged-in users.
- If pages are cached for anonymous users, use a separate REST call to determine user state that lets you keep pages highly cacheable while returning accurate per-user data in the client.
- Reduce the REST endpoint payload to a minimal JSON object to reduce latency.
Edge-cases and pitfalls
- Some CDNs (e.g., Cloudflare) cache static HTML aggressively. If the HTML contains a hard-coded isLoggedIn true/false and is cached, you risk stale or incorrect behavior. Use client-side REST checks if you are not sure.
- wp_localize_script only runs when WordPress is rendering the page on the server. If a page is served from cache without executing PHP, localized data wont be updated.
- Reading authentication cookies via document.cookie to detect logged-in state may reveal cookie names and is brittle across multisite and different cookie configurations.
Checklist before shipping
- Is the client-side payload minimal and sanitized?
- Is any endpoint returning per-user data protected by authentication and not cacheable by shared caches?
- Are nonces generated and passed when doing authenticated REST calls?
- Have you tested behavior behind your production caching/CDN configuration?
- Have you audited what data is available to client-side scripts and removed anything unnecessary?
References
Summary
To detect if a visitor is logged in from JavaScript in WordPress, favor minimal data exposure and authenticated server-side checks. The simplest approach for non-cached, personalized pages is to expose a small, sanitized object via wp_localize_script or an inline JSON script. For environments that serve cached pages, use an authenticated REST call protected by an X-WP-Nonce and return only the tiny amount of data the client needs. Always sanitize, escape, and consider caching headers to avoid leaking personalized content.
|
Acepto donaciones de BAT's mediante el navegador Brave 🙂 |