HTTPS: The Non-Negotiable Base
Why every scheme in this course is worthless without TLS, and what to configure so it actually holds.
Every authentication scheme sends a secret across a network. Over plain HTTP that secret is readable by every router, proxy, and person on the same café Wi-Fi. TLS is not one of the security layers — it is the ground the others stand on.
What TLS gives you
- Confidentiality — the header carrying your token is encrypted.
- Integrity — nobody can rewrite the response body in flight.
- Server authentication — the certificate proves you are talking to
dfg.comand not an impostor.
Notice what it does not give you: it says nothing about who the client is. That is the job of everything else in this course (except mTLS, which extends TLS to prove the client too).
Downgrade is the real attack
An attacker rarely breaks TLS. They stop it from starting. A user types dfg.com, the browser tries http://, and the first request — cookies attached — goes out in the clear before the redirect arrives.
Two headers close that gap:
- HSTS (
Strict-Transport-Security) tells the browser to never use HTTP for this host again. - The cookie
Secureattribute makes the browser refuse to send that cookie over HTTP at all.
Behind a proxy or load balancer
TLS usually terminates at a CDN or load balancer, and your app receives plain HTTP on an internal network. That is fine — but your framework now thinks the connection is insecure and may refuse to set Secure cookies or may build http:// redirect URLs. Configure trusted proxies so X-Forwarded-Proto: https is believed.
Certificates
Use an automated issuer (Let's Encrypt via certbot, or your cloud provider's managed certificate). Manual renewal is how APIs go down at 2am on a bank holiday. For local development, tools like mkcert give you a locally trusted certificate so you can test Secure cookies and SameSite=None without deploying.
Example
# Force HTTPS and pin it for a year, subdomains included
# (add ; preload only when you are ready to be on the browser preload list)
Strict-Transport-Security: max-age=31536000; includeSubDomains
# Verify what a host actually sends
curl -sI https://dfg.com | grep -i strict-transport-security
# Inspect the certificate chain and expiry
openssl s_client -connect dfg.com:443 -servername dfg.com </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates
# Local development certificate that browsers actually trust
mkcert -install
mkcert abc.localhost dfg.localhostWhen to use it
- An API served over HTTP leaks bearer tokens in transit; adding TLS plus HSTS closes both the interception and the first-request downgrade window.
- Cookies stop being set in production because TLS terminates at the load balancer and the framework does not trust X-Forwarded-Proto, so Secure cookies are silently dropped.
- A developer cannot reproduce a cross-site cookie bug locally until mkcert gives both the frontend and API real HTTPS origins.
More examples
Trusting the proxy so Secure cookies survive
Every framework has this switch: Laravel's TrustProxies middleware, Django's SECURE_PROXY_SSL_HEADER, Rails' config.force_ssl.
// Express behind a load balancer / CDN
app.set('trust proxy', 1); // believe X-Forwarded-Proto from 1 hop
app.use((req, res, next) => {
if (!req.secure) { // now accurate behind the proxy
return res.redirect(308, 'https://' + req.headers.host + req.originalUrl);
}
res.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
next();
});
// Without trust proxy, req.secure is false, the redirect loops forever,
// and Secure cookies are never written.The same setting in Laravel
Trust '*' only when the app is genuinely unreachable except through your proxy; otherwise a client could forge the forwarded headers itself.
<?php
// bootstrap/app.php (Laravel 11+)
->withMiddleware(function (Middleware $middleware) {
$middleware->trustProxies(at: '*', headers:
Request::HEADER_X_FORWARDED_FOR |
Request::HEADER_X_FORWARDED_HOST |
Request::HEADER_X_FORWARDED_PORT |
Request::HEADER_X_FORWARDED_PROTO
);
})
// AppServiceProvider::boot() — build every URL as https in production
if (app()->environment('production')) {
URL::forceScheme('https');
}
Discussion