Articles and Libraries
General notes
Cross-origin requests, are resource-scoped. In REST terms, the server can
decide whether to allow cross-origin requests to a particular API or not. That
capability is naturally exposed by the Go middleware above - and you can wrap
any handlers (which correspond to paths) you have for which you want to enable
CORS. In python frameworks, a @cors(...)
decorator is more conventional.
Once you’re familiar with the idea of CORS being resource scoped, enabling CORS “globally” or “fixing CORS issue” will strike you as anti-patterns. For example you might not want to enable cross-origin requests to your graphql playground path, obviously. Or any API not intended for your browser app to access.
Sending and receiving cookies with CORS
While developing a web-app, I want to have the app served from the dist/
directory and all API calls will be driven by a dedicated server written in Go.
In local, the server listens at localhost:8080
and the
Vite app is served from localhost:5173
by Vite’s
builtin server. Doesn’t matter what bundle/build tool you use, of course.
The login API at http://localhost:8080/login
will receive form data like email
and password and create an auth-token 1 and respond with status
200
along with Set-Cookie:...
response header. The cookie needs to have its
SameSite
attribute set to None
and Secure
attribute set to true
- which
makes the browser accept the cookie only if the cross-origin request (to
localhost
in this case, to the /login
API) happens over HTTPS, or if the
domain is localhost
.
Also, this request should be done with credentials: "include"
in the fetch
options. This actually confused me: my understanding initially was that
credentials: "include"
in a fetch call only makes it send the cookies that
are already present, but it turns out you also need it to receive and store
the cookie via Set-Cookie
response header. In fact, the documentation states
exactly that - and of
course I didn’t read it before wasting my time debugging :) 2
Make sure to do the request via fetch
instead of action
attribute of your
<form>
element - otherwise the browser location will end up on the auth
server’s address (http://localhost:8080/login
in my case) - probably don’t
want that :).
The above will make the browser store your cookie for the domain
localhost:5173
itself even though it was set by a different origin. Subsequent
requests to CORS-enabled APIs should be done with credentials: "include"
-
which makes sense, since by default the browser does not include credentials (in
our case, cookies) in a cross-origin request.
Cookie Domain
The Domain
attribute of a cookie is a domain name indepdent of port. So if you
set localhost
- the cookie will be sent as part of requests from the client
app to any localhost:XXX
address (provided you send credentials of course).
In fact, you can set the domain to something like my-org.com
and the cookie
will be sent to any subdomain address also like auth.my-org.com
and
app.my-org.com
.
In short
Server side:
- Enable CORS for your
/login
API by allowing your web-app’s origin(s) - Generate your auth-token and set the following attributes on your auth-token
cookie
SameSite: None
,Domain: my-org.com
,Secure: true
and send the cookie in Set-Cookie response header.
Client side:
- Always set
{credentials: "include"}
to your fetch API calls to ‘/login’ and any authenticated API thereafter.