38

Is there a way to check if the current page was opened with SSL? For example, I want my login page (login.php) to check if it was accessed using SSL (https://mywebserver.com/login.php). If not, redirect them to the SSL version of the page.

Pretty much, I want to enfore that the user uses the page securely.

Spidy
  • 39,723
  • 15
  • 65
  • 83
  • possible duplicate of [How can I prevent access to PHP files if the caller isn't using HTTPS?](http://stackoverflow.com/questions/3729497/how-can-i-prevent-access-to-php-files-if-the-caller-isnt-using-https) – Bruno Feb 13 '12 at 22:01
  • possible duplicate of [How To Find Out If You are Using HTTPS Without $\_SERVER\['HTTPS'\]](http://stackoverflow.com/questions/1175096/how-to-find-out-if-you-are-using-https-without-serverhttps) – Dave Jarvis Nov 21 '13 at 18:28

9 Answers9

57

You should be able to check that $_SERVER['HTTPS'] is set, e.g.:

if (empty($_SERVER['HTTPS'])) {
    header('Location: https://mywebserver.com/login.php');
    exit;
}
Long Ears
  • 4,886
  • 1
  • 21
  • 16
35

Be careful. On my IIS server, $_SERVER['HTTPS'] is not empty but has the value 'off'.

So i had to do

if (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != 'on') {
    // no SSL request
}
  • 1
    this is probably the best thing to do, better add strict checking too... `$_SERVER['HTTPS'] !== 'on'` – Florian F Mar 25 '15 at 20:36
  • I personally do not perform this check because I am sure my script will never run on Microsoft IIS. I think this applies to most users having a standard webspace. – zomega Jan 22 '23 at 17:16
9

You'll find this may not work if you are working over forwarded protocols. For example, Amazon's ELB can handle SSL negotiation and interact with your app servers over port 80.

This block handles that:

    public function isSSL()
    {
        if( !empty( $_SERVER['https'] ) )
            return true;

        if( !empty( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' )
            return true;

        return false;
    }
Saeven
  • 2,280
  • 1
  • 20
  • 33
  • 6
    IIS returns `$_SERVER['HTTPS'] = "off"` when there's no SSL so you have to use `if (!empty( $_SERVER['HTTPS'] && $_SERVER['HTTPS'] != 'off')` to make sure it works on IIS ;) – Wh1T3h4Ck5 Feb 23 '13 at 14:16
  • This may also be the value used: `$_SERVER['HTTP_X_FORWARDED_PORT'] == '443'` – Nick Johnson Apr 09 '14 at 15:40
  • @Wh1T3h4Ck5, That's a bug they need to fix. Not our code. – Pacerier Mar 06 '15 at 00:08
  • @Pacrerier It's a bug *you* need to fix, if you support IIS. Or not, if you don't. Nothing in the question indicates that IIS is unsupported, therefore without the amendment by Wh1T3h4Ck5, this is not a complete answer as it will give the wrong result in some situations. – HappyDog Jul 29 '20 at 11:30
9

Well, Here is another chunk of code. The code will return full url with https/http.

<?php

/**
 * Check whether URL is HTTPS/HTTP
 * @return boolean [description]
 */
function isSecure()
{

    if (
        ( ! empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
        || ( ! empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')
        || ( ! empty($_SERVER['HTTP_X_FORWARDED_SSL']) && $_SERVER['HTTP_X_FORWARDED_SSL'] == 'on')
        || (isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443)
        || (isset($_SERVER['HTTP_X_FORWARDED_PORT']) && $_SERVER['HTTP_X_FORWARDED_PORT'] == 443)
        || (isset($_SERVER['REQUEST_SCHEME']) && $_SERVER['REQUEST_SCHEME'] == 'https')
    ) {
        return true;
    } else {
        return false;
    }

}
/**
 * Example Use
 */
define('APP_URL', (isSecure() ? 'https' : 'http') . "://{$_SERVER['SERVER_NAME']}".str_replace(basename($_SERVER['SCRIPT_NAME']),"",$_SERVER['SCRIPT_NAME']));
echo APP_URL;


/**
 * +++++++++++++++++++++++++
 * OR - One line Code
 * +++++++++++++++++++++++++
 */
define('APP_URL', ((( ! empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || ( ! empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') || ( ! empty($_SERVER['HTTP_X_FORWARDED_SSL']) && $_SERVER['HTTP_X_FORWARDED_SSL'] == 'on') || (isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443) || (isset($_SERVER['HTTP_X_FORWARDED_PORT']) && $_SERVER['HTTP_X_FORWARDED_PORT'] == 443) || (isset($_SERVER['REQUEST_SCHEME']) && $_SERVER['REQUEST_SCHEME'] == 'https') ) ? 'https' : 'http') . "://{$_SERVER['SERVER_NAME']}".str_replace(basename($_SERVER['SCRIPT_NAME']),"",$_SERVER['SCRIPT_NAME']));
echo APP_URL;

?>
Nono
  • 6,986
  • 4
  • 39
  • 39
  • great answer! Caveat: `$_SERVER['HTTP_HOST'] ` instead of `${_SERVER['SERVER_NAME']}` .. is better since it reflects what the client requested, including the port number. – Otheus Apr 29 '22 at 05:46
3

Another method is to check for the existence of HTTPS cookies. First your server needs to send the browser a cookie with the secure flag:

Set-Cookie:some_key=some_value;secure

After your server has sent the browser the cookie, whenever the browser requests a page from your server, it will send along the secure cookie some_key=some_value only if it is requesting a HTTPS page. This means that if you see the existence of the cookie some_key=some_value you know that the browser is requesting a HTTPS page. Voila!

Browser support is very good, as this is fundamental to security. Browsers without support for HTTPS cookies are Firesheepable when users request pages from non-HSTSed domains.

For more info, see:

Community
  • 1
  • 1
Pacerier
  • 86,231
  • 106
  • 366
  • 634
  • Can this not be spoofed on the client-side, i.e. by manually adding the cookie into a non-secure page? – HappyDog Jul 29 '20 at 11:33
  • I don't like using secure cookies to find out if a page is secured with TLS. The server may have set the cookie with the secure flag, however on receipt of the cookie from the client there is no way to tell if the connection is in fact secure or that the secure flag was indeed set. All you get from the client is a name-value pair. And absence of the cookie also in no way means that TLS is not enabled. – PHP Guru Mar 25 '23 at 17:24
3
<?php
if ( !empty( $_SERVER['HTTPS'] ) ) {
  //do secure stuff
}else{
  //warn or redirect or whatever
}
?>

http://php.net/manual/en/reserved.variables.server.php

Farray
  • 8,290
  • 3
  • 33
  • 37
1

Just to add that in case of nginx, the way to check for https is:

if (isset($_SERVER['SERVER_PORT']) &&
        ($_SERVER['SERVER_PORT'] === '443')) {
    return 'https';
}
koninos
  • 4,969
  • 5
  • 28
  • 47
  • well while that works for many cases, this will fail if your HTTPS is NOT on 443... – My1 Nov 06 '15 at 10:42
  • === '443' looks wrong - it's checking for a type match also, but providing a string value for an integer. – Q Studio Jan 07 '16 at 10:02
  • @QStudio `=== '443'` is actually correct in this case if you evaluate `gettype($_SERVER['SERVER_PORT']) == 'string'` – Matt Borja Jun 25 '16 at 05:45
  • @My1 This solution also "fails" if your SSL connection is terminated anywhere before reaching its final destination (i.e. reverse proxies, load balancer, and even [CloudFlare's Flexible SSL](https://www.cloudflare.com/ssl/) option). In and of itself, however, it doesn't actually fail and could even be used to do a sanity check to verify end-to-end "Full SSL" connection. – Matt Borja Jun 25 '16 at 05:48
0

To use PHP to check if the page was accessed without SSL you can check the port number.

// Most encrypted web sites use port 443
if ($_SERVER['SERVER_PORT']==443) {
    // Tell browser to always use HTTPS
    header('strict-transport-security: max-age=126230400');
}
elseif (isset($_SERVER['SERVER_PORT'])) {
    // Redirect current page to https with 301 Moved Permanently response
    header('location: https://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'], true, 301);
    exit;
}

This assumes your server is configured with the SERVER_PORT environment variable and that the encrypted version of your web site is hosted on port 443. It also assumes your server is not behind a load balancer. If your server is behind a load balancer, you might need a more advanced solution such as this one that does not rely on custom HTTP headers which can vary from one load balancer to the next:

// Set secure cookie to detect HTTPS as cookie will not exist otherwise.
header('set-cookie: __Secure-https=1; expires='.substr(gmdate('r', ($_SERVER['REQUEST_TIME']?: time())+126230400), 0, -5).'GMT; path=/; secure', false);
// Tell browser to always use HTTPS
header('strict-transport-security: max-age=126230400');
if (!isset($_COOKIE['__Secure-https']) && !isset($_GET['https'])) {
    // Redirect to secure version of site and add https=1 GET variable in case cookies are blocked
    header('location: https://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'].(strpos($_SERVER['REQUEST_URI'], '?')===false? '?': '&').'https=1', true, 307);
    exit;
}

If the above solution is problematic because it adds ?https=1 to your URL then you can always use JavaScript. Add this to the top of your page right after <head>:

<script>
// This will redirect all requests from http to https
if (location.protocol=='http:') {
    location.replace('https://'+location.host+location.pathname+location.search)
    document.write('<noscript>');// hack to stop page from displaying
}
</script>

Then add the following to your PHP script if you want browsers to remember to always use HTTPS when accessing your site:

header('strict-transport-security: max-age=126230400');

or if you want browsers to have your preferences preloaded use:

header('strict-transport-security: max-age=126230400; preload');// HTTPS will always be used!

If you use the preload feature you will need to submit your web site to be included in Chrome's HSTS preload list so that browsers come preloaded with your web site preferences. If you use preload, it's also advisable to host your site on the naked domain without the www. This is because it's usually easier for most people to type in your domain without the www, and with preload your web site loads without the need of a tedious redirect since https is already the default.

PHP Guru
  • 1,301
  • 11
  • 20
0

Detect server side SLL with some additions:

function detectSSL(): ?bool {

    // check HTTPS protocol
    if( isset($_SERVER['HTTPS']) ) {

        if( 'off' !== strtolower($_SERVER['HTTPS']) ) {

            return true;

        }

        if( 1 === (int)$_SERVER['HTTPS'] ) {

            return true;

        }

    }

    if( isset($_SERVER['HTTP_X_FORWARDED_SSL']) ) {

        if( 'on' === $_SERVER['HTTP_X_FORWARDED_SSL'] ) {

            return true;

        }

    }

    if( isset($_SERVER['HTTP_X_FORWARDED_PORT']) ) {

        if( 443 === (int)$_SERVER['HTTP_X_FORWARDED_PORT'] ) {

            return true;

        }

    }

    if( isset($_SERVER['HTTP_X_FORWARDED_PROTO']) ) {

        if( strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) === 'https' ) {

            return true;

        }

    }

    if( isset($_SERVER['REQUEST_SCHEME']) ) {

        if( strtolower($_SERVER['REQUEST_SCHEME'] === 'https') ) {

            return true;

        }

    }

    // check server port
    if( isset($_SERVER['SERVER_PORT']) ) {

        if( 443 === (int)$_SERVER['SERVER_PORT'] ) {

            return true;

        }

    }

    // non-SSL
    return null;

}

// Set URI prefix
define('uri_prefix', detectSSL() ? 'https://' : 'http://');

define('site_host', strtolower(uri_prefix . $_SERVER['HTTP_HOST']));

And addition for .htaccess:

# SSL schema off
RewriteCond %{HTTPS} off

RewriteRule .* - [E=REQUEST_SCHEME:http]

# SSL schema
RewriteCond %{HTTPS} on

RewriteRule .* - [E=REQUEST_SCHEME:https]