This document explains the current WordPress core browser-authentication path, cookie generation, and session validation flow.
Verified against WordPress core source and official Developer Documentation on March 31, 2026. The forward local test lane in this repo is currently pinned to WordPress 7.0-RC1.
At a high level, browser login follows this sequence:
User submits credentials
|
v
wp_signon()
|
|-- do_action_ref_array( 'wp_authenticate', [&$user_login, &$user_password] )
|-- apply_filters( 'secure_signon_cookie', ... )
|-- add_filter( 'authenticate', 'wp_authenticate_cookie', 30, 3 )
|-- wp_authenticate( $user_login, $user_password )
| |
| +-- apply_filters( 'authenticate', null, $username, $password )
| |
| |-- [20] wp_authenticate_username_password()
| |-- [20] wp_authenticate_email_password()
| |-- [20] wp_authenticate_application_password()
| |-- [30] wp_authenticate_cookie() (added inside wp_signon)
| +-- [99] wp_authenticate_spam_check()
|
|-- On success: wp_set_auth_cookie()
|-- Clear user_activation_key if present
+-- do_action( 'wp_login', $user->user_login, $user )
wp_authenticate_cookie()is not a default global registration indefault-filters.php;wp_signon()adds it at runtime with priority30.wp_login_failedis not fired for every failed auth attempt. Core skips it for theempty_usernameandempty_passwordcases insidewp_authenticate().- Plugins can insert their own logic on
authenticate,wp_login,set_auth_cookie,set_logged_in_cookie, anddetermine_current_user. See the companion reference on Two-Factor Plugin Authentication Flow for a concrete example.
Current core defaults register these authenticate callbacks:
add_filter( 'authenticate', 'wp_authenticate_username_password', 20, 3 );
add_filter( 'authenticate', 'wp_authenticate_email_password', 20, 3 );
add_filter( 'authenticate', 'wp_authenticate_application_password', 20, 3 );
add_filter( 'authenticate', 'wp_authenticate_spam_check', 99 );wp_authenticate_username_password()- Looks up the user by login.
- Applies
wp_authenticate_user. - Verifies the password via
wp_check_password(). - Rehashes when
wp_password_needs_rehash()says it should.
wp_authenticate_email_password()- Same pattern, but resolves by email first.
wp_authenticate_application_password()- Handles REST/XML-RPC or other requests allowed by
application_password_is_api_request. - Accepts Basic Auth credentials for application passwords.
- Handles REST/XML-RPC or other requests allowed by
wp_authenticate_spam_check()- Multisite-only spam-account check.
wp_set_auth_cookie() creates a session token, generates two cookie values, fires pre-send actions, and then sends cookies unless send_auth_cookies short-circuits the write.
- Remember-me login: default 14-day lifetime, filterable via
auth_cookie_expiration. - Non-remember login: browser-session cookie (
$expire = 0), while core still appliesauth_cookie_expirationinternally to enforce a default 2-day login lifetime. - Login grace period: for persistent cookies, core keeps the browser sending the cookie for another 12 hours after the main expiration so validation can apply grace behavior.
WP_Session_Tokens::create( $expiration ):
- applies
attach_session_information - stores
expiration - stores
ipif available - stores
ua(user agent) if available - stores
logintimestamp - generates a random 43-character plaintext token
- stores the SHA-256 verifier in user meta under
session_tokens
Default storage backend:
WP_Session_Tokens
βββ WP_User_Meta_Session_Tokens
βββ user meta key: session_tokens
The storage backend is replaceable via session_token_manager.
- Auth cookie (
AUTH_COOKIEorSECURE_AUTH_COOKIE)- scheme:
authorsecure_auth - paths:
PLUGINS_COOKIE_PATH,ADMIN_COOKIE_PATH
- scheme:
- Logged-in cookie (
LOGGED_IN_COOKIE)- scheme:
logged_in - paths:
COOKIEPATH, andSITECOOKIEPATHwhen different
- scheme:
Core currently sets these cookies with httpOnly=true. The auth-cookie security flags also flow through secure_auth_cookie and secure_logged_in_cookie.
Generated cookie format:
username|expiration|token|hmac
Core derives the cookie HMAC in two steps:
- Key derivation
- uses
wp_hash( username|pass_frag|expiration|token, $scheme )
- uses
- Cookie HMAC
- uses
hash_hmac( 'sha256', username|expiration|token, key )
- uses
pass_frag is taken from the password hash:
- for legacy phpass (
$P$) and vanilla bcrypt ($2y$) hashes:substr( ..., 8, 4 ) - otherwise:
substr( ..., -4 )
That matters for current core too: modern non-$2y$ formats fall into the second branch.
- Password changes invalidate cookies because
pass_fragchanges. - Salt rotation invalidates cookies site-wide because
wp_hash()depends on WordPress salts. - Per-session binding comes from the session token verifier stored in user meta.
Core currently registers these callbacks on determine_current_user:
add_filter( 'determine_current_user', 'wp_validate_auth_cookie' );
add_filter( 'determine_current_user', 'wp_validate_logged_in_cookie', 20 );
add_filter( 'determine_current_user', 'wp_validate_application_password', 20 );_wp_get_current_user() applies determine_current_user with false as the starting value, then calls wp_set_current_user() with the resolved ID (or 0).
Important details:
wp_validate_auth_cookie()is the main callback.- Parses the cookie.
- Applies a 1-hour POST/AJAX grace window before treating an otherwise valid cookie as expired.
- Recomputes the HMAC with
hash_equals(). - Verifies the session token via
WP_Session_Tokens::verify(). - Sets
$GLOBALS['login_grace_period']when the cookie is past its nominal expiration but still allowed by grace rules.
wp_validate_logged_in_cookie()is a front-end fallback.- Returns early if a user ID is already resolved.
- Bails on
is_blog_admin(),is_network_admin(), or a missingLOGGED_IN_COOKIE. - Otherwise calls
wp_validate_auth_cookie( $_COOKIE[ LOGGED_IN_COOKIE ], 'logged_in' ).
wp_validate_application_password()only runs if no earlier callback resolved a user and Basic Auth credentials are present.
XML-RPC authentication is special-cased earlier in core. Do not describe determine_current_user as the only path for every authenticated request.
Authentication-cookie validation exposes these hooks:
| Hook | Fires when |
|---|---|
auth_cookie_malformed |
Cookie could not be parsed |
auth_cookie_expired |
Cookie is expired beyond allowed grace |
auth_cookie_bad_username |
User lookup by login failed |
auth_cookie_bad_hash |
HMAC mismatch |
auth_cookie_bad_session_token |
Session verifier missing or invalid |
auth_cookie_valid |
Cookie passed all checks |
Other key auth lifecycle hooks:
| Hook | Fires when |
|---|---|
wp_authenticate |
Before wp_authenticate() runs |
wp_login_failed |
Authentication failed, except empty username/password cases |
set_auth_cookie |
Core generated auth cookie value |
set_logged_in_cookie |
Core generated logged-in cookie value |
clear_auth_cookie |
Core is clearing auth cookies |
wp_login |
Successful login after cookies are set |
WP Sudo rides on top of these core guarantees rather than replacing them:
- WordPress core still owns password verification.
- WordPress core still owns auth-cookie generation and validation.
- WordPress core still owns session-token storage and invalidation.
WP Sudo adds a second, shorter-lived reauthentication session for sensitive actions, but it depends on the browser already having a valid core-authenticated WordPress session.
For a concrete plugin-level extension of this flow, see:
Official documentation:
- wp_signon()
- wp_authenticate()
- wp_authenticate_cookie()
- wp_set_auth_cookie()
- wp_generate_auth_cookie()
- wp_validate_auth_cookie()
- wp_validate_logged_in_cookie()
- wp_validate_application_password()
- wp_authenticate_application_password()
- WP_Session_Tokens
- Application Passwords
- Cookies
Primary source code checked on 2026-03-31:
wp-includes/user.phpwp-includes/pluggable.phpwp-includes/default-filters.phpwp-includes/class-wp-session-tokens.phpwp-includes/class-wp-user-meta-session-tokens.php