Last active
May 1, 2025 08:18
-
-
Save Bobz-zg/a239e75cefdce0c0a7e3e2fb826161d2 to your computer and use it in GitHub Desktop.
WordPress REST Auth with custom cookie
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php declare( strict_types=1 ); | |
/** Prevents direct file access for security. */ | |
defined( 'ABSPATH' ) || exit; | |
/** | |
* Class CodeSoup_CrossDomain_Cookie_Auth | |
* | |
* Drop-in class for secure cross-domain cookie authentication in WordPress REST API. | |
* | |
* Features: | |
* - Generates and validates secure authentication cookies across subdomains. | |
* - Integrates with WordPress session tokens for user validation. | |
* - Provides login, logout and nonce endpoints for REST API workflows. | |
* - Designed for easy integration into any plugin, with detailed inline documentation. | |
* | |
* Usage: | |
* 1. Include this file in your plugin. | |
* 2. Instantiate the class in your plugin bootstrap file: | |
* $cross_domain_auth = new CodeSoup_CrossDomain_Cookie_Auth(); | |
* 3. Ensure your site uses HTTPS and proper CORS headers for cross-domain requests. | |
* | |
* @author Code Soup | |
* @version 1.1.0 | |
*/ | |
class CodeSoup_CrossDomain_Cookie_Auth { | |
/** | |
* Name of the custom authentication cookie. | |
* @var string | |
*/ | |
public const COOKIE_NAME = 'pwa_rest_auth'; | |
/** | |
* Cookie domain for cross-subdomain authentication. | |
* Example: '.yourdomain.com' | |
* @var string | |
*/ | |
public const COOKIE_DOMAIN = '.yourdomain.com'; // Example: '.yourdomain.com' to allow subdomains. TODO: Make this configurable. | |
/** | |
* Cookie path. | |
* @var string | |
*/ | |
public const COOKIE_PATH = '/'; | |
/** | |
* Cookie expiration time in seconds (two weeks). | |
* Uses the WordPress DAY_IN_SECONDS constant for readability. | |
* | |
* @see DAY_IN_SECONDS https://codex.wordpress.org/Easier_Expression_of_Time_Constants | |
* @var int | |
*/ | |
public const COOKIE_LIFETIME = 14 * DAY_IN_SECONDS; // Two weeks | |
/** | |
* Array of allowed domains for CORS (Cross-Origin Resource Sharing). | |
* These are the origins (domains) permitted to make authenticated requests | |
* to this WordPress site's REST API using the custom cookie. | |
* IMPORTANT: Only list trusted domains. | |
* @var array<string> | |
*/ | |
public const ALLOWED_DOMAINS = [ | |
'https://app.yourdomain.com', | |
'https://wp.yourdomain.com', | |
// Add more domains as needed | |
]; | |
/** | |
* Constructor: sets up all hooks. | |
*/ | |
public function __construct() { | |
// Register REST API endpoints for login, logout, and nonce | |
add_action( 'rest_api_init', [ $this, 'register_rest_routes' ] ); | |
// Generate custom cookie value | |
add_filter( 'auth_cookie', [ $this, 'auth_cookie' ], 20, 5 ); | |
// Authenticate user on REST requests using custom cookie | |
add_filter( 'determine_current_user', [ $this, 'determine_current_user' ], 20 ); | |
// Set CORS headers for REST API responses using a filter that runs later | |
add_filter( 'rest_pre_serve_request', [ $this, 'add_cors_headers' ], 15, 4 ); | |
} | |
/** | |
* Registers custom REST API routes for login, logout, and nonce retrieval. | |
* Uses the 'codesoup/v1/auth' namespace. | |
* | |
* @return void | |
*/ | |
public function register_rest_routes(): void { | |
$namespace = 'codesoup/v1/auth'; | |
register_rest_route( $namespace, '/login', [ | |
'methods' => 'POST', | |
'callback' => [ $this, 'handle_login' ], | |
'permission_callback' => '__return_true', // Anyone can attempt to log in. | |
'args' => [ // Define and validate expected request parameters. | |
'username' => [ | |
'required' => true, | |
'type' => 'string', | |
'description' => __( 'WordPress username or email.', 'my-plugin-name' ), | |
'sanitize_callback' => 'sanitize_user', // Sanitize username input. | |
], | |
'password' => [ | |
'required' => true, | |
'type' => 'string', | |
'description' => __( 'WordPress password.', 'my-plugin-name' ), | |
// No sanitize_callback for password, as it's used directly for authentication. | |
], | |
], | |
'schema' => [ // Provides a schema for the request body (useful for documentation/validation). | |
// Define the expected request structure | |
'$schema' => 'http://json-schema.org/draft-04/schema#', | |
'title' => 'login', | |
'type' => 'object', | |
'properties' => [ | |
'username' => [ | |
'description' => esc_html__( 'User login name or email address.', 'my-plugin-name' ), | |
'type' => 'string', | |
'required' => true, | |
], | |
'password' => [ | |
'description' => esc_html__( 'User password.', 'my-plugin-name' ), | |
'type' => 'string', | |
'required' => true, | |
], | |
], | |
], | |
] ); | |
register_rest_route( $namespace, '/logout', [ | |
'methods' => 'POST', | |
'callback' => [ $this, 'handle_logout' ], | |
'permission_callback' => [ $this, 'is_user_logged_in_with_valid_nonce' ], // Requires logged-in user and valid nonce. | |
'schema' => [ | |
// Define the expected request structure (empty for logout) | |
'$schema' => 'http://json-schema.org/draft-04/schema#', | |
'title' => 'logout', | |
'type' => 'object', | |
'properties' => [], // No body parameters expected | |
], | |
] ); | |
register_rest_route( $namespace, '/nonce', [ | |
'methods' => 'GET', | |
'callback' => [ $this, 'handle_nonce' ], | |
'permission_callback' => 'is_user_logged_in', // Standard login check is sufficient, nonce obtained here. | |
'schema' => [ | |
// Define the expected request structure (empty for nonce GET) | |
'$schema' => 'http://json-schema.org/draft-04/schema#', | |
'title' => 'nonce', | |
'type' => 'object', | |
'properties' => [], // No parameters expected | |
], | |
] ); | |
} | |
/** | |
* Handles user login, sets a secure cross-domain cookie, and returns a REST nonce. | |
* | |
* @param WP_REST_Request $request The incoming request object. | |
* @return WP_REST_Response|WP_Error Response object or WP_Error on failure. | |
*/ | |
public function handle_login( WP_REST_Request $request ): WP_REST_Response|WP_Error { | |
// Parameters are already validated/sanitized by the 'args' definition | |
// in register_rest_route, ensuring they meet the required format and type. | |
$username = $request['username']; | |
$password = $request['password']; | |
// Authenticate the user using the standard WordPress function. | |
// This checks the provided username and password against the WP user database. | |
$user = wp_authenticate( $username, $password ); | |
if ( is_wp_error( $user ) ) { | |
// Authentication failed. Return a WP_Error object. | |
// Use the specific error message from wp_authenticate if available for better feedback. | |
$error_message = $user->get_error_message(); | |
return new WP_Error( | |
'auth_failed', | |
$error_message ?: __( 'Invalid credentials.', 'my-plugin-name' ), | |
[ 'status' => 401 ] | |
); | |
} | |
// Authentication successful. Set the custom cross-domain authentication cookie. | |
$this->set_cookie( $user ); | |
// Optionally, set the standard WordPress auth cookie as well. | |
// This might be necessary if other parts of the site or plugins rely on the standard cookie. | |
// However, if the frontend relies solely on the custom cookie via REST, this might be omitted. | |
// wp_set_auth_cookie( $user->ID, true ); // Consider if this is needed for your setup | |
// Return a success response including the user ID and a fresh REST API nonce. | |
// The nonce is crucial for subsequent authenticated requests (like logout) to prevent CSRF attacks. | |
return new WP_REST_Response( [ | |
'success' => true, | |
'user_id' => $user->ID, | |
'message' => __( 'Login successful.', 'my-plugin-name' ), | |
'nonce' => wp_create_nonce( 'wp_rest' ) | |
] ); | |
} | |
/** | |
* Handles user logout requests. Clears both standard and custom auth cookies. | |
* Requires a valid nonce passed via X-WP-Nonce header. | |
* | |
* @param WP_REST_Request $request Full data about the request. | |
* @return WP_REST_Response Response object on success. | |
*/ | |
public function handle_logout( WP_REST_Request $request ): WP_REST_Response { | |
// Nonce verification is handled by the 'permission_callback' defined in register_rest_route. | |
// This ensures the logout request originated from an authenticated session. | |
// Clear standard WordPress authentication cookies and associated session tokens. | |
// This logs the user out of the standard WordPress session. | |
wp_logout(); | |
// Explicitly unset/expire the custom authentication cookie. | |
// Check if headers have already been sent to avoid errors. | |
if ( ! headers_sent() ) { | |
// Use modern array syntax for setcookie options for clarity. | |
setcookie( self::COOKIE_NAME, '', [ | |
'expires' => time() - YEAR_IN_SECONDS, // Set expiration date far in the past to ensure removal. | |
'path' => self::COOKIE_PATH, // Must match the path used when setting the cookie. | |
'domain' => self::COOKIE_DOMAIN, // Must match the domain used when setting the cookie. | |
'secure' => is_ssl(), // Should be true if site uses HTTPS. | |
'httponly' => true, // Prevents client-side script access to the cookie. | |
'samesite' => 'None' // Required for cross-domain usage when 'secure' is true. | |
] ); | |
} else { | |
// Log an error if the cookie couldn't be unset because headers were already sent. | |
// This usually indicates an issue elsewhere in the code outputting content too early. | |
error_log( 'Could not unset ' . self::COOKIE_NAME . ' cookie because headers were already sent.' ); | |
} | |
// Return success response indicating successful logout. | |
return new WP_REST_Response( | |
[ | |
'success' => true, | |
'message' => __( 'You have been logged out successfully.', 'my-plugin-name' ), | |
], | |
200 // HTTP 200 OK | |
); | |
} | |
/** | |
* Returns a fresh REST API nonce for the authenticated user. | |
* | |
* @return WP_REST_Response|WP_Error WP_REST_Response with nonce on success. | |
*/ | |
public function handle_nonce(): WP_REST_Response|WP_Error { | |
// The 'permission_callback' ('is_user_logged_in') ensures this endpoint | |
// is only accessible to logged-in users (authenticated via standard or custom cookie). | |
return new WP_REST_Response( [ | |
'success' => true, | |
// Generate a fresh nonce for the 'wp_rest' action. | |
// This nonce should be used in the 'X-WP-Nonce' header for subsequent authenticated POST/PUT/DELETE requests. | |
'nonce' => wp_create_nonce( 'wp_rest' ) | |
] ); | |
} | |
/** | |
* Generate custom cookie format: user_id|expiration|token|hmac | |
* This method filters the standard WordPress auth cookie generation. | |
* Instead of returning the default cookie, it returns our custom structured cookie. | |
* | |
* @param string $cookie Default WP auth cookie value (ignored in our case, except for testcookie). | |
* @param int $user_id User ID. | |
* @param int $expiration Cookie expiration timestamp. | |
* @param string $scheme Authentication scheme ('auth', 'secure_auth', 'logged_in'). | |
* @param string $token User's session token. | |
* @return string Custom cookie value in the format: "user_id|expiration|token|hmac". | |
* | |
* @link https://developer.wordpress.org/reference/hooks/auth_cookie/ | |
*/ | |
public function auth_cookie( string $cookie, int $user_id, int $expiration, string $scheme, string $token ): string { | |
// The 'testcookie' request parameter is used by WordPress during the standard login process | |
// to check if the browser supports cookies. We must return the original cookie value | |
// in this specific case to avoid breaking the standard wp-login.php flow. | |
// Consider potential implications if both standard and REST logins are used concurrently. | |
if ( isset( $_REQUEST['testcookie'] ) && $_REQUEST['testcookie'] ) { | |
return $cookie; | |
} | |
// Retrieve the authentication key salt. This is crucial for generating a secure HMAC. | |
$key = wp_salt( 'auth' ); | |
// Generate a Hash-based Message Authentication Code (HMAC) using SHA256. | |
// This ensures the cookie data (user ID, expiration, token) hasn't been tampered with. | |
// The HMAC is created using the user ID, expiration, token, and the secret auth key. | |
$hmac = hash_hmac( 'sha256', "{$user_id}|{$expiration}|{$token}", $key ); | |
// Combine the user ID, expiration timestamp, session token, and the HMAC, | |
// separated by pipes, to form the final custom cookie value. | |
return implode( '|', [ $user_id, $expiration, $token, $hmac ] ); | |
} | |
/** | |
* Authenticates the user for REST API requests using the custom cookie. | |
* | |
* @param int|false|null $user_id Current user ID determination. | |
* @return int|false|null User ID if authenticated, otherwise the original value. | |
* | |
* @link https://developer.wordpress.org/reference/hooks/determine_current_user/ | |
*/ | |
public function determine_current_user( $user_id ): int|false|null { | |
// If a user ID is already determined (e.g., by a standard WP auth cookie or another method), | |
// or if this isn't a REST API request (checked via the REST_REQUEST constant), | |
// or if our custom cookie isn't present, then we don't need to do anything. | |
// Let WordPress continue its default user determination process. | |
if ( $user_id || ! ( defined( 'REST_REQUEST' ) && REST_REQUEST ) || empty( $_COOKIE[ self::COOKIE_NAME ] ) ) { | |
return $user_id; | |
} | |
// Retrieve the custom cookie value. Use wp_unslash as cookie values might be slashed by PHP/WordPress. | |
$cookie_value = wp_unslash( $_COOKIE[ self::COOKIE_NAME ] ); | |
// Split the cookie value into its constituent parts based on the pipe delimiter. | |
$parts = explode( '|', $cookie_value ); | |
// The cookie must have exactly 4 parts: user_id, expiration, token, hmac. | |
if ( count( $parts ) !== 4 ) { | |
// Invalid cookie format. Ignore the cookie and return the original user ID determination. | |
return $user_id; | |
} | |
// Assign parts to variables for clarity. Perform explicit type casting for numeric values. | |
list( $uid_str, $expiration_str, $token, $hmac ) = $parts; | |
$uid = (int) $uid_str; | |
$expiration = (int) $expiration_str; | |
// Validate the expiration timestamp. If the current time is past the expiration, the cookie is invalid. | |
if ( $expiration < time() ) { | |
// Cookie has expired. Ignore the cookie. | |
return $user_id; | |
} | |
// Re-generate the HMAC using the same data (uid, expiration, token) and the secret auth key. | |
$key = wp_salt( 'auth' ); | |
$expected_hmac = hash_hmac( 'sha256', "{$uid}|{$expiration}|{$token}", $key ); | |
// Compare the expected HMAC with the HMAC from the cookie using hash_equals for timing attack resistance. | |
if ( ! hash_equals( $expected_hmac, $hmac ) ) { | |
// HMAC mismatch indicates the cookie data may have been tampered with. Ignore the cookie. | |
return $user_id; | |
} | |
// Validate the session token using WordPress's session management API. | |
// This checks if the session token is valid for the given user ID and hasn't been revoked (e.g., by logging out elsewhere). | |
$manager = WP_Session_Tokens::get_instance( $uid ); | |
if ( ! $manager->verify( $token ) ) { | |
// Session token is invalid or expired according to WordPress. Ignore the cookie. | |
return $user_id; | |
} | |
// All checks passed: format is correct, cookie hasn't expired, data integrity is verified (HMAC), | |
// and the session token is valid according to WordPress. | |
// Therefore, authenticate the user based on this cookie by returning their user ID. | |
return $uid; | |
} | |
/** | |
* Adds necessary CORS headers to the REST API response. | |
* Handles OPTIONS pre-flight requests. | |
* Hooked into 'rest_pre_serve_request'. | |
* | |
* @param bool $served Whether the request has already been served. | |
* @param WP_REST_Response $result Result to send to the client. | |
* @param WP_REST_Request $request Request used to generate the response. | |
* @param WP_REST_Server $server Server instance. | |
* @return bool Whether the request handler should continue processing. | |
*/ | |
public function add_cors_headers( bool $served, WP_REST_Response $result, WP_REST_Request $request, WP_REST_Server $server ): bool { | |
// Get the origin of the incoming request (e.g., 'https://app.yourdomain.com'). | |
$origin = get_http_origin(); | |
// Check if the origin is present and is in our list of allowed domains. | |
if ( $origin && in_array( $origin, self::ALLOWED_DOMAINS ) ) { | |
// Origin is allowed. Send necessary CORS headers using the WP_REST_Server instance. | |
// This allows the browser on the specified origin to access the response. | |
$server->send_header( 'Access-Control-Allow-Origin', $origin ); | |
// Specify allowed HTTP methods for cross-origin requests. | |
$server->send_header( 'Access-Control-Allow-Methods', 'POST, GET, OPTIONS, PUT, DELETE' ); | |
// Allow credentials (like cookies) to be sent with cross-origin requests. | |
$server->send_header( 'Access-Control-Allow-Credentials', 'true' ); | |
// Specify allowed headers in cross-origin requests (e.g., for authentication, content type, nonce). | |
$server->send_header( 'Access-Control-Allow-Headers', 'Authorization, Content-Type, X-WP-Nonce, X-Requested-With' ); | |
// Add Referrer-Policy header for better security, restricting referrer information. | |
$server->send_header( 'Referrer-Policy', 'origin' ); | |
// Handle OPTIONS pre-flight requests specifically. | |
// Browsers send an OPTIONS request before a 'complex' cross-origin request (e.g., POST with JSON) | |
// to check if the actual request is allowed. | |
if ( 'OPTIONS' === $request->get_method() ) { | |
// For OPTIONS, just send the CORS headers with a success status (204 No Content is common) | |
// and stop further WordPress processing for this request. | |
status_header( 204 ); | |
exit; // Stop processing further for OPTIONS | |
} | |
} elseif ( $origin ) { | |
// If an origin was provided but it's not in the allowed list, log it for debugging/monitoring. | |
// This helps identify configuration issues or potentially malicious requests. | |
error_log( 'CORS Origin denied: ' . $origin ); | |
} | |
// For non-OPTIONS requests or requests from disallowed origins, | |
// return the original $served value to let WordPress continue its normal response processing. | |
return $served; | |
} | |
/** | |
* Sets the custom cross-domain authentication cookie. | |
* | |
* @param WP_User $user The user object for whom to set the cookie. | |
* @return void | |
*/ | |
private function set_cookie( WP_User $user ): void { | |
// Use WordPress's session token manager to create a secure session token for the user. | |
// This token is tied to the user's session and login time. | |
$manager = WP_Session_Tokens::get_instance( $user->ID ); | |
// Set the cookie expiration time based on the defined lifetime. | |
$expiration = time() + self::COOKIE_LIFETIME; | |
// Create the session token with the calculated expiration. | |
$token = $manager->create( $expiration ); | |
// Create a secure HMAC for cookie integrity, using the same method as in auth_cookie. | |
// This ensures the cookie we set can be verified later. | |
$key = wp_salt( 'auth' ); | |
$hmac = hash_hmac( 'sha256', "{$user->ID}|{$expiration}|{$token}", $key ); | |
// Compose the final cookie value string. | |
$cookie_value = implode( '|', [ $user->ID, $expiration, $token, $hmac ] ); | |
// Set the cookie using PHP's setcookie function with appropriate security attributes. | |
setcookie( | |
self::COOKIE_NAME, | |
$cookie_value, | |
[ | |
'expires' => $expiration, | |
'path' => self::COOKIE_PATH, | |
'domain' => self::COOKIE_DOMAIN, | |
'secure' => is_ssl(), // Dynamically set based on connection | |
'httponly' => true, | |
'samesite' => 'None' // Necessary for cross-site usage with Secure flag | |
] | |
); | |
} | |
/** | |
* Checks if the user is logged in and has provided a valid nonce. | |
* Used as a permission callback for actions requiring authentication and CSRF protection. | |
* | |
* @param WP_REST_Request $request The request object. | |
* @return bool|WP_Error True if authorized, WP_Error otherwise. | |
*/ | |
public function is_user_logged_in_with_valid_nonce( WP_REST_Request $request ): bool|WP_Error { | |
// First, check if the user is considered logged in (could be via standard or our custom cookie). | |
if ( ! is_user_logged_in() ) { | |
// If not logged in, return a WP_Error with a 401 Unauthorized status. | |
return new WP_Error( | |
'rest_not_logged_in', | |
__( 'You are not currently logged in.', 'my-plugin-name' ), | |
[ 'status' => 401 ] // Unauthorized | |
); | |
} | |
// If logged in, check for the CSRF nonce provided in the 'X-WP-Nonce' request header. | |
// This header should be sent by the client application using the nonce obtained from /login or /nonce endpoints. | |
$nonce = $request->get_header( 'X-WP-Nonce' ); | |
// Verify the nonce against the 'wp_rest' action. | |
// If the nonce is missing or invalid, return a WP_Error with a 403 Forbidden status. | |
if ( ! $nonce || ! wp_verify_nonce( $nonce, 'wp_rest' ) ) { | |
return new WP_Error( | |
'rest_cookie_invalid_nonce', | |
__( 'Cookie check failed. Missing or invalid nonce.', 'my-plugin-name' ), | |
[ 'status' => 403 ] // Forbidden | |
); | |
} | |
// Both checks passed: User is logged in and provided a valid nonce. | |
// Grant permission to proceed with the requested action (e.g., logout). | |
return true; | |
} | |
} | |
/** | |
* ============================================================================== | |
* Developer Documentation: CodeSoup_CrossDomain_Cookie_Auth Class | |
* ============================================================================== | |
* | |
* Purpose: | |
* -------- | |
* This class provides a robust mechanism for handling user authentication for the | |
* WordPress REST API, specifically designed for scenarios where the frontend | |
* application (e.g., a PWA or SPA) resides on a different subdomain than the | |
* WordPress backend. It replaces the standard WordPress cookie authentication | |
* with a custom, secure HTTPOnly cookie that can be shared across specified subdomains. | |
* | |
* Key Features: | |
* ------------- | |
* 1. **Cross-Domain Cookie Authentication:** | |
* - Generates a custom authentication cookie (`wp_rest_auth` by default) during login. | |
* - This cookie is configured with `SameSite=None; Secure` and a configurable domain | |
* (e.g., `.yourdomain.com`) allowing it to be sent by browsers from allowed subdomains. | |
* - Uses WordPress session tokens (`WP_Session_Tokens`) for secure session management. | |
* - Includes an HMAC (Hash-based Message Authentication Code) within the cookie value | |
* to ensure data integrity and prevent tampering. | |
* | |
* 2. **REST API Endpoints:** | |
* - Registers the following endpoints under the `codesoup/v1/auth` namespace: | |
* - `POST /login`: Authenticates user credentials (username/password), sets the custom | |
* auth cookie, and returns a `wp_rest` nonce for CSRF protection. | |
* - `POST /logout`: Logs the user out by clearing the standard WP session/cookie | |
* and explicitly expiring the custom auth cookie. Requires a valid `wp_rest` nonce | |
* sent via the `X-WP-Nonce` header. | |
* - `GET /nonce`: Provides a fresh `wp_rest` nonce for authenticated users. Useful for | |
* clients needing a new nonce for subsequent state-changing requests. | |
* | |
* 3. **WordPress Integration:** | |
* - Hooks into `determine_current_user` to authenticate users based on the custom cookie | |
* for incoming REST API requests. | |
* - Hooks into `auth_cookie` to generate the custom cookie format when a user logs in | |
* (while carefully handling the standard `testcookie` check). | |
* - Hooks into `rest_pre_serve_request` to correctly handle CORS headers, allowing | |
* requests from configured origins (`ALLOWED_DOMAINS`) and managing OPTIONS pre-flight requests. | |
* | |
* 4. **Security:** | |
* - Enforces HTTPS for the cookie (`Secure` attribute, `is_ssl()` checks). | |
* - Makes the cookie `HttpOnly` to prevent access via client-side JavaScript. | |
* - Requires nonces (`wp_rest`) for state-changing operations (logout) via the | |
* `is_user_logged_in_with_valid_nonce` permission callback, mitigating CSRF risks. | |
* - Validates cookie expiration and HMAC integrity during user determination. | |
* - Uses `wp_salt('auth')` for secure keying of the HMAC. | |
* - Includes basic input validation and sanitization for login parameters via `register_rest_route` args. | |
* | |
* 5. **Configuration & Usage:** | |
* - Key parameters like `COOKIE_DOMAIN` and `ALLOWED_DOMAINS` are defined as constants. | |
* (Marked with TODOs for potential external configuration in a real-world plugin). | |
* - Requires instantiation within a plugin or theme (e.g., `new CodeSoup_CrossDomain_Cookie_Auth();`). | |
* - Assumes the frontend will handle storing the user ID/details and the nonce received upon login, | |
* and will send the `X-WP-Nonce` header with subsequent authenticated requests. | |
* | |
* How it Works (Flow): | |
* --------------------- | |
* 1. **Login:** Client sends POST to `/codesoup/v1/auth/login` with username/password. | |
* - `handle_login` validates credentials via `wp_authenticate`. | |
* - If valid, `set_cookie` is called. | |
* - `set_cookie` generates a WP session token and an HMAC. | |
* - `set_cookie` sends the `wp_rest_auth` cookie (user_id|expiration|token|hmac) via `setcookie` | |
* with appropriate domain, path, secure, httponly, samesite attributes. | |
* - Response includes `success: true`, `user_id`, and a `nonce`. | |
* 2. **Authenticated Request (e.g., POST to another endpoint):** | |
* - Client sends request including the `wp_rest_auth` cookie and the `X-WP-Nonce` header. | |
* - `determine_current_user` filter is triggered. | |
* - It parses the `wp_rest_auth` cookie, validates expiration, HMAC, and the WP session token. | |
* - If valid, it returns the user ID, authenticating the request for WordPress. | |
* - The target endpoint's permission callback likely checks `is_user_logged_in()` and potentially verifies the nonce. | |
* 3. **Logout:** Client sends POST to `/codesoup/v1/auth/logout` with `X-WP-Nonce` header. | |
* - `is_user_logged_in_with_valid_nonce` permission callback verifies login status and nonce. | |
* - If valid, `handle_logout` calls `wp_logout` (clears standard session) and explicitly | |
* sends an expired `wp_rest_auth` cookie via `setcookie`. | |
* | |
* Dependencies: | |
* ------------- | |
* - WordPress Core functions (REST API, authentication, nonces, session tokens, salts). | |
* - PHP 7.4+ (due to strict types, arrow functions might be usable if min PHP version allows). | |
* | |
* Considerations: | |
* --------------- | |
* - Ensure the `COOKIE_DOMAIN` and `ALLOWED_DOMAINS` constants are correctly configured for the target environment. | |
* - Requires HTTPS on the site for the `Secure` cookie attribute to work correctly. | |
* - The frontend client is responsible for securely handling the received nonce and including it in subsequent requests. | |
* - The `testcookie` handling in `auth_cookie` ensures compatibility with standard wp-login but warrants review depending on specific plugin/theme interactions. | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment