WordPress – SSL login page without SSL admin

This post was published 2 years, 6 months ago. Due to the rapidly evolving world of technology, some concepts may no longer be applicable.

In WordPress (v3.3), FORCE_SSL_LOGIN will transmit login credentials over SSL, however, the login page itself may be accessed over HTTP (as opposed to HTTPS). If the login page is accessed via HTTP, the user has no (easy) way of verifying that the page is legitimate and that the credentials will in fact be sent over SSL (i.e. the address bar doesn’t display the ‘https’ and ‘lock symbol’). If one is also using FORCE_SSL_ADMIN, this problem is easily solved – simply access your login page over SSL.

I do not want an SSL admin page (FORCE_SSL_ADMIN=false), but want to access the login page via SSL (i.e. actually have the URL start with https – not just send the credentials over SSL), and this is where you run into a bit of a problem. If the login page is accessed over HTTPS, a successful login redirects to the SSL admin section and sets a secure cookie.

To briefly go over what occurs:

wp-login.php determines where to ‘redirect’ the page after the login ($redirect_to). If no redirect is specified in the URL, you should end up on the admin section. However, if the login page is accessed via SSL, the redirect points to the SSL version of the admin page (this is easy enough to resolve with a web server redirect). The problem is that WordPress assumes that the admin section will be accessed only via SSL and sets a secure cookie. If you set a secure cookie, the cookie cannot be accessed from a non SSL page.

Specifically, the function admin_url() will set $redirect_to to the SSL admin page if is_ssl() is true (and it is true if the login page is accessed via SSL). Deciding whether to disable the secure cookie is done in the following if statement (lines 565 and 566):

565
566
if (!$secure_cookie && is_ssl() && force_ssl_login() && !force_ssl_admin() && ( 0 !== strpos($redirect_to, 'https') ) && ( 0 === strpos($redirect_to, 'http') ) )
$secure_cookie = false;

Since admin_url() starts with https, the last two conditions evaluate to false, and $secure_cookie remains an empty string. $secure_cookie is passed to wp_signon(), which will then use the value of is_ssl() and set a secure cookie.

There are two easy solutions to this, unfortunately, both require editing wp-login.php:

  1. Change line 537 (568 for WP 3.4; 565 for WP 3.4.1; 574 for WP 3.5) of wp-login.php:
    From: $secure_cookie = '';
    To: $secure_cookie = false;
  2. Explictly request admin_url() to return the non SSL url by changing line 557 (589 for WP 3.4; 588 for WP 3.4.1; 597 for WP 3.5) of wp-login.php:
    From: $redirect_to = admin_url();
    To: $redirect_to = admin_url('','http');
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
case 'login' :
default:
        $secure_cookie = '';
        $interim_login = isset($_REQUEST['interim-login']);
 
        // If the user wants ssl but the session is not ssl, force a secure cookie.
        if ( !empty($_POST['log']) && !force_ssl_admin() ) {
                $user_name = sanitize_user($_POST['log']);
                if ( $user = get_userdatabylogin($user_name) ) {
                        if ( get_user_option('use_ssl', $user->ID) ) {
                                $secure_cookie = true;
                                force_ssl_admin(true);
                        }
                }
        }
 
        if ( isset( $_REQUEST['redirect_to'] ) ) {
                $redirect_to = $_REQUEST['redirect_to'];
                // Redirect to https if user wants ssl
                if ( $secure_cookie && false !== strpos($redirect_to, 'wp-admin') )
                        $redirect_to = preg_replace('|^http://|', 'https://', $redirect_to);
        } else {
                $redirect_to = admin_url();
        }

In my case, I prefer the second solution, as it saves on one redirect.

Finally, to force the login URL to always be over SSL, and all other paths to not be over SSL, some nginx rewrite rules:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
server {
#SSL server
	listen *:443 ssl;
	#ssl_certificate, ssl_certificate_key, server_name, listen, root, logs, etc
 
	location = /wp-login.php {
		include /etc/nginx/fastcgi_params;
		fastcgi_pass 127.0.0.1:XXXX;
	}
 
	location / {
		rewrite ^ http://$host$request_uri permanent;
	}
}
 
server {
#Non-SSL server
        #server_name, listen, root, logs, etc
 
        rewrite ^/wp-login.php$ https://$host/wp-login.php;
 
	##other directives
}

