HTTP vs HTTPS, Cookies, Sessions and CORS for API Testers
Three topics confuse new API testers more than anything else: why HTTPS matters when “the API works fine on HTTP”, how a website remembers you are logged in (cookies, sessions or tokens), and the famous red “blocked by CORS policy” error that appears in the browser while the same call works perfectly in Postman. This lesson explains all three with everyday examples and gives you a practical test checklist for each.
Access-Control-Allow-Origin allowing it. Postman and Playwright’s request client are not browsers, so CORS never blocks them.HTTP vs HTTPS (The Postcard vs Sealed Envelope Example)
| HTTP | HTTPS | |
|---|---|---|
| Encryption | None (plain text) | TLS encryption |
| Default port | 80 | 443 |
| Server identity | Not verified | Verified by a certificate from a trusted authority |
| Data integrity | Can be modified in transit | Tampering is detected |
| Browser display | “Not secure” warning | Padlock |
| Required for | Nothing sensitive | Logins, payments, personal data, HTTP/2 in browsers, PWAs |
How HTTPS works (simplified TLS handshake)
- The client says hello and lists the TLS versions and ciphers it supports.
- The server replies with its certificate (containing its public key) signed by a Certificate Authority such as Let’s Encrypt or DigiCert.
- The client checks the certificate: trusted issuer, not expired, matches the domain name.
- Both sides agree on a shared secret session key.
- All HTTP data is now encrypted with that key.
The request and response inside are exactly the same as HTTP; only the transport is protected. That’s why everything you learned about methods, headers and status codes applies unchanged.
How to Test HTTPS for APIs
| Check | How | Expected |
|---|---|---|
| HTTP is redirected or refused | curl -i http://api.example.com/v1/users | 301/308 to HTTPS, or connection refused; never data over HTTP |
| Certificate valid | Browser padlock, curl -v, SSL Labs | Trusted CA, correct domain, not expired |
| Certificate expiry monitored | Check expiry date | Alert at least 30 days before |
| Old protocols disabled | SSL Labs / nmap --script ssl-enum-ciphers | Only TLS 1.2 and 1.3 |
| HSTS | Response header | Strict-Transport-Security: max-age=31536000 |
| No mixed content | Responses with links | All URLs in responses are https:// |
| Self-signed certs in test envs | Client config | Tests don’t disable TLS checks in production code |
# See the TLS handshake and certificate details
curl -v https://sqatools.in/wp-json/sqa-api/v1/health 2>&1 | grep -E "SSL connection|subject|expire|issuer"
Cookies Explained for API Testers
A cookie is a small name-value pair that the server sets with a Set-Cookie response header. The browser stores it and automatically sends it back in a Cookie request header on every later request to that site.
HTTP/1.1 200 OK
Set-Cookie: session_id=8f2a9c1e77; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=1800
GET /account HTTP/1.1
Cookie: session_id=8f2a9c1e77
Cookie attributes you must test
| Attribute | Meaning | Security expectation for session cookies |
|---|---|---|
Secure | Sent only over HTTPS | Must be set |
HttpOnly | JavaScript can’t read it | Must be set (protects against XSS stealing sessions) |
SameSite | Strict, Lax or None: sent on cross-site requests or not | Lax or Strict (protects against CSRF); None requires Secure |
Expires / Max-Age | Lifetime | Short for sessions; none = deleted when browser closes |
Domain / Path | Where it is sent | As narrow as possible |
Sessions vs Tokens (JWT)
| Session (cookie-based) | Token (e.g. JWT Bearer) | |
|---|---|---|
| Where state lives | Server stores the session | Token itself carries the claims (stateless) |
| How it’s sent | Cookie header, automatically by the browser | Authorization: Bearer ..., added by the app |
| Best for | Classic server-rendered websites | Mobile apps, SPAs, microservices, third-party APIs |
| Logout | Delete the server session: instant | Needs short expiry, a deny-list or refresh-token revocation |
| Main risk | CSRF (fixed with SameSite + CSRF tokens) | Token theft from storage; long expiry |
The practice API supports token login with refresh and logout, which you will test in detail in the JWT lesson of Module 3:
https://sqatools.in/wp-json/sqa-api/v1/auth/logincurl -i \
-X POST \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin@123"}' \
"https://sqatools.in/wp-json/sqa-api/v1/auth/login"These are public practice credentials published on the API practice page; never use real credentials on practice systems.
Session and token test cases
- After logout, the old session id or token is rejected (
401). - Session or token expires after the configured idle and absolute timeouts.
- A new session id is issued after login (prevents session fixation).
- Session ids are long and random, not sequential.
- Changing the password invalidates other sessions.
- Tokens never appear in URLs or logs.
What Is CORS? (And Why Postman Works When the Browser Fails)
Browsers follow the Same-Origin Policy: JavaScript on one origin (scheme + host + port) can’t read responses from another origin unless that server explicitly allows it. CORS is the mechanism a server uses to say “these other origins may read my responses”.
| Page origin | API URL | Same origin? |
|---|---|---|
https://shop.com | https://shop.com/api/cart | Yes |
https://shop.com | https://api.shop.com/cart | No (different host) |
https://shop.com | http://shop.com/api | No (different scheme) |
http://localhost:3000 | http://localhost:8080/api | No (different port) |
Access-Control-Allow-Origin: https://shop.com). Postman is not a guard: it walks straight in. That’s why a call that works in Postman can fail in the browser.Simple vs pre-flighted requests
For “non-simple” requests (PUT, DELETE, PATCH, custom headers like Authorization, or JSON content type), the browser first sends an automatic OPTIONS pre-flight:
OPTIONS /api/cart HTTP/1.1
Origin: https://shop.com
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: authorization, content-type
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://shop.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 600
Only if the pre-flight approves does the browser send the real DELETE.
CORS response headers
| Header | Purpose |
|---|---|
Access-Control-Allow-Origin | Which origin may read the response (one origin or *) |
Access-Control-Allow-Methods | Allowed methods for pre-flighted requests |
Access-Control-Allow-Headers | Allowed custom request headers |
Access-Control-Allow-Credentials | Whether cookies/auth may be included |
Access-Control-Expose-Headers | Which response headers JavaScript may read (e.g. Location, ETag) |
Access-Control-Max-Age | How long the pre-flight result can be cached |
The SQA Tools practice API is CORS-enabled and exposes headers such as Location, ETag, Retry-After and X-Total-Count, which is why the “Send request” boxes in these lessons can show them.
CORS test cases
- Allowed origin: response contains the matching
Access-Control-Allow-Origin. - Unknown origin (e.g.
https://evil.example): no allow-origin header for it. - Security bug: the server reflects any
Originback and sendsAllow-Credentials: true. Any website can then read logged-in users’ data. Access-Control-Allow-Origin: *combined with credentials is invalid and must not be used.- Pre-flight OPTIONS returns 2xx with the right methods and headers.
- Headers the frontend needs (Location, ETag, pagination) are exposed.
# Simulate a browser pre-flight from another origin
curl -i -X OPTIONS "https://api.example.com/v1/cart" \
-H "Origin: https://evil.example" \
-H "Access-Control-Request-Method: DELETE"
# PASS if the response does NOT allow https://evil.example
Playwright: Checking Cookies, HTTPS and CORS Headers
import { test, expect } from '@playwright/test';
test('API is served only over HTTPS', async ({ request }) => {
const res = await request.get('https://sqatools.in/wp-json/sqa-api/v1/health');
expect(res.url()).toMatch(/^https:\/\//);
expect(res.ok()).toBeTruthy();
});
test('session cookie has secure attributes', async ({ request }) => {
// Replace with your application's login endpoint
const res = await request.post('https://your-app.test/api/login', {
data: { username: 'qa_user', password: process.env.QA_PASSWORD },
});
const setCookie = res.headersArray().filter(h => h.name.toLowerCase() === 'set-cookie');
const session = setCookie.find(h => h.value.startsWith('session_id='));
expect(session?.value).toContain('Secure');
expect(session?.value).toContain('HttpOnly');
expect(session?.value).toMatch(/SameSite=(Lax|Strict)/i);
});
test('CORS does not trust random origins', async ({ request }) => {
const res = await request.fetch('https://your-app.test/api/cart', {
method: 'OPTIONS',
headers: { Origin: 'https://evil.example', 'Access-Control-Request-Method': 'DELETE' },
});
expect(res.headers()['access-control-allow-origin']).not.toBe('https://evil.example');
});
Use headersArray() for Set-Cookie because a response can contain several cookies with the same header name. Keep real passwords in environment variables, never in test code.
Common Mistakes
- Reporting a CORS error as an “API down” bug. Check the browser console and the response headers first; the API usually works in Postman.
- “Fixing” CORS with
Access-Control-Allow-Origin: *on an API that uses cookies. - Disabling certificate checks (
ignoreHTTPSErrors,curl -k) in production tests. - Storing JWTs in
localStoragewithout considering XSS risk. - Not testing what happens after logout or expiry.
Interview Questions: HTTPS, Cookies and CORS
What is the difference between HTTP and HTTPS?
HTTPS is HTTP over TLS. It encrypts data in transit, verifies the server’s identity with a certificate and protects integrity against tampering. HTTP sends everything in plain text. HTTPS uses port 443 by default, HTTP uses port 80.
What is a TLS certificate and what do you test about it?
A digital certificate issued by a trusted Certificate Authority that binds a public key to a domain. Testers check that it is trusted, matches the domain, is not expired or close to expiry, and that only modern TLS versions (1.2 and 1.3) are enabled.
What is CORS?
Cross-Origin Resource Sharing is a browser mechanism that lets a server allow web pages from other origins to read its responses, using headers like Access-Control-Allow-Origin. Without it, the browser’s Same-Origin Policy blocks cross-origin reads.
Why does an API call work in Postman but fail in the browser with a CORS error?
CORS is enforced only by browsers. Postman, cURL and server-side clients don’t apply the Same-Origin Policy. The fix is on the server: return the correct CORS headers for the allowed origins.
What is a pre-flight request?
An automatic OPTIONS request the browser sends before a non-simple cross-origin request (for example PUT, DELETE or one with an Authorization header) to ask whether the method, headers and origin are allowed.
What are HttpOnly, Secure and SameSite cookie attributes?
HttpOnly stops JavaScript from reading the cookie, reducing XSS session theft. Secure sends the cookie only over HTTPS. SameSite controls whether the cookie is sent on cross-site requests, protecting against CSRF.
What is the difference between session-based and token-based authentication?
Session-based auth stores state on the server and identifies the user with a session id cookie. Token-based auth gives the client a signed token (often a JWT) that carries the claims and is sent in the Authorization header; the server can validate it without storing a session.
What CORS misconfiguration is a security risk?
Reflecting any Origin header back in Access-Control-Allow-Origin while also sending Access-Control-Allow-Credentials: true. It lets any malicious website make authenticated requests and read the victim’s data.
What is HSTS?
HTTP Strict Transport Security is a response header that tells browsers to use only HTTPS for the domain for a set time, preventing downgrade and SSL-stripping attacks.
How do you test session management in APIs?
Verify the session or token is rejected after logout and expiry, a new id is issued at login, ids are random, cookies have Secure, HttpOnly and SameSite, password change invalidates other sessions and tokens are never exposed in URLs or logs.