From b2c0d472a31fdafc1e72f8970ee668f16837fe93 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 15 Apr 2026 05:30:59 +0300 Subject: [PATCH] veb: fix CSRF module (fixes #26871) --- vlib/veb/csrf/README.md | 5 +++++ vlib/veb/csrf/csrf_test.v | 31 +++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/vlib/veb/csrf/README.md b/vlib/veb/csrf/README.md index 32bfd5941..21c80bc34 100644 --- a/vlib/veb/csrf/README.md +++ b/vlib/veb/csrf/README.md @@ -76,6 +76,8 @@ _main.v_ ```v ignore fn (app &App) index(mut ctx) veb.Result { + // assign the config before generating the token on non-middleware routes + ctx.config = csrf_config // this function will set a cookie header and generate a CSRF token ctx.set_csrf_token(mut ctx) return $veb.html() @@ -140,11 +142,14 @@ fn (app &App) login(mut ctx, password string) veb.Result { When `set_csrf_token` is called the token is stored in the `csrf_token` field. You access this field directly to use it in an input field, or call `csrf_token_input`. +If the handler is not covered by `csrf.middleware`, assign `ctx.config = csrf_config` +first so the generated cookie and token use the same configuration. **Example:** ```v ignore fn (app &App) index(mut ctx) veb.Result { + ctx.config = csrf_config token := ctx.set_csrf_token(mut ctx) } ``` diff --git a/vlib/veb/csrf/csrf_test.v b/vlib/veb/csrf/csrf_test.v index 2670bd8e2..6387ab4e1 100644 --- a/vlib/veb/csrf/csrf_test.v +++ b/vlib/veb/csrf/csrf_test.v @@ -182,6 +182,7 @@ pub fn (mut app App) before_accept_loop() { } fn (app &App) index(mut ctx Context) veb.Result { + ctx.config = *csrf_config ctx.set_csrf_token(mut ctx) return ctx.html('
@@ -240,6 +241,36 @@ fn test_token_input() { assert csrf_config.token_name == inputs[0].attributes['name'] } +fn test_token_roundtrip_from_index_form() { + res := http.get('http://${localserver}/') or { panic(err) } + assert res.status() == .ok + + cookies := res.cookies() + assert cookies.len == 1 + + mut doc := html.parse(res.body) + inputs := doc.get_tags_by_attribute_value('type', 'hidden') + assert inputs.len == 1 + + token := inputs[0].attributes['value'] + assert token.len != 0 + + mut req := http.Request{ + method: .post + url: 'http://${localserver}/auth' + data: http.url_encode_form_data({ + csrf_config.token_name: token + }) + } + for cookie in cookies { + req.add_cookie(cookie) + } + + post_res := req.do() or { panic(err) } + assert post_res.status() == .ok + assert post_res.body == 'authenticated' +} + // utility function to check whether the route at `path` is protected against csrf fn protect_route_util(path string) { mut req := http.Request{ -- 2.39.5