13 thoughts on “WordPress – SSL login page without SSL admin

  1. That’s a nice trick to enable only the login attempts via SSL. Currently I enabled SSL for all the administration that is not necessary for me too.

    The code could be improved a little to make it work for any domain. For example, the first rewrite (inside SSL server) could be…

    rewrite ^ http://$host$request_uri permanent;

    And the second rewrite (inside Non-SSL server) could be…

    rewrite ^/wp-login.php$ https://$host$request_uri;
    • Glad you found it useful. Thanks for the suggestion – I will make the recommended change. Using $host is definitely more versatile. The Non-SSL server though, needs to end in /wp-login.php. (It is worth noting that for unusual setups that use a port number, $host wouldn’t work, but in general, it is better).

      • Thanks for adding my tip. I’m no expert in Nginx. I thought $host was better. I highly appreciate your listening skill. :)

        I wish there is a way to subscribe to the comments here. I came back here after a long time (after seeing you in ServerFault). It’s hard to believe that your day is “completely unrelated to technology”. Anyway, thanks for all you do there. I’ve already to all your comment in SF, though. :) You have amazing insights on AWS, Nginx, PHP-FPM, etc.

        • You should be able to subscribe to comments actually – there is a comments feed, just no icon for it. Most RSS readers should pick up on it (it is a standard WordPress feed). I tried to keep things simpler and uncluttered – and figured that one RSS icon was enough – I might have to reconsider that.

          As for my day – I hardly see a computer at work – and their use is mostly for presentations or word processing. Helping people out, on the other hand, is quite integral to my work, and is something I enjoy. Combine that with some problem solving and you can see why I enjoy SF.

          • Thanks for the link to comments feed. I’d better subscribe to all the comments in this site than subscribing to only one post. Thanks again for your time to help me.

    • Exactly right, if you want what the above article is proposing. You do not want to access the admin page over SSL – so we set that to false (FORCE_SSL_ADMIN=false). We do want the login to be over SSL – so we set that to true (FORCE_SSL_LOGIN=true).

    • I don’t use WP Multisite, (so I am not familiar with how it works), on a single site (e.g. this one), the following happens on logout:

      http://www.thatsgeeky.com/wp-login.php?action=logout&_wpnonce=xxxxxxxxxx :
      302 redirect: https://www.thatsgeeky.com/wp-login.php?action=logout&_wpnonce=xxxxxxxxxx
       
      https://www.thatsgeeky.com/wp-login.php?action=logout&_wpnonce=xxxxxxxxxx :
      302 redirect: wp-login.php?loggedout=true
       
      ...and we end up on: https://www.thatsgeeky.com/wp-login.php?loggedout=true

      You can see that WordPress pointed to a non-HTTPS URL, but nginx redirected it.

      I would expect the same thing to happen on the main site of a WP-multisite setup (i.e. if you access http://example.com/wp-login.php – it should redirect to https regardless of the query parameters). Now, if you have a username in there, you probably need to modify the location block:

      location /wp-login.php {}

      becomes:

      location ~* /wp-login.php$ {}

      (Note: you might have to escape that forward slash – location ~* \/wp-login.php$)

      The difference is that the ~* makes it a regex match (instead of looking at the start of the url). So, /wp-login.php$ says anything ending in (the $ sign) /wp-login.php. (You could use ~ instead of ~* – the former is case-sensitive, the latter, case-insensitive).

      The server block I currently use is a bit different than the ones above. For HTTPS, I have:

      server {
      	listen *:443 ssl;
      	ssl_certificate /path/to/thatsgeeky.com.crt;
      	ssl_certificate_key /path/to/thatsgeeky.com.key;
              server_name thatsgeeky.com www.thatsgeeky.com;
      	root   /path/to/thatsgeeky.com/web;
      	error_log /path/to/thatsgeeky.com/error.log;
      	access_log /path/to/thatsgeeky.com/access.log main;
       
      	location = /wp-login.php {
      		include /etc/nginx/fastcgi_params;
      		fastcgi_pass 127.0.0.1:9011;
      	}
       
      	location / {
      		if ($http_referer !~ ^(https://www.thatsgeeky.com/) ){
      			rewrite ^ http://www.thatsgeeky.com$request_uri permanent;
      		}
      	}
      	location = / {
      		rewrite ^ http://www.thatsgeeky.com permanent;
      	}
      }

      For my main server block, there is only one line relevant to the login page:

      rewrite ^/wp-login.php$ https://www.thatsgeeky.com/wp-login.php;
  2. Excellent article, just what i was looking for. However, i’m unable to get this to work. I believe it may be due to the fact that i’m trying to use a shared SSL from bluehost.com and had to set my Website URL to secure.bluehost.com/~polishp2/blog which is the path my blog is installed. I’m able to get an SSL login page at https://secure.bluehost.com/~polishp2/blog/wp-login.php but it only works with HTTP for the admin section. I cannot use that session to administer via HTTP. This is an issue since the host limits data passed via SSL and you can’t view all media. Any ideas?

    • Glad you found it useful. By ‘it only works with HTTP for the admin section’ do you mean that a login from HTTPS does not take you to the admin section (as in it just redirects you back to the login page)? Did you try the fully WordPress approach (that is, both the login page and the admin page being SSL – i.e. FORCE_SSL_LOGIN=true and FORCE_SSL_ADMIN=true)? The approach proposed here builds on the functionality established by those parameters, so if the default approach does not work for you, neither will the approach taken by this article. Offhand, it sounds like either you are using a secure cookie (set by $secure_cookie and only usable over HTTPS (so not accessible to the HTTP session)), or that the page is redirecting to the wrong location. Hope it works out for you.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>