I am working with a client with some strict security measures. After undergoing security review, we were notified that the user name stored in the logged in cookie, e.g.

wordpress_logged_in[username]|[hash]

is something that has to be removed. Since this is an integral part of the login system, I'm not sure how to remove it and still maintain the session.

up vote 10 down vote accepted
+50

Short introduction

After quick look inside WP source code, I think I've found solution...

WordPress uses two functions to set and parse auth cookies:

  • wp_generate_auth_cookie
  • wp_parse_auth_cookie

There is a filter in wp_generate_auth_cookie called auth_cookie which you probably could use to change the contents of cookie, but there is no filter inside wp_parse_auth_cookie, but...

Both of these functions are defined in pluggable.php, which means, that you can write your own implementations for them and overwrite default ones.

Solution

  1. Write your own plugin (let's call it Better Auth Cookie)
  2. Implement your own wp_generate_auth_cookie and wp_parse_auth_cookie functions inside this plugin.
  3. Activate your plugin.

You can find my sample implementation (based strongly on original versions) of these functions below:

if ( !function_exists('wp_generate_auth_cookie') ) :
/**
 * Generate authentication cookie contents.
 *
 * @since 2.5.0
 *
 * @param int $user_id User ID
 * @param int $expiration Cookie expiration in seconds
 * @param string $scheme Optional. The cookie scheme to use: auth, secure_auth, or logged_in
 * @param string $token User's session token to use for this cookie
 * @return string Authentication cookie contents. Empty string if user does not exist.
 */
function wp_generate_auth_cookie( $user_id, $expiration, $scheme = 'auth', $token = '' ) {
    $user = get_userdata($user_id);
    if ( ! $user ) {
        return '';
    }

    if ( ! $token ) {
        $manager = WP_Session_Tokens::get_instance( $user_id );
        $token = $manager->create( $expiration );
    }

    $pass_frag = substr($user->user_pass, 8, 4);

    $key = wp_hash( $user->user_login . '|' . $pass_frag . '|' . $expiration . '|' . $token, $scheme );

    // If ext/hash is not present, compat.php's hash_hmac() does not support sha256.
    $algo = function_exists( 'hash' ) ? 'sha256' : 'sha1';
    $hash = hash_hmac( $algo, $user->user_login . '|' . $expiration . '|' . $token, $key );

    $cookie = $user_id . '|' . $expiration . '|' . $token . '|' . $hash;

    /**
     * Filter the authentication cookie.
     *
     * @since 2.5.0
     *
     * @param string $cookie     Authentication cookie.
     * @param int    $user_id    User ID.
     * @param int    $expiration Authentication cookie expiration in seconds.
     * @param string $scheme     Cookie scheme used. Accepts 'auth', 'secure_auth', or 'logged_in'.
     * @param string $token      User's session token used.
     */
    return apply_filters( 'auth_cookie', $cookie, $user_id, $expiration, $scheme, $token );
}
endif;


if ( !function_exists('wp_parse_auth_cookie') ) :
/**
 * Parse a cookie into its components
 *
 * @since 2.7.0
 *
 * @param string $cookie
 * @param string $scheme Optional. The cookie scheme to use: auth, secure_auth, or logged_in
 * @return array Authentication cookie components
 */
function wp_parse_auth_cookie($cookie = '', $scheme = '') {
    if ( empty($cookie) ) {
        switch ($scheme){
            case 'auth':
                $cookie_name = AUTH_COOKIE;
                break;
            case 'secure_auth':
                $cookie_name = SECURE_AUTH_COOKIE;
                break;
            case "logged_in":
                $cookie_name = LOGGED_IN_COOKIE;
                break;
            default:
                if ( is_ssl() ) {
                    $cookie_name = SECURE_AUTH_COOKIE;
                    $scheme = 'secure_auth';
                } else {
                    $cookie_name = AUTH_COOKIE;
                    $scheme = 'auth';
                }
        }

        if ( empty($_COOKIE[$cookie_name]) )
            return false;
        $cookie = $_COOKIE[$cookie_name];
    }

    $cookie_elements = explode('|', $cookie);
    if ( count( $cookie_elements ) !== 4 ) {
        return false;
    }

    list( $user_id, $expiration, $token, $hmac ) = $cookie_elements;

    $user = get_userdata($user_id);
    $username = ( ! $user ) ? '' : $user->user_login;

    return compact( 'username', 'expiration', 'token', 'hmac', 'scheme' );
}
endif;

My version of these functions replaces user_login with user_id. But it should be a good start for changing it to something even more complex (i.e. user specific hash, or something like this).

  • Nice answer. Though I'll wait until the last day of my bounty period. :) – Anonymous Platypus Jun 11 '15 at 5:54
  • I'm going to accept this, although I won't be testing it as I no longer need this solution. You clearly put a lot of effort in to digging to the root of the system, I appreciate the effort :) – phatskat Jun 12 '15 at 3:11
  • 1
    While this approach is a good one, you should be aware that it affords no more protection. The username is substituted for the user ID, but the username can be gotten from the user ID via a request to example.com?author=123, which performs a canonical redirect to a URL such as example.com/author/john. – John Blackbourn Jun 16 '15 at 0:06
  • 1
    @john read carefully please. I've mentioned, that you can easily make it much more secure storing some random hash in cookie instead of userID. – Krzysiek Dróżdż Jun 16 '15 at 3:52

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.