Purpose of Go CSRF / XSRF Token and Form Protection, and Default Token Expiration of Standard Package
Hello, I am Munou.
I have an online storage that I built and use personally:
GitHub - haturatu/puremania: No security, very fast, web UI self-hosted online storage
However, if I want to add authentication in front of it, I decided to create an authentication proxy.
GitHub - haturatu/auth-proxy: An authentication proxy server and frontend for a website without built-in authentication. JavaScript is supported, but it can also work without JS if using PHP-FPM. The backend is written in Go.
Actually, it's mostly thanks to Gemini-kun...
It only proxies requests that pass authentication.
Admin users can issue Bearer tokens for API Endpoints, and requests can be made using those tokens. However, in this case, there are also API endpoints that need to be accessible from frontend sessions, so in practice, both cookies and Bearer tokens will work.
If you only want to protect API endpoints, you can isolate the frontend (like web login) in a private network and expose only the API endpoints. Then, user creation can be done from the private network, and API token issuance and API proxying using those users are practically possible. (Please don't say, "Just use an API Gateway OSS.")
In reality, this would practically allow double brute-forcing with both cookies and tokens, but oh well...
So, regarding form security for the safety of another web frontend during login.
If JS loading is mandatory, most problems can be solved, but isn't it boring to rely solely on JS? So, I decided to enhance form security using methods other than JS.
CSRF / XSRF Token
This will be the focus of this discussion, specifically what it is in the first place.
Simply put, it's a mechanism to verify that a request from the client side was properly issued from my server and to allow it if the token is within its validity period.
For example,
<input type="hidden" name="xsrf_token" value="BeftikzaR8Oe6npnqXYC7WtBhuo:1760376550660">
Such a token is embedded in the frontend HTML. This value is signed by the server's secret key. So the server knows, "This was issued from my server!"1760376550660 is just a string of characters representing a UNIX timestamp, but what is it needed for with the current time?
xsrftoken package - golang.org/x/net/xsrftoken - Go Packages
func ValidFor(token, key, userID, actionID string, timeout time.Duration) bool
It is used for the expiration setting mentioned above.
In practice, with the time package included, it looks like this:
if !xsrftoken.ValidFor(clientToken, string(xsrfSecret), sessionID, r.URL.Path, 15*time.Minute) {
If this argument is not provided, the default is 24 hours, which is quite lenient. However, in that case, I believe it's provided in the intended form of an XSRF Token, where only the verification that the token was issued by the server is performed.
What's the difference between CSRF and XSRF?!
In reality, there doesn't seem to be much difference.
The purpose is to prevent cross-site attacks.
In the case of CSRF
GitHub - gorilla/csrf: Package gorilla/csrf provides Cross Site Request Forgery (CSRF) prevention middleware for Go web applications & services 🔒
I found this library and happened to come across this:
gorilla/csrf CSRF vulnerability demo
One way to improve the gorilla/csrf implementation is to replace the random value used in forms with an encrypted token tied to the user's ID. This prevents an attacker from swapping their own CSRF token and cookie value with those of the target, because the attacker's CSRF token corresponds to a different ID. In fact, this method is implemented in a library that uses x/net/xsrftokenHMAC to authenticate the user ID, an optional form action, and an expiration date.
So, I thought, 'Oh, it's in the standard library,' and
xsrftoken/xsrf.go
I was surprised to see the source code: only 100 lines, including comments! I'm a bit concerned that SHA1 is still being used, but perhaps it will be updated eventually.
Well, if the standard library is this simple, then it's sufficient for the intended purpose. If you need to perform SameSite validation, then you should probably use gorilla/csrf.
Caveats of the Standard Library
As I mentioned earlier, the default token expiration is 24H.
In a real-world operation, this token expiration could potentially be abused, so it might be better to set a shorter duration. Assuming this token is intended for sending requests up to form submission, 24H means that if no other security measures are in place, the same token could be used repeatedly to send requests. It's not impossible to brute-force by sending requests to the form repeatedly with the same token using infinite curl.