Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1182 +/- ##
==========================================
+ Coverage 68.86% 68.90% +0.04%
==========================================
Files 230 236 +6
Lines 14879 15426 +547
==========================================
+ Hits 10246 10630 +384
- Misses 4235 4410 +175
+ Partials 398 386 -12 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
http/middleware/CSRFToken.go
Outdated
|
|
||
| func CSRFToken() httpcontract.Middleware { | ||
| return func(ctx httpcontract.Context) { | ||
| sessionCSRFTokenInterface := ctx.Request().Session().Get(csrfKey) |
There was a problem hiding this comment.
Session csrf token can be obtained using the Token method. Make sure the session is set, otherwise it will panic. You can use the HasSession method from Request() to check this.
There was a problem hiding this comment.
@kkumar-gcc,
I tested the regenerate function, which is responsible for generating a new CSRF token and setting it on _token. However, I noticed that when I send a subsequent request and access the token using Session.Token(), it still returns the token from the previous request not the updated one generated in the current request.
Could you please take a look and test this behavior? This could potentially lead to issues in the future when passing the _token to HTML forms, as the form would use an outdated CSRF token.
Here’s the middleware function I'm currently using for testing:
func VerifyCsrfToken(excepts []string) contractshttp.Middleware {
return func(ctx contractshttp.Context) {
if isReading(ctx.Request().Method()) || inExceptArray(excepts, ctx.Request().FullUrl()) || tokenMatch(ctx) {
ctx.Request().Next()
ctx.Request().Session().Regenerate()
ctx.Request().Session().Put(csrfKey, ctx.Request().Session().Token())
} else {
ctx.Request().AbortWithStatusJson(contractshttp.StatusTokenMismatch, map[string]string{
"message": "CSRF token mismatch.",
})
}
}
}
There was a problem hiding this comment.
Yes, that’s expected behavior. The CSRF token in session isn’t meant to change on every request. It only needs to be rotated on important events like login, logout, privilege changes, or when the session is migrated. Rotating it per request would cause unnecessary complexity without adding real security benefits. Though we can expose session.RegenerateToken() in case we ever need to manually force a new token in any flow.
cc: @hwbrzzl
There was a problem hiding this comment.
I tested the regenerate function, which is responsible for generating a new CSRF token and setting it on _token. However, I noticed that when I send a subsequent request and access the token using Session.Token(), it still returns the token from the previous request not the updated one generated in the current request.
@NiteshSingh17 Could you add some screenshots for your test? I'm not sure if I got the correct information on this.
There was a problem hiding this comment.
Screencast.from.2025-09-07.21-32-35.mp4
I thought there might be an issue because I was expecting the CSRF token to reset with every request. But keeping it the same throughout the session makes sense and works fine.
There was a problem hiding this comment.
You may need to Save the session after regenerating.
There was a problem hiding this comment.
hwbrzzl
left a comment
There was a problem hiding this comment.
Thanks, LGTM, could you add some screenshots to confirm if the middleware is worked as expected?
http/middleware/verify_csrf_token.go
Outdated
| ctx.Request().Next() | ||
| ctx.Response().Header(HeaderCsrfKey, ctx.Request().Session().Token()) | ||
| } else { | ||
| ctx.Request().AbortWithStatusJson(contractshttp.StatusTokenMismatch, map[string]string{"message": contractshttp.StatusText(contractshttp.StatusTokenMismatch)}) |
There was a problem hiding this comment.
AbortWithStatusJson is deprecated, please use ctx.Response().Json().Abort() instead.
| if requestCsrfToken == "" { | ||
| requestCsrfToken = ctx.Request().Input("_token") | ||
| } | ||
| if requestCsrfToken == "" || subtle.ConstantTimeCompare([]byte(requestCsrfToken), []byte(sessionCsrfToken)) == 0 { |
There was a problem hiding this comment.
They may be compared by length first, otherwise, ConstantTimeCompare will panic.
| if requestCsrfToken == "" || subtle.ConstantTimeCompare([]byte(requestCsrfToken), []byte(sessionCsrfToken)) == 0 { | |
| if requestCsrfToken == "" || len(requestCsrfToken) !=len(sessionCsrfToken) || subtle.ConstantTimeCompare([]byte(requestCsrfToken), []byte(sessionCsrfToken)) == 0 { |
There was a problem hiding this comment.
Thanks for the test cases! It's a bit complex. Actually, contractshttp.Context can be mocked, and we can just test the functions in verify_csrf_token.go one by one. But it doesn't matter, you have done great work, we can merge this PR first if you want. You or I can optimize the test cases later.
There was a problem hiding this comment.
I agree that the implementation was a bit complex. Thank you for your support. We can go ahead and merge the PR now, and optimize the test cases later 👍
|
@NiteshSingh17 We can optimize the gin PR and create a PR for fiber next. |
* create csrf middleware * minor * update csrf token * handle absolute paths * csrf test * replace http to net http * replace AbortWithStatusJson with Abort








📑 Description
Closes goravel/goravel#218
✅ Checks