@jialin.huang
FRONT-ENDBACK-ENDNETWORK, HTTPOS, COMPUTERCLOUD, AWS, Docker
To live is to risk it all Otherwise you are just an inert chunk of randomly assembled molecules drifting wherever the Universe blows you

© 2024 jialin00.com

Original content since 2022

back
RSS

Frontend Server-Side Calls and CORS

WHY

When dealing with sensitive information such as login or account-related matters, including sessions and tokens, one might wonder how to hide this information instead of having it visible in the browser's console. A common approach is to avoid using Client API and instead utilize the NextJS Server functionality provided by the NextJS framework. For example, you can define dedicated server-side code in the actions/ directory to handle these sensitive operations.

To put it simply, instead of calling client APIs (e.g. useSWR) in client components, you call server-side code, asking your frontend's backend NextJS Server to make requests to the backend on your behalf.

The advantage of this approach is that the requests won't show up when debugging using dev tools. To debug these operations, we need to check the server's log output, which could be in the shell terminal or elsewhere.

I discovered that "CORS issues occur when sending requests from client API browsers, but there's no problem when the NextJS Server makes the requests

The Broad Concept of Client

When discussing CORS and API calls, it's important to ensure a consistent understanding of what "client" means. It doesn't just refer to browsers, but to any request initiator relative to the backend API server. This can include:

  • Traditional web browsers
  • API testing tools (like Postman)
  • CLI (like curl)
  • Server-side code (like functions in the actions/ directory of NextJS)

CORS

CORS is a self-protection mechanism of the browser. Its purpose is to prevent users from unknowingly sending requests to unknown sources or exposing sensitive information to unauthorized recipients. Therefore, CORS only constrains browser environments and does not affect NextJS Server applications or common API testing tools (e.g., Postman, Insomnia).

This explains why we often see "Backend says it's good to go, but frontend's still stuck. They swear it works in Postman!"

CORS-related Headers

client’s (a.k.a browser)

shortcut: ACR-[name] for Access-Control-Request

  1. Origin: Where it comes (protocol + domain + port)

    preflight/OPTIONS both need to have this header

  1. ACR-Method
  1. ACR-Headers

Server’s

Access-Control-Allow as ACA

  1. ACA-Origin: Manages the domain whitelist that can access the server
    • If your current website's request is not accepted by the server, meaning your browser origin is not in the server's whitelist, the browser check will tell you

      "Hey, you can't see this"
  1. ACA-Methods: HTTP methods (like GET, POST, PUT, DELETE, etc.)
    • No need to include Options
  1. ACA-Headers: Specifies allowed headers

    e.g. ACA-Headers: Content-Type, Authorization, X-Requested-With

  1. ACA-Credentials: Whether cookies can be sent.
  1. Access-Control-Max-Age: Tells how long the browser can remember the "Can I?" answer.
    • For a specific endpoint, not for all endpoints under the entire server

Preflight/OPTIONS Request Response Steps

💡

"Preflight" is a term describing the purpose of this request, while OPTIONS is the HTTP method implementing this purpose
preflight is the concept, options is the practice

  1. When the browser sees a fancy request (not just GET/POST/HEAD), it first sends a "May I?" request (OPTIONS) to check if the server's cool with it.
    1. Simple requests: No needed preflight, they can be sent directly. If failed, you'll receive a response with a 200 status but with a CORS error, and the browser will refuse to read the result with Javascript. (I remember Firefox and Chrome react slightly differently to this situation, but both indeed have CORS errors)
      The characteristic of simple requests is that they don't cause effects on the server, so they are allowed to be sent directly
    1. Non-simple requests: Require preflight. If the preflight fails, the actual PUT/DELETE request won't be sent
  1. This request includes
    1. Access-Control-Request-Method
    1. Access-Control-Request-Headers
  1. The server responds to the OPTIONS request, including the corresponding CORS headers.
    • If the server agrees, the browser will then send the actual request
    • Access-Control-Allow-Origin: Must include the Origin of the request, or be *
    • Other related Access-Control-* headers, set according to actual needs

Bad Website and Bank without CORS

Scenario

  1. You are logged in to Bank Y (no CORS setup).
  1. At the same time, you accidentally browse the malicious website evil.com.
  1. The malicious website uses your bank cookies to send a transfer request, a CSRF attack.

CORS can't stop CSRF attacks, but it can stop attackers from reading the bank's response.

CSRF Attack

The transfer might success, but here's the key difference:

Without CORS

  • Bank processes the request and sends back: "Transfer successful, balance: $5,000"
  • Evil script can read this response completely
  • Attacker now knows: attack worked + your account balance + can plan more attacks

With CORS

  • Bank has a whitelist that doesn't include evil.com
  • Transfer might still happen, but evil script gets blocked from reading any response
  • Attacker is blind - doesn't know if attack worked or how much money you have
  • This turns a "precise attack" into a "blind attack"

CORS doesn't prevent requests, but prevents responses from being read.

CSRF Prevention

  • CSRF tokens
  • Check the Referer header
  • Use SameSite cookie attribute
  • Use HttpOnly and Secure cookie flags

Bad Script vs. Bank without CORS

Scenario:

  1. You’re browsing a trusted site, trusted-site.com.
  1. trusted-site.com loads a script from ad-scripts.com, which looks normal but actually has bad intentions.
  1. This bad script tries to send a transfer request to bank.com/transfer. The browser automatically includes your bank cookies with the request.

Without CORS

The bank sees your cookies and accepts the request as valid, responding accordingly. The bad script (ad-scripts.com) can then freely read the bank’s response, stealing sensitive data or causing harm.

With CORS:

The browser checks the CORS headers and sees that trusted-site.com is not allowed by the bank:

  • For simple requests, No preflight (OPTIONS) is sent, but the browser blocks JavaScript from reading the response due to missing/incorrect CORS headers.
  • For non-simple requests: The preflight OPTIONS request fails because the bank's CORS policy doesn't allow trusted-site.com, so the actual request is blocked completely.

Conclusion

  1. ad-scripts.com (bad guy)
  1. trusted-site.com (kind but dumb)
  1. bank.com/transfer (protected by CORS)

CORS Alone Is Not Enough!

In the above scenario, if trusted-site.com itself is not secure enough, and the bank server still puts trusted-site.com on the whitelist, then CORS is useless. CORS only recognizes where the incoming request is from, but doesn't know that there are many bad scripts within the request source.

CORS only recognizes place, not the bad buildings built on that place

Solutions:

  1. Strictly limit the CORS origin list aka whitelist
  1. OAuth or API
  1. Content Security Policy (CSP) header setup
    1. CSP was designed to protect against XSS attacks
  1. Subresource Integrity (SRI)
    1. Prevents CDN tampering

    https://imququ.com/post/subresource-integrity.html

  1. Principle of least privilege

Some Thoughts

Thought 1

While browsers have CORS to protect cross-origin requests, other clients like mobile apps or server-to-server communications need different security solutions.

In these cases, the party sending the request and the server share a secret key:

  1. The request sender uses the secret key to generate a signature by combining parts of the request (such as URL, timestamp, and body) and encrypting them (e.g., with HMAC-SHA256).
  1. The server recalculates the signature using the same secret key and algorithm, then compares it to the signature sent in the request.
  1. Security:
    • Without the secret key, attackers cannot forge a valid signature.
    • Any change to the request will cause a signature mismatch, preventing tampering.

Use cases include API request verification, payment systems, and file upload verification.

Request signing provides stronger security than simple tokens, especially to prevent request tampering.

Thought 2

What if: it's a simple request (no preflight), but the server still doesn't return the required CORS headers?

Truth is: It doesn’t matter whether the request is "simple" or not — if the server doesn’t include the right CORS headers, the browser will block JavaScript from accessing the response.

Even if the network request technically succeeds (e.g., you see a 200 OK in the Network tab), you’ll still get a CORS error in the console. The browser sees that your origin isn’t allowed and prevents access to the response (e.g., via xhr.responseText or fetch's response object).

CORS doesn’t block the request itself. It blocks JavaScript from reading the response.

Thought 3

If a badly configured server enables CORS for everyone, the browser will allow cross-origin access. Doesn't that defeat the purpose of CORS as a protection mechanism?

Not really. The browser is doing its job correctly. The real problem is that the bad server is exposing sensitive data to the world.

Thought 4

Do you need Access-Control-Allow-Credentials: true?

  • cookies or sessions: If your site uses cookies or sessions, you need this header to allow browsers to send cookies on cross-origin requests.
    • But using this header means you must be careful with security, because browsers will send cookies automatically, which can be risky if not set up properly (e.g., CSRF attacks).
  • Bearer Token : If you use bearer tokens, you don’t need this header because tokens aren’t sent automatically by the browser.

https://sentry.io/answers/why-does-my-javascript-code-receive-a-no-access-control-allow-origin-header-error-while-postman-does-not/

https://stackoverflow.com/questions/19743396/cors-cannot-use-wildcard-in-access-control-allow-origin-when-credentials-flag-i

EOF