Repository: sebadob/rauthy (language: rust)

Issue:
drop `actix-web-validator`
I do have some issues with `actix-web-validator` and it should be dropped as a dependency.

- The updates have been pretty slow in the past, preventing updates to latest crates to fix some bugs (which is the case right now as well since a few weeks).
- The types have the exact same name as the `actix` default ones, just with a different prefix. This is very error-prone in case the IDE auto-imports the default type instead of the validator type. In this case, no validation will be done at all and it's super easy to miss.

Instead, the goal is to not use any actix-validation library at all and only rely on `validator` itself. This means adding a single line of code to each endpoint, but will make it absolutely clear if any validation is missing somewhere. It is possible to use libraries that do it automatically, but these need additional code like calling `.into_inner()` as well, so there is no benefit, only overhead.

Calling `.validate()?` manually in each endpoint makes it also possible to return the exact same `ErrorResponse` type as for all the other endpoints, which is a big benefit for the UX.



Discussion / hints:

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 87c741c80..471bf0068 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -97,7 +97,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb"
 dependencies = [
  "quote",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
@@ -135,7 +135,7 @@ dependencies = [
  "parse-size",
  "proc-macro2",
  "quote",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
@@ -290,7 +290,7 @@ dependencies = [
  "actix-router",
  "proc-macro2",
  "quote",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
@@ -339,7 +339,7 @@ checksum = "4c221da13534b9352f3f79fcbbd6095f6d8aee63bdf1da8a73d36f9eeea17d5a"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
@@ -357,28 +357,6 @@ dependencies = [
  "strfmt",
 ]
 
-[[package]]
-name = "actix-web-validator"
-version = "6.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f65804955af25ad36225100983ec8d92f14795e7f8080073f8147d0b768c78e"
-dependencies = [
- "actix-http",
- "actix-router",
- "actix-web",
- "bytes",
- "futures",
- "futures-util",
- "log",
- "mime",
- "serde",
- "serde_json",
- "serde_qs",
- "serde_urlencoded",
- "thiserror 1.0.69",
- "validator",
-]
-
 [[package]]
 name = "actix_derive"
 version = "0.6.2"
@@ -387,7 +365,7 @@ checksum = "b6ac1e58cded18cb28ddc17143c4dea5345b3ad575e14f32f66e4054a56eb271"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
@@ -583,7 +561,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
@@ -653,7 +631,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "serde",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
@@ -695,7 +673,7 @@ checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.93",
+ "syn 2.0.94",
  "synstructure",
 ]
 
@@ -707,7 +685,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
@@ -748,18 +726,18 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
 name = "async-trait"
-version = "0.1.83"
+version = "0.1.84"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
+checksum = "1b1244b10dcd56c92219da4e14caa97e312079e185f04ba3eea25061561dc0a0"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
@@ -819,16 +797,15 @@ dependencies = [
 
 [[package]]
 name = "aws-lc-sys"
-version = "0.24.0"
+version = "0.24.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8478a5c29ead3f3be14aff8a202ad965cf7da6856860041bfca271becf8ba48b"
+checksum = "923ded50f602b3007e5e63e3f094c479d9c8a9b42d7f4034e4afe456aa48bfd2"
 dependencies = [
  "bindgen",
  "cc",
  "cmake",
  "dunce",
  "fs_extra",
- "libc",
  "paste",
 ]
 
@@ -1028,7 +1005,7 @@ dependencies = [
  "regex",
  "rustc-hash 1.1.0",
  "shlex",
- "syn 2.0.93",
+ "syn 2.0.94",
  "which",
 ]
 
@@ -1120,7 +1097,7 @@ dependencies = [
  "proc-macro-crate",
  "proc-macro2",
  "quote",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
@@ -1268,7 +1245,7 @@ dependencies = [
  "darling",
  "proc-macro2",
  "quote",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
@@ -1279,9 +1256,9 @@ checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0"
 
 [[package]]
 name = "cc"
-version = "1.2.6"
+version = "1.2.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8d6dbb628b8f8555f86d0323c2eb39e3ec81901f4b83e091db8a6a76d316a333"
+checksum = "a012a0df96dd6d06ba9a1b29d6402d1a5d77c6befd2566afdc26e10603dc93d7"
 dependencies = [
  "jobserver",
  "libc",
@@ -1427,7 +1404,7 @@ dependencies = [
  "heck",
  "proc-macro2",
  "quote",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
@@ -1763,7 +1740,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "strsim",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
@@ -1774,7 +1751,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
 dependencies = [
  "darling_core",
  "quote",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
@@ -1864,7 +1841,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
@@ -1877,7 +1854,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "rustc_version",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
@@ -1897,7 +1874,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.93",
+ "syn 2.0.94",
  "unicode-xid",
 ]
 
@@ -1927,7 +1904,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
@@ -2174,6 +2151,12 @@ version = "1.0.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
 
+[[package]]
+name = "foldhash"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f"
+
 [[package]]
 name = "foreign-types"
 version = "0.3.2"
@@ -2277,7 +2260,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
@@ -2441,6 +2424,11 @@ name = "hashbrown"
 version = "0.15.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
+dependencies = [
+ "allocator-api2",
+ "equivalent",
+ "foldhash",
+]
 
 [[package]]
 name = "hashlink"
@@ -2451,6 +2439,15 @@ dependencies = [
  "hashbrown 0.14.5",
 ]
 
+[[package]]
+name = "hashlink"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
+dependencies = [
+ "hashbrown 0.15.2",
+]
+
 [[package]]
 name = "heck"
 version = "0.5.0"
@@ -2918,7 +2915,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
@@ -2927,16 +2924,6 @@ version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
 
-[[package]]
-name = "idna"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
-dependencies = [
- "unicode-bidi",
- "unicode-normalization",
-]
-
 [[package]]
 name = "idna"
 version = "1.0.3"
@@ -3037,7 +3024,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
@@ -3242,7 +3229,7 @@ dependencies = [
  "futures-io",
  "futures-util",
  "httpdate",
- "idna 1.0.3",
+ "idna",
  "mime",
  "nom",
  "percent-encoding",
@@ -3637,7 +3624,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
@@ -3763,7 +3750,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "semver",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
@@ -3789,7 +3776,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
@@ -3949,7 +3936,7 @@ checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
@@ -4047,7 +4034,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033"
 dependencies = [
  "proc-macro2",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
@@ -4069,27 +4056,25 @@ dependencies = [
 ]
 
 [[package]]
-name = "proc-macro-error"
-version = "1.0.4"
+name = "proc-macro-error-attr2"
+version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
 dependencies = [
- "proc-macro-error-attr",
  "proc-macro2",
  "quote",
- "syn 1.0.109",
- "version_check",
 ]
 
 [[package]]
-name = "proc-macro-error-attr"
-version = "1.0.4"
+name = "proc-macro-error2"
+version = "2.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
 dependencies = [
+ "proc-macro-error-attr2",
  "proc-macro2",
  "quote",
- "version_check",
+ "syn 2.0.94",
 ]
 
 [[package]]
@@ -4117,7 +4102,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30"
 dependencies = [
  "quote",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
@@ -4440,7 +4425,6 @@ dependencies = [
  "actix-multipart",
  "actix-web",
  "actix-web-lab",
- "actix-web-validator",
  "chrono",
  "cryptr",
  "mime_guess",
@@ -4776,9 +4760,9 @@ dependencies = [
 
 [[package]]
 name = "reqwest"
-version = "0.12.11"
+version = "0.12.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7fe060fe50f524be480214aba758c71f99f90ee8c83c5a36b5e9e1d568eb4eb3"
+checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da"
 dependencies = [
  "async-compression",
  "base64 0.22.1",
@@ -4961,7 +4945,7 @@ dependencies = [
  "regex",
  "relative-path",
  "rustc_version",
- "syn 2.0.93",
+ "syn 2.0.94",
  "unicode-ident",
 ]
 
@@ -5102,7 +5086,7 @@ dependencies = [
  "quote",
  "ruma-identifiers-validation",
  "serde",
- "syn 2.0.93",
+ "syn 2.0.94",
  "toml",
 ]
 
@@ -5116,7 +5100,7 @@ dependencies = [
  "chrono",
  "fallible-iterator",
  "fallible-streaming-iterator",
- "hashlink",
+ "hashlink 0.9.1",
  "libsqlite3-sys",
  "serde_json",
  "smallvec",
@@ -5144,7 +5128,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "rust-embed-utils",
- "syn 2.0.93",
+ "syn 2.0.94",
  "walkdir",
 ]
 
@@ -5465,7 +5449,7 @@ checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
@@ -5541,7 +5525,7 @@ checksum = "aafbefbe175fa9bf03ca83ef89beecff7d2a95aaacd5732325b90ac8c3bd7b90"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
@@ -5563,19 +5547,6 @@ dependencies = [
  "serde",
 ]
 
-[[package]]
-name = "serde_qs"
-version = "0.13.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cd34f36fe4c5ba9654417139a9b3a20d2e1de6012ee678ad14d240c22c78d8d6"
-dependencies = [
- "actix-web",
- "futures",
- "percent-encoding",
- "serde",
- "thiserror 1.0.69",
-]
-
 [[package]]
 name = "serde_rusqlite"
 version = "0.36.0"
@@ -5634,7 +5605,7 @@ dependencies = [
  "darling",
  "proc-macro2",
  "quote",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
@@ -5786,21 +5757,11 @@ dependencies = [
  "thiserror 1.0.69",
 ]
 
-[[package]]
-name = "sqlformat"
-version = "0.2.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790"
-dependencies = [
- "nom",
- "unicode_categories",
-]
-
 [[package]]
 name = "sqlx"
-version = "0.8.2"
+version = "0.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93334716a037193fac19df402f8571269c84a00852f6a7066b5d2616dcd64d3e"
+checksum = "4410e73b3c0d8442c5f99b425d7a435b5ee0ae4167b3196771dd3f7a01be745f"
 dependencies = [
  "sqlx-core",
  "sqlx-macros",
@@ -5811,30 +5772,25 @@ dependencies = [
 
 [[package]]
 name = "sqlx-core"
-version = "0.8.2"
+version = "0.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4d8060b456358185f7d50c55d9b5066ad956956fddec42ee2e8567134a8936e"
+checksum = "6a007b6936676aa9ab40207cde35daab0a04b823be8ae004368c0793b96a61e0"
 dependencies = [
- "atoi",
- "byteorder",
  "bytes",
  "crc",
  "crossbeam-queue",
  "either",
  "event-listener",
- "futures-channel",
  "futures-core",
  "futures-intrusive",
  "futures-io",
  "futures-util",
- "hashbrown 0.14.5",
- "hashlink",
- "hex",
+ "hashbrown 0.15.2",
+ "hashlink 0.10.0",
  "indexmap 2.7.0",
  "log",
  "memchr",
  "once_cell",
- "paste",
  "percent-encoding",
  "rustls 0.23.20",
  "rustls-pemfile 2.2.0",
@@ -5842,8 +5798,7 @@ dependencies = [
  "serde_json",
  "sha2",
  "smallvec",
- "sqlformat",
- "thiserror 1.0.69",
+ "thiserror 2.0.9",
  "tokio",
  "tokio-stream",
  "tracing",
@@ -5854,22 +5809,22 @@ dependencies = [
 
 [[package]]
 name = "sqlx-macros"
-version = "0.8.2"
+version = "0.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cac0692bcc9de3b073e8d747391827297e075c7710ff6276d9f7a1f3d58c6657"
+checksum = "3112e2ad78643fef903618d78cf0aec1cb3134b019730edb039b69eaf531f310"
 dependencies = [
  "proc-macro2",
  "quote",
  "sqlx-core",
  "sqlx-macros-core",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
 name = "sqlx-macros-core"
-version = "0.8.2"
+version = "0.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1804e8a7c7865599c9c79be146dc8a9fd8cc86935fa641d3ea58e5f0688abaa5"
+checksum = "4e9f90acc5ab146a99bf5061a7eb4976b573f560bc898ef3bf8435448dd5e7ad"
 dependencies = [
  "dotenvy",
  "either",
@@ -5885,7 +5840,7 @@ dependencies = [
  "sqlx-mysql",
  "sqlx-postgres",
  "sqlx-sqlite",
- "syn 2.0.93",
+ "syn 2.0.94",
  "tempfile",
  "tokio",
  "url",
@@ -5893,9 +5848,9 @@ dependencies = [
 
 [[package]]
 name = "sqlx-mysql"
-version = "0.8.2"
+version = "0.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64bb4714269afa44aef2755150a0fc19d756fb580a67db8885608cf02f47d06a"
+checksum = "4560278f0e00ce64938540546f59f590d60beee33fffbd3b9cd47851e5fff233"
 dependencies = [
  "atoi",
  "base64 0.22.1",
@@ -5928,7 +5883,7 @@ dependencies = [
  "smallvec",
  "sqlx-core",
  "stringprep",
- "thiserror 1.0.69",
+ "thiserror 2.0.9",
  "tracing",
  "uuid",
  "whoami",
@@ -5936,9 +5891,9 @@ dependencies = [
 
 [[package]]
 name = "sqlx-postgres"
-version = "0.8.2"
+version = "0.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6fa91a732d854c5d7726349bb4bb879bb9478993ceb764247660aee25f67c2f8"
+checksum = "c5b98a57f363ed6764d5b3a12bfedf62f07aa16e1856a7ddc2a0bb190a959613"
 dependencies = [
  "atoi",
  "base64 0.22.1",
@@ -5949,7 +5904,6 @@ dependencies = [
  "etcetera",
  "futures-channel",
  "futures-core",
- "futures-io",
  "futures-util",
  "hex",
  "hkdf",
@@ -5967,7 +5921,7 @@ dependencies = [
  "smallvec",
  "sqlx-core",
  "stringprep",
- "thiserror 1.0.69",
+ "thiserror 2.0.9",
  "tracing",
  "uuid",
  "whoami",
@@ -5975,9 +5929,9 @@ dependencies = [
 
 [[package]]
 name = "sqlx-sqlite"
-version = "0.8.2"
+version = "0.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d5b2cf34a45953bfd3daaf3db0f7a7878ab9b7a6b91b422d24a7a9e4c857b680"
+checksum = "f85ca71d3a5b24e64e1d08dd8fe36c6c95c339a896cc33068148906784620540"
 dependencies = [
  "atoi",
  "flume",
@@ -6058,7 +6012,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "rustversion",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
@@ -6107,9 +6061,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.93"
+version = "2.0.94"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c786062daee0d6db1132800e623df74274a0a87322d8e183338e01b3d98d058"
+checksum = "987bc0be1cdea8b10216bd06e2ca407d40b9543468fafd3ddfb02f36e77f71f3"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -6133,7 +6087,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
@@ -6163,12 +6117,13 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
 
 [[package]]
 name = "tempfile"
-version = "3.14.0"
+version = "3.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c"
+checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704"
 dependencies = [
  "cfg-if",
  "fastrand",
+ "getrandom",
  "once_cell",
  "rustix",
  "windows-sys 0.59.0",
@@ -6200,7 +6155,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
@@ -6211,7 +6166,7 @@ checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
@@ -6329,7 +6284,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
@@ -6501,7 +6456,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
@@ -6632,12 +6587,6 @@ version = "0.2.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
 
-[[package]]
-name = "unicode_categories"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
-
 [[package]]
 name = "universal-hash"
 version = "0.5.1"
@@ -6661,7 +6610,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
 dependencies = [
  "form_urlencoded",
- "idna 1.0.3",
+ "idna",
  "percent-encoding",
  "serde",
 ]
@@ -6717,7 +6666,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "regex",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
@@ -6761,11 +6710,11 @@ dependencies = [
 
 [[package]]
 name = "validator"
-version = "0.18.1"
+version = "0.19.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "db79c75af171630a3148bd3e6d7c4f42b6a9a014c2945bc5ed0020cbb8d9478e"
+checksum = "d0b4a29d8709210980a09379f27ee31549b73292c87ab9899beee1c0d3be6303"
 dependencies = [
- "idna 0.5.0",
+ "idna",
  "once_cell",
  "regex",
  "serde",
@@ -6777,16 +6726,16 @@ dependencies = [
 
 [[package]]
 name = "validator_derive"
-version = "0.18.2"
+version = "0.19.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df0bcf92720c40105ac4b2dda2a4ea3aa717d4d6a862cc217da653a4bd5c6b10"
+checksum = "bac855a2ce6f843beb229757e6e570a42e837bcb15e5f449dd48d5747d41bf77"
 dependencies = [
  "darling",
  "once_cell",
- "proc-macro-error",
+ "proc-macro-error2",
  "proc-macro2",
  "quote",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
@@ -6883,7 +6832,7 @@ dependencies = [
  "log",
  "proc-macro2",
  "quote",
- "syn 2.0.93",
+ "syn 2.0.94",
  "wasm-bindgen-shared",
 ]
 
@@ -6918,7 +6867,7 @@ checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.93",
+ "syn 2.0.94",
  "wasm-bindgen-backend",
  "wasm-bindgen-shared",
 ]
@@ -7302,9 +7251,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
 
 [[package]]
 name = "winnow"
-version = "0.6.20"
+version = "0.6.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b"
+checksum = "39281189af81c07ec09db316b302a3e67bf9bd7cbf6c820b50e35fee9c2fa980"
 dependencies = [
  "memchr",
 ]
@@ -7379,7 +7328,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.93",
+ "syn 2.0.94",
  "synstructure",
 ]
 
@@ -7401,7 +7350,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
@@ -7421,7 +7370,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.93",
+ "syn 2.0.94",
  "synstructure",
 ]
 
@@ -7450,7 +7399,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.93",
+ "syn 2.0.94",
 ]
 
 [[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 5290c3a34..158acba21 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -4,7 +4,7 @@ members = ["src/*"]
 exclude = ["rauthy-client"]
 
 [workspace.package]
-version = "0.27.3"
+version = "0.28.0-20250104"
 edition = "2021"
 authors = ["Sebastian Dobe <sebastiandobe@mailbox.org>"]
 license = "Apache-2.0"
@@ -30,7 +30,6 @@ actix-web = { version = "4", features = ["rustls-0_23"] }
 actix-web-actors = "4"
 actix-web-lab = "0.23"
 actix-web-prom = "0.9.0"
-actix-web-validator = "6"
 anyhow = "1"
 argon2 = { version = "0.5", features = ["std", "zeroize"] }
 askama = { version = "0.12", features = ["with-actix-web"] }
@@ -105,8 +104,7 @@ tokio = { version = "1", features = ["full"] }
 utoipa = { version = "5", features = ["actix_extras", "chrono", "openapi_extensions"] }
 utoipa-swagger-ui = { version = "8.0.3", features = ["actix-web"] }
 uuid = { version = "1", features = ["serde", "v4"] }
-# update to 0.19 blocked by `actix-web-validator`
-validator = { version = "0.18", features = ["derive"] }
+validator = { version = "0.19.0", features = ["derive"] }
 webauthn-rs = { version = "0.5", features = [
     "danger-allow-state-serialisation", "danger-credential-internals"
 ] }
diff --git a/dev_notes.md b/dev_notes.md
index 577f92fdb..4c4b2b076 100644
--- a/dev_notes.md
+++ b/dev_notes.md
@@ -13,7 +13,6 @@
 - "known host cookie" with connection between accounts and IPs to send out warnings in case
   of a login on a new device
 - credential stuffing detection
-- on the long term, get rid of `actix-web-validator` as it often blocked `validator` updates already
 - find a nice way to always expose the swagger UI for rauthy admins only without config
   current issue: when the session cookie is a non-host cookie with path restriction -> not working
   probably move from `/docs/v1` to `/auth/v1/docs`
diff --git a/src/api/Cargo.toml b/src/api/Cargo.toml
index 8ab52977e..105b1b916 100644
--- a/src/api/Cargo.toml
+++ b/src/api/Cargo.toml
@@ -15,7 +15,6 @@ rauthy-service = { path = "../service" }
 actix-multipart = { workspace = true }
 actix-web = { workspace = true }
 actix-web-lab = { workspace = true }
-actix-web-validator = { workspace = true }
 chrono = { workspace = true }
 cryptr = { workspace = true }
 mime_guess = { workspace = true }
diff --git a/src/api/src/api_keys.rs b/src/api/src/api_keys.rs
index 07227578b..df7a240ea 100644
--- a/src/api/src/api_keys.rs
+++ b/src/api/src/api_keys.rs
@@ -1,10 +1,11 @@
 use crate::ReqPrincipal;
+use actix_web::web::Json;
 use actix_web::{delete, get, post, put, web, HttpResponse};
-use actix_web_validator::Json;
 use mime_guess::mime::TEXT_PLAIN_UTF_8;
 use rauthy_api_types::api_keys::{ApiKeyRequest, ApiKeyResponse, ApiKeysResponse};
 use rauthy_error::{ErrorResponse, ErrorResponseType};
 use rauthy_models::entity::api_keys::ApiKeyEntity;
+use validator::Validate;
 
 /// Returns all API Keys
 ///
@@ -52,13 +53,13 @@ pub async fn get_api_keys(principal: ReqPrincipal) -> Result<HttpResponse, Error
 #[post("/api_keys")]
 pub async fn post_api_key(
     principal: ReqPrincipal,
-    payload: Json<ApiKeyRequest>,
+    Json(payload): Json<ApiKeyRequest>,
 ) -> Result<HttpResponse, ErrorResponse> {
     principal.validate_admin_session()?;
+    payload.validate()?;
 
-    let req = payload.into_inner();
-    let access = req.access.into_iter().map(|a| a.into()).collect();
-    let secret = ApiKeyEntity::create(req.name, req.exp, access).await?;
+    let access = payload.access.into_iter().map(|a| a.into()).collect();
+    let secret = ApiKeyEntity::create(payload.name, payload.exp, access).await?;
 
     Ok(HttpResponse::Ok()
         .content_type(TEXT_PLAIN_UTF_8)
@@ -84,21 +85,21 @@ pub async fn post_api_key(
 pub async fn put_api_key(
     principal: ReqPrincipal,
     name: web::Path<String>,
-    payload: Json<ApiKeyRequest>,
+    Json(payload): Json<ApiKeyRequest>,
 ) -> Result<HttpResponse, ErrorResponse> {
     principal.validate_admin_session()?;
+    payload.validate()?;
 
     let name = name.into_inner();
-    let req = payload.into_inner();
-    if req.name != name {
+    if payload.name != name {
         return Err(ErrorResponse::new(
             ErrorResponseType::BadRequest,
             "JSON payload does not match the Name from the path".to_string(),
         ));
     }
 
-    let access = req.access.into_iter().map(|a| a.into()).collect();
-    ApiKeyEntity::update(&name, req.exp, access).await?;
+    let access = payload.access.into_iter().map(|a| a.into()).collect();
+    ApiKeyEntity::update(&name, payload.exp, access).await?;
 
     Ok(HttpResponse::Ok().finish())
 }
diff --git a/src/api/src/auth_providers.rs b/src/api/src/auth_providers.rs
index 6b89deb7c..1836f22a4 100644
--- a/src/api/src/auth_providers.rs
+++ b/src/api/src/auth_providers.rs
@@ -1,8 +1,8 @@
 use crate::{map_auth_step, ReqPrincipal};
 use actix_web::http::header::{CACHE_CONTROL, CONTENT_TYPE, LOCATION};
+use actix_web::web::Json;
 use actix_web::{delete, get, post, put, web, HttpRequest, HttpResponse};
 use actix_web_lab::__reexports::futures_util::StreamExt;
-use actix_web_validator::Json;
 use rauthy_api_types::auth_providers::{
     ProviderCallbackRequest, ProviderLoginRequest, ProviderLookupRequest, ProviderRequest,
 };
@@ -19,6 +19,7 @@ use rauthy_models::entity::users::User;
 use rauthy_models::language::Language;
 use rauthy_models::templates::ProviderCallbackHtml;
 use tracing::debug;
+use validator::Validate;
 
 /// GET all upstream auth providers
 ///
@@ -63,10 +64,11 @@ pub async fn post_providers(principal: ReqPrincipal) -> Result<HttpResponse, Err
 )]
 #[post("/providers/create")]
 pub async fn post_provider(
-    payload: Json<ProviderRequest>,
+    Json(payload): Json<ProviderRequest>,
     principal: ReqPrincipal,
 ) -> Result<HttpResponse, ErrorResponse> {
     principal.validate_admin_session()?;
+    payload.validate()?;
 
     if !payload.use_pkce && payload.client_secret.is_none() {
         return Err(ErrorResponse::new(
@@ -75,7 +77,7 @@ pub async fn post_provider(
         ));
     }
 
-    let provider = AuthProvider::create(payload.into_inner()).await?;
+    let provider = AuthProvider::create(payload).await?;
     Ok(HttpResponse::Ok().json(ProviderResponse::try_from(provider)?))
 }
 
@@ -101,12 +103,12 @@ pub async fn post_provider(
     skip_all, fields(issuer = payload.issuer)
 )]
 pub async fn post_provider_lookup(
-    payload: Json<ProviderLookupRequest>,
+    Json(payload): Json<ProviderLookupRequest>,
     principal: ReqPrincipal,
 ) -> Result<HttpResponse, ErrorResponse> {
     principal.validate_admin_session()?;
+    payload.validate()?;
 
-    let payload = payload.into_inner();
     let resp = AuthProvider::lookup_config(&payload).await?;
 
     Ok(HttpResponse::Ok().json(resp))
@@ -129,12 +131,12 @@ pub async fn post_provider_lookup(
 )]
 #[post("/providers/login")]
 pub async fn post_provider_login(
-    payload: Json<ProviderLoginRequest>,
+    Json(payload): Json<ProviderLoginRequest>,
     principal: ReqPrincipal,
 ) -> Result<HttpResponse, ErrorResponse> {
     principal.validate_session_auth_or_init()?;
+    payload.validate()?;
 
-    let payload = payload.into_inner();
     let (cookie, xsrf_token, location) = AuthProviderCallback::login_start(payload).await?;
 
     Ok(HttpResponse::Accepted()
@@ -175,12 +177,12 @@ pub async fn get_provider_callback_html(req: HttpRequest) -> Result<HttpResponse
 pub async fn post_provider_callback(
     data: web::Data<AppState>,
     req: HttpRequest,
-    payload: Json<ProviderCallbackRequest>,
+    Json(payload): Json<ProviderCallbackRequest>,
     principal: ReqPrincipal,
 ) -> Result<HttpResponse, ErrorResponse> {
     principal.validate_session_auth_or_init()?;
+    payload.validate()?;
 
-    let payload = payload.into_inner();
     let session = principal.get_session()?;
     let (auth_step, cookie) =
         AuthProviderCallback::login_finish(&data, &req, &payload, session.clone()).await?;
@@ -256,10 +258,11 @@ pub async fn get_providers_minimal() -> Result<HttpResponse, ErrorResponse> {
 #[put("/providers/{id}")]
 pub async fn put_provider(
     id: web::Path<String>,
-    payload: Json<ProviderRequest>,
+    Json(payload): Json<ProviderRequest>,
     principal: ReqPrincipal,
 ) -> Result<HttpResponse, ErrorResponse> {
     principal.validate_admin_session()?;
+    payload.validate()?;
 
     if !payload.use_pkce && payload.client_secret.is_none() {
         return Err(ErrorResponse::new(
@@ -276,7 +279,7 @@ pub async fn put_provider(
         ));
     }
 
-    AuthProvider::update(id.into_inner(), payload.into_inner()).await?;
+    AuthProvider::update(id.into_inner(), payload).await?;
     Ok(HttpResponse::Ok().finish())
 }
 
@@ -434,9 +437,10 @@ pub async fn put_provider_img(
 pub async fn post_provider_link(
     provider_id: web::Path<String>,
     principal: ReqPrincipal,
-    payload: Json<ProviderLoginRequest>,
+    Json(payload): Json<ProviderLoginRequest>,
 ) -> Result<HttpResponse, ErrorResponse> {
     principal.validate_session_auth()?;
+    payload.validate()?;
 
     let user_id = principal.user_id()?.to_string();
     let user = User::find(user_id).await?;
@@ -457,8 +461,7 @@ pub async fn post_provider_link(
     };
 
     // directly redirect to the provider login page
-    let (login_cookie, xsrf_token, location) =
-        AuthProviderCallback::login_start(payload.into_inner()).await?;
+    let (login_cookie, xsrf_token, location) = AuthProviderCallback::login_start(payload).await?;
 
     Ok(HttpResponse::Accepted()
         .insert_header((LOCATION, location))
diff --git a/src/api/src/blacklist.rs b/src/api/src/blacklist.rs
index 5a47a3b69..f52b801b7 100644
--- a/src/api/src/blacklist.rs
+++ b/src/api/src/blacklist.rs
@@ -1,6 +1,6 @@
 use crate::ReqPrincipal;
+use actix_web::web::Json;
 use actix_web::{delete, get, post, web, HttpResponse};
-use actix_web_validator::Json;
 use chrono::DateTime;
 use rauthy_api_types::blacklist::{BlacklistResponse, BlacklistedIp, IpBlacklistRequest};
 use rauthy_error::ErrorResponse;
@@ -9,6 +9,7 @@ use rauthy_models::entity::api_keys::{AccessGroup, AccessRights};
 use rauthy_models::events::event::Event;
 use rauthy_models::events::ip_blacklist_handler::IpBlacklistReq;
 use tokio::sync::oneshot;
+use validator::Validate;
 
 /// Returns all blacklisted IP's
 ///
@@ -69,9 +70,10 @@ pub async fn get_blacklist(
 pub async fn post_blacklist(
     data: web::Data<AppState>,
     principal: ReqPrincipal,
-    payload: Json<IpBlacklistRequest>,
+    Json(payload): Json<IpBlacklistRequest>,
 ) -> Result<HttpResponse, ErrorResponse> {
     principal.validate_api_key_or_admin_session(AccessGroup::Blacklist, AccessRights::Create)?;
+    payload.validate()?;
 
     data.tx_events
         .send_async(Event::ip_blacklisted(
diff --git a/src/api/src/clients.rs b/src/api/src/clients.rs
index cadf046f7..b5fa33112 100644
--- a/src/api/src/clients.rs
+++ b/src/api/src/clients.rs
@@ -2,6 +2,7 @@ use crate::ReqPrincipal;
 use actix_web::http::header::{
     ACCESS_CONTROL_ALLOW_ORIGIN, CACHE_CONTROL, CONTENT_TYPE, WWW_AUTHENTICATE,
 };
+use actix_web::web::Json;
 use actix_web::{delete, get, post, put, web, HttpRequest, HttpResponse};
 use actix_web_lab::__reexports::futures_util::StreamExt;
 use rauthy_api_types::clients::{
@@ -20,6 +21,7 @@ use rauthy_models::entity::logos::{Logo, LogoType};
 use rauthy_service::client;
 use rauthy_service::oidc::helpers;
 use tracing::debug;
+use validator::Validate;
 
 /// Returns all existing OIDC clients with all their information, except for the client secrets.
 ///
@@ -130,12 +132,13 @@ pub async fn get_client_secret(
 )]
 #[post("/clients")]
 pub async fn post_clients(
-    client: actix_web_validator::Json<NewClientRequest>,
+    Json(payload): Json<NewClientRequest>,
     principal: ReqPrincipal,
 ) -> Result<HttpResponse, ErrorResponse> {
     principal.validate_api_key_or_admin_session(AccessGroup::Clients, AccessRights::Create)?;
+    payload.validate()?;
 
-    let client = Client::create(client.into_inner()).await?;
+    let client = Client::create(payload).await?;
     Ok(HttpResponse::Ok().json(ClientResponse::from(client)))
 }
 
@@ -155,12 +158,13 @@ pub async fn post_clients(
 #[post("/clients_dyn")]
 pub async fn post_clients_dyn(
     data: web::Data<AppState>,
-    payload: actix_web_validator::Json<DynamicClientRequest>,
+    Json(payload): Json<DynamicClientRequest>,
     req: HttpRequest,
 ) -> Result<HttpResponse, ErrorResponse> {
     if !*ENABLE_DYN_CLIENT_REG {
         return Ok(HttpResponse::NotFound().finish());
     }
+    payload.validate()?;
 
     if let Some(token) = &*DYN_CLIENT_REG_TOKEN {
         let bearer = helpers::get_bearer_token_from_header(req.headers())?;
@@ -178,14 +182,12 @@ pub async fn post_clients_dyn(
         ClientDyn::rate_limit_ip(ip).await?;
     }
 
-    Client::create_dynamic(&data, payload.into_inner())
-        .await
-        .map(|resp| {
-            HttpResponse::Created()
-                // The registration should be possible from another Web UI by RFC
-                .insert_header((ACCESS_CONTROL_ALLOW_ORIGIN, "*"))
-                .json(resp)
-        })
+    Client::create_dynamic(&data, payload).await.map(|resp| {
+        HttpResponse::Created()
+            // The registration should be possible from another Web UI by RFC
+            .insert_header((ACCESS_CONTROL_ALLOW_ORIGIN, "*"))
+            .json(resp)
+    })
 }
 
 /// GET a dynamic OIDC client
@@ -236,20 +238,21 @@ pub async fn get_clients_dyn(
 #[put("/clients_dyn/{id}")]
 pub async fn put_clients_dyn(
     data: web::Data<AppState>,
-    payload: actix_web_validator::Json<DynamicClientRequest>,
+    Json(payload): Json<DynamicClientRequest>,
     id: web::Path<String>,
     req: HttpRequest,
 ) -> Result<HttpResponse, ErrorResponse> {
     if !*ENABLE_DYN_CLIENT_REG {
         return Ok(HttpResponse::NotFound().finish());
     }
+    payload.validate()?;
 
     let bearer = helpers::get_bearer_token_from_header(req.headers())?;
     let id = id.into_inner();
     let client_dyn = ClientDyn::find(id.clone()).await?;
     client_dyn.validate_token(&bearer)?;
 
-    let resp = Client::update_dynamic(&data, payload.into_inner(), client_dyn).await?;
+    let resp = Client::update_dynamic(&data, payload, client_dyn).await?;
     Ok(HttpResponse::Ok().json(resp))
 }
 
@@ -272,13 +275,14 @@ pub async fn put_clients_dyn(
 )]
 #[put("/clients/{id}")]
 pub async fn put_clients(
-    client: actix_web_validator::Json<UpdateClientRequest>,
+    Json(payload): Json<UpdateClientRequest>,
     path: web::Path<String>,
     principal: ReqPrincipal,
 ) -> Result<HttpResponse, ErrorResponse> {
     principal.validate_api_key_or_admin_session(AccessGroup::Clients, AccessRights::Update)?;
+    payload.validate()?;
 
-    client::update_client(path.into_inner(), client.into_inner())
+    client::update_client(path.into_inner(), payload)
         .await
         .map(|r| HttpResponse::Ok().json(ClientResponse::from(r)))
 }
@@ -329,13 +333,13 @@ pub async fn get_client_colors(
 pub async fn put_client_colors(
     id: web::Path<String>,
     principal: ReqPrincipal,
-    req_data: actix_web_validator::Json<ColorsRequest>,
+    Json(payload): Json<ColorsRequest>,
 ) -> Result<HttpResponse, ErrorResponse> {
     principal.validate_api_key_or_admin_session(AccessGroup::Clients, AccessRights::Update)?;
+    payload.validate()?;
 
-    let colors = req_data.into_inner();
-    colors.validate_css()?;
-    ColorEntity::update(id.as_str(), colors).await?;
+    payload.validate_css()?;
+    ColorEntity::update(id.as_str(), payload).await?;
 
     Ok(HttpResponse::Ok().finish())
 }
diff --git a/src/api/src/events.rs b/src/api/src/events.rs
index cc0843694..5031ebae3 100644
--- a/src/api/src/events.rs
+++ b/src/api/src/events.rs
@@ -1,7 +1,7 @@
 use crate::ReqPrincipal;
+use actix_web::web::{Json, Query};
 use actix_web::{get, post, web, HttpRequest, HttpResponse, Responder};
 use actix_web_lab::sse;
-use actix_web_validator::Json;
 use chrono::Utc;
 use rauthy_api_types::events::{EventsListenParams, EventsRequest};
 use rauthy_common::constants::SSE_KEEP_ALIVE;
@@ -30,12 +30,10 @@ use validator::Validate;
 #[post("/events")]
 pub async fn post_events(
     principal: ReqPrincipal,
-    payload: Json<EventsRequest>,
+    Json(payload): Json<EventsRequest>,
 ) -> Result<HttpResponse, ErrorResponse> {
     principal.validate_api_key_or_admin_session(AccessGroup::Events, AccessRights::Read)?;
-
     payload.validate()?;
-    let payload = payload.into_inner();
 
     let events = Event::find_all(
         payload.from,
@@ -66,11 +64,10 @@ pub async fn post_events(
 pub async fn sse_events(
     data: web::Data<AppState>,
     principal: ReqPrincipal,
-    params: web::Query<EventsListenParams>,
+    params: Query<EventsListenParams>,
     req: HttpRequest,
 ) -> Result<impl Responder, ErrorResponse> {
     principal.validate_api_key_or_admin_session(AccessGroup::Events, AccessRights::Read)?;
-
     params.validate()?;
 
     let ip = real_ip_from_req(&req)?.to_string();
diff --git a/src/api/src/fed_cm.rs b/src/api/src/fed_cm.rs
index d7fa5d435..0e30157a5 100644
--- a/src/api/src/fed_cm.rs
+++ b/src/api/src/fed_cm.rs
@@ -1,5 +1,6 @@
 use actix_web::http::header;
 use actix_web::http::header::{HeaderName, HeaderValue};
+use actix_web::web::{Form, Query};
 use actix_web::{get, post, web, HttpRequest, HttpResponse};
 use chrono::Utc;
 use rauthy_api_types::clients::EphemeralClientRequest;
@@ -22,6 +23,7 @@ use rauthy_models::entity::users::User;
 use rauthy_models::ListenScheme;
 use rauthy_service::token_set::{AuthCodeFlow, AuthTime, DeviceCodeFlow, TokenNonce, TokenSet};
 use tracing::{debug, error, warn};
+use validator::Validate;
 
 const HEADER_ALLOW_CREDENTIALS: (&str, &str) = ("access-control-allow-credentials", "true");
 
@@ -79,12 +81,11 @@ pub async fn get_fed_cm_accounts(req: HttpRequest) -> Result<HttpResponse, Error
 pub async fn get_fed_cm_client_meta(
     data: web::Data<AppState>,
     req: HttpRequest,
-    params: actix_web_validator::Query<FedCMClientMetadataRequest>,
+    Query(params): Query<FedCMClientMetadataRequest>,
 ) -> Result<HttpResponse, ErrorResponse> {
     is_fed_cm_enabled()?;
     is_web_identity_fetch(&req)?;
-
-    let params = params.into_inner();
+    params.validate()?;
 
     if &params.client_id == "rauthy" {
         return Err(ErrorResponse::new(
@@ -238,10 +239,11 @@ pub async fn get_fed_cm_status(req: HttpRequest) -> HttpResponse {
 pub async fn post_fed_cm_token(
     req: HttpRequest,
     data: web::Data<AppState>,
-    payload: actix_web_validator::Form<FedCMAssertionRequest>,
+    Form(payload): Form<FedCMAssertionRequest>,
 ) -> Result<HttpResponse, ErrorResponse> {
     is_fed_cm_enabled()?;
     is_web_identity_fetch(&req)?;
+    payload.validate()?;
 
     let (login_status, user_id) = login_status_from_req(&req).await;
     if login_status == FedCMLoginStatus::LoggedOut {
@@ -250,8 +252,6 @@ pub async fn post_fed_cm_token(
             .finish());
     }
 
-    let payload = payload.into_inner();
-
     // find and check the client
     let client = match Client::find_maybe_ephemeral(payload.client_id).await {
         Ok(c) => c,
diff --git a/src/api/src/generic.rs b/src/api/src/generic.rs
index 552a5f107..7e080b908 100644
--- a/src/api/src/generic.rs
+++ b/src/api/src/generic.rs
@@ -1,6 +1,7 @@
 use crate::{Assets, ReqPrincipal};
 use actix_web::http::header::{HeaderValue, CONTENT_TYPE};
 use actix_web::http::{header, StatusCode};
+use actix_web::web::{Json, Query};
 use actix_web::{get, post, put, web, HttpRequest, HttpResponse, Responder};
 use chrono::Utc;
 use cryptr::EncKeys;
@@ -50,6 +51,7 @@ use std::borrow::Cow;
 use std::ops::{Add, Sub};
 use std::str::FromStr;
 use tracing::{error, info, warn};
+use validator::Validate;
 
 #[get("/")]
 pub async fn get_index(req: HttpRequest) -> Result<HttpResponse, ErrorResponse> {
@@ -95,10 +97,10 @@ pub async fn get_static_assets(
 pub async fn post_i18n(
     req: HttpRequest,
     // no validation needed for I18nRequest
-    req_data: web::Json<I18nRequest>,
+    Json(payload): Json<I18nRequest>,
 ) -> Result<HttpResponse, ErrorResponse> {
     let lang = Language::try_from(&req).unwrap_or_default();
-    let body = match req_data.content {
+    let body = match payload.content {
         I18nContent::Authorize => I18nAuthorize::build(&lang).as_json(),
         I18nContent::Account => I18nAccount::build(&lang).as_json(),
         I18nContent::Device => I18nDevice::build(&lang).as_json(),
@@ -341,12 +343,13 @@ pub async fn post_migrate_enc_key(
     data: web::Data<AppState>,
     req: HttpRequest,
     principal: ReqPrincipal,
-    req_data: actix_web_validator::Json<EncKeyMigrateRequest>,
+    Json(payload): Json<EncKeyMigrateRequest>,
 ) -> Result<HttpResponse, ErrorResponse> {
     principal.validate_api_key_or_admin_session(AccessGroup::Secrets, AccessRights::Update)?;
-    let ip = real_ip_from_req(&req)?;
+    payload.validate()?;
 
-    encryption::migrate_encryption_alg(&data, &req_data.key_id).await?;
+    let ip = real_ip_from_req(&req)?;
+    encryption::migrate_encryption_alg(&data, &payload.key_id).await?;
 
     data.tx_events
         .send_async(Event::secrets_migrated(ip))
@@ -418,11 +421,12 @@ pub async fn get_login_time(
 #[post("/password_hash_times")]
 pub async fn post_password_hash_times(
     principal: ReqPrincipal,
-    req_data: actix_web_validator::Json<PasswordHashTimesRequest>,
+    Json(payload): Json<PasswordHashTimesRequest>,
 ) -> Result<HttpResponse, ErrorResponse> {
     principal.validate_api_key_or_admin_session(AccessGroup::Generic, AccessRights::Create)?;
+    payload.validate()?;
 
-    PasswordHashTimes::compute(req_data.into_inner())
+    PasswordHashTimes::compute(payload)
         .await
         .map(|r| HttpResponse::Ok().json(r))
 }
@@ -466,12 +470,13 @@ pub async fn get_password_policy(principal: ReqPrincipal) -> Result<HttpResponse
 #[put("/password_policy")]
 pub async fn put_password_policy(
     principal: ReqPrincipal,
-    req_data: actix_web_validator::Json<PasswordPolicyRequest>,
+    Json(payload): Json<PasswordPolicyRequest>,
 ) -> Result<HttpResponse, ErrorResponse> {
     principal.validate_api_key_or_admin_session(AccessGroup::Secrets, AccessRights::Update)?;
+    payload.validate()?;
 
     let mut rules = PasswordPolicy::find().await?;
-    rules.apply_req(req_data.into_inner());
+    rules.apply_req(payload);
     rules.save().await?;
     Ok(HttpResponse::Ok().json(PasswordPolicyResponse::from(rules)))
 }
@@ -520,10 +525,11 @@ pub async fn post_pow() -> Result<HttpResponse, ErrorResponse> {
 )]
 #[get("/search")]
 pub async fn get_search(
-    params: actix_web_validator::Query<SearchParams>,
+    Query(params): Query<SearchParams>,
     principal: ReqPrincipal,
 ) -> Result<HttpResponse, ErrorResponse> {
     principal.validate_admin_session()?;
+    params.validate()?;
 
     let limit = params.limit.unwrap_or(100) as i64;
     match params.ty {
diff --git a/src/api/src/groups.rs b/src/api/src/groups.rs
index 8843295c9..af8a798d0 100644
--- a/src/api/src/groups.rs
+++ b/src/api/src/groups.rs
@@ -1,9 +1,11 @@
 use crate::ReqPrincipal;
+use actix_web::web::Json;
 use actix_web::{delete, get, post, put, web, HttpResponse};
 use rauthy_api_types::groups::NewGroupRequest;
 use rauthy_error::ErrorResponse;
 use rauthy_models::entity::api_keys::{AccessGroup, AccessRights};
 use rauthy_models::entity::groups::Group;
+use validator::Validate;
 
 /// Returns all existing *groups*
 ///
@@ -45,12 +47,13 @@ pub async fn get_groups(principal: ReqPrincipal) -> Result<HttpResponse, ErrorRe
 )]
 #[post("/groups")]
 pub async fn post_group(
-    group_req: actix_web_validator::Json<NewGroupRequest>,
+    Json(payload): Json<NewGroupRequest>,
     principal: ReqPrincipal,
 ) -> Result<HttpResponse, ErrorResponse> {
     principal.validate_api_key_or_admin_session(AccessGroup::Groups, AccessRights::Create)?;
+    payload.validate()?;
 
-    Group::create(group_req.into_inner())
+    Group::create(payload)
         .await
         .map(|r| HttpResponse::Ok().json(r))
 }
@@ -73,12 +76,13 @@ pub async fn post_group(
 #[put("/groups/{id}")]
 pub async fn put_group(
     id: web::Path<String>,
-    group_req: actix_web_validator::Json<NewGroupRequest>,
+    Json(payload): Json<NewGroupRequest>,
     principal: ReqPrincipal,
 ) -> Result<HttpResponse, ErrorResponse> {
     principal.validate_api_key_or_admin_session(AccessGroup::Groups, AccessRights::Update)?;
+    payload.validate()?;
 
-    Group::update(id.into_inner(), group_req.group.to_owned())
+    Group::update(id.into_inner(), payload.group)
         .await
         .map(|g| HttpResponse::Ok().json(g))
 }
diff --git a/src/api/src/oidc.rs b/src/api/src/oidc.rs
index ae9e53a49..6f52392c1 100644
--- a/src/api/src/oidc.rs
+++ b/src/api/src/oidc.rs
@@ -3,6 +3,7 @@ use actix_web::cookie::time::OffsetDateTime;
 use actix_web::cookie::SameSite;
 use actix_web::http::header::{HeaderValue, CONTENT_TYPE};
 use actix_web::http::{header, StatusCode};
+use actix_web::web::{Form, Json, Query};
 use actix_web::{get, post, web, HttpRequest, HttpResponse, HttpResponseBuilder, ResponseError};
 use chrono::Utc;
 use rauthy_api_types::oidc::{
@@ -51,6 +52,7 @@ use std::borrow::Cow;
 use std::ops::Add;
 use std::time::{SystemTime, UNIX_EPOCH};
 use tracing::{debug, error};
+use validator::Validate;
 
 /// OIDC Authorization HTML
 ///
@@ -70,10 +72,12 @@ use tracing::{debug, error};
 pub async fn get_authorize(
     data: web::Data<AppState>,
     req: HttpRequest,
-    req_data: actix_web_validator::Query<AuthRequest>,
+    Query(params): Query<AuthRequest>,
     principal: ReqPrincipal,
 ) -> Result<HttpResponse, ErrorResponse> {
-    let colors = ColorEntity::find(&req_data.client_id)
+    params.validate()?;
+
+    let colors = ColorEntity::find(&params.client_id)
         .await
         .unwrap_or_default();
     let lang = Language::try_from(&req).unwrap_or_default();
@@ -81,10 +85,10 @@ pub async fn get_authorize(
     let (client, origin_header) = match validation::validate_auth_req_param(
         &data,
         &req,
-        &req_data.client_id,
-        &req_data.redirect_uri,
-        &req_data.code_challenge,
-        &req_data.code_challenge_method,
+        &params.client_id,
+        &params.redirect_uri,
+        &params.code_challenge,
+        &params.code_challenge_method,
     )
     .await
     {
@@ -97,14 +101,14 @@ pub async fn get_authorize(
     };
 
     // check prompt and max_age to possibly force a new session
-    let mut force_new_session = if req_data
+    let mut force_new_session = if params
         .prompt
         .as_ref()
         .map(|p| p.as_str() == "login")
         .unwrap_or(false)
     {
         true
-    } else if let Some(max_age) = req_data.max_age {
+    } else if let Some(max_age) = params.max_age {
         if let Some(session) = &principal.session {
             let session_created = session.exp - *SESSION_LIFETIME as i64;
             Utc::now().timestamp() > session_created + max_age
@@ -133,7 +137,7 @@ pub async fn get_authorize(
 
     // check for `prompt=no-prompt`
     if !force_new_session
-        && req_data
+        && params
             .prompt
             .as_ref()
             .map(|p| p.as_str() == "none")
@@ -249,12 +253,11 @@ pub async fn get_authorize(
 pub async fn post_authorize(
     data: web::Data<AppState>,
     req: HttpRequest,
-    payload: actix_web_validator::Json<LoginRequest>,
+    Json(payload): Json<LoginRequest>,
     principal: ReqPrincipal,
 ) -> Result<HttpResponse, ErrorResponse> {
-    debug!("Validating session in auth or init state");
     principal.validate_session_auth_or_init()?;
-    debug!("session is in auth or init state");
+    payload.validate()?;
 
     // TODO refactor login delay to use Instant, which is a bit cleaner
     let start = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
@@ -268,7 +271,7 @@ pub async fn post_authorize(
     let res = match authorize::post_authorize(
         &data,
         &req,
-        payload.into_inner(),
+        payload,
         session.clone(),
         &mut has_password_been_hashed,
         &mut add_login_delay,
@@ -323,24 +326,24 @@ pub async fn post_authorize(
 pub async fn post_authorize_refresh(
     data: web::Data<AppState>,
     req: HttpRequest,
-    req_data: actix_web_validator::Json<LoginRefreshRequest>,
+    Json(payload): Json<LoginRefreshRequest>,
     principal: ReqPrincipal,
 ) -> Result<HttpResponse, ErrorResponse> {
     let session = principal.validate_session_auth()?;
+    payload.validate()?;
 
     let (client, header_origin) = validation::validate_auth_req_param(
         &data,
         &req,
-        &req_data.client_id,
-        &req_data.redirect_uri,
-        &req_data.code_challenge,
-        &req_data.code_challenge_method,
+        &payload.client_id,
+        &payload.redirect_uri,
+        &payload.code_challenge,
+        &payload.code_challenge_method,
     )
     .await?;
 
     let auth_step =
-        authorize::post_authorize_refresh(session, client, header_origin, req_data.into_inner())
-            .await?;
+        authorize::post_authorize_refresh(session, client, header_origin, payload).await?;
     map_auth_step(auth_step, &req).await
 }
 
@@ -404,8 +407,12 @@ pub async fn get_cert_by_kid(kid: web::Path<String>) -> Result<HttpResponse, Err
 #[post("/oidc/device")]
 pub async fn post_device_auth(
     req: HttpRequest,
-    payload: actix_web_validator::Form<DeviceGrantRequest>,
+    Form(payload): Form<DeviceGrantRequest>,
 ) -> HttpResponse {
+    if let Err(err) = payload.validate() {
+        return ErrorResponse::from(err).error_response();
+    }
+
     // handle ip rate-limiting
     if DEVICE_GRANT_RATE_LIMIT.is_some() {
         match real_ip_from_req(&req) {
@@ -454,7 +461,6 @@ pub async fn post_device_auth(
     }
 
     // find and validate the client
-    let payload = payload.into_inner();
     let client = match Client::find(payload.client_id).await {
         Ok(client) => client,
         Err(_) => {
@@ -546,12 +552,12 @@ pub async fn post_device_auth(
 #[post("/oidc/device/verify")]
 #[tracing::instrument(level = "debug", skip_all, fields(user_code = payload.user_code))]
 pub async fn post_device_verify(
-    payload: actix_web_validator::Json<DeviceVerifyRequest>,
+    Json(payload): Json<DeviceVerifyRequest>,
     principal: ReqPrincipal,
 ) -> Result<HttpResponse, ErrorResponse> {
     principal.validate_session_auth()?;
+    payload.validate()?;
 
-    let payload = payload.into_inner();
     debug!("{:?}", payload);
 
     let challenge = Pow::validate(&payload.pow)?;
@@ -601,9 +607,13 @@ pub async fn post_device_verify(
 pub async fn get_logout(
     data: web::Data<AppState>,
     req: HttpRequest,
-    req_data: actix_web_validator::Query<LogoutRequest>,
+    Query(params): Query<LogoutRequest>,
     principal: ReqPrincipal,
 ) -> HttpResponse {
+    if let Err(err) = params.validate() {
+        return ErrorResponse::from(err).error_response();
+    }
+
     // If we get any logout errors, maybe because there is no session anymore or whatever happens,
     // just redirect to rauthy's root page, since the user is not logged in anyway anymore.
     let session = match principal.get_session() {
@@ -616,7 +626,7 @@ pub async fn get_logout(
     };
 
     let lang = Language::try_from(&req).unwrap_or_default();
-    let body = match logout::get_logout_html(req_data.into_inner(), session, &data, &lang).await {
+    let body = match logout::get_logout_html(params, session, &data, &lang).await {
         Ok(t) => t,
         Err(_) => {
             return HttpResponse::build(StatusCode::from_u16(302).unwrap())
@@ -646,10 +656,12 @@ pub async fn get_logout(
 )]
 #[post("/oidc/logout")]
 pub async fn post_logout(
-    req_data: actix_web_validator::Query<LogoutRequest>,
+    Query(params): Query<LogoutRequest>,
     principal: ReqPrincipal,
 ) -> Result<HttpResponse, ErrorResponse> {
     let session = principal.get_session()?.clone();
+    params.validate()?;
+
     let cookie_fed_cm = ApiCookie::build_with_same_site(
         COOKIE_SESSION_FED_CM,
         Cow::from(&session.id),
@@ -660,15 +672,15 @@ pub async fn post_logout(
     let cookie = ApiCookie::build(COOKIE_SESSION, &sid, 0);
     session.invalidate().await?;
 
-    if req_data.post_logout_redirect_uri.is_some() {
-        let state = if req_data.state.is_some() {
-            req_data.state.as_ref().unwrap().as_str()
+    if params.post_logout_redirect_uri.is_some() {
+        let state = if params.state.is_some() {
+            params.state.as_ref().unwrap().as_str()
         } else {
             ""
         };
         let loc = format!(
             "{}?state={}",
-            req_data.post_logout_redirect_uri.as_ref().unwrap(),
+            params.post_logout_redirect_uri.as_ref().unwrap(),
             state
         );
         return Ok(HttpResponse::build(StatusCode::MOVED_PERMANENTLY)
@@ -883,21 +895,23 @@ pub async fn get_session_xsrf(
 pub async fn post_token(
     req: HttpRequest,
     data: web::Data<AppState>,
-    payload: actix_web_validator::Form<TokenRequest>,
+    Form(payload): Form<TokenRequest>,
 ) -> Result<HttpResponse, ErrorResponse> {
+    payload.validate()?;
+
     let ip = real_ip_from_req(&req)?;
 
     if payload.grant_type == GRANT_TYPE_DEVICE_CODE {
         // the `urn:ietf:params:oauth:grant-type:device_code` needs
         // a fully customized handling here with customized error response
         // to meet the oauth rfc
-        return Ok(oidc::grant_type_device_code(&data, ip, payload.into_inner()).await);
+        return Ok(oidc::grant_type_device_code(&data, ip, payload).await);
     }
 
     let start = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
     let has_password_been_hashed = payload.grant_type == "password";
 
-    let res = match oidc::get_token_set(payload.into_inner(), &data, req).await {
+    let res = match oidc::get_token_set(payload, &data, req).await {
         Ok((token_set, headers)) => {
             let mut builder = HttpResponseBuilder::new(StatusCode::OK);
             for h in headers {
@@ -945,9 +959,11 @@ pub async fn post_token(
 pub async fn post_token_introspect(
     data: web::Data<AppState>,
     req: HttpRequest,
-    req_data: actix_web_validator::Form<TokenValidationRequest>,
+    Form(payload): Form<TokenValidationRequest>,
 ) -> Result<HttpResponse, ErrorResponse> {
-    match token_info::get_token_info(&data, &req, &req_data.token).await {
+    payload.validate()?;
+
+    match token_info::get_token_info(&data, &req, &payload.token).await {
         Ok(info) => Ok(HttpResponse::Ok().json(info)),
         Err(err) => {
             error!("{:?}", err);
@@ -974,9 +990,11 @@ pub async fn post_token_introspect(
 #[post("/oidc/token/validate")]
 pub async fn post_validate_token(
     data: web::Data<AppState>,
-    req_data: actix_web_validator::Json<TokenValidationRequest>,
+    Json(payload): Json<TokenValidationRequest>,
 ) -> Result<HttpResponse, ErrorResponse> {
-    validation::validate_token::<JwtCommonClaims>(&data, &req_data.token)
+    payload.validate()?;
+
+    validation::validate_token::<JwtCommonClaims>(&data, &payload.token)
         .await
         .map(|_| HttpResponse::Accepted().finish())
 }
diff --git a/src/api/src/roles.rs b/src/api/src/roles.rs
index 4d96512e5..b5ea8e852 100644
--- a/src/api/src/roles.rs
+++ b/src/api/src/roles.rs
@@ -1,9 +1,11 @@
 use crate::ReqPrincipal;
+use actix_web::web::Json;
 use actix_web::{delete, get, post, put, web, HttpResponse};
 use rauthy_api_types::roles::NewRoleRequest;
 use rauthy_error::ErrorResponse;
 use rauthy_models::entity::api_keys::{AccessGroup, AccessRights};
 use rauthy_models::entity::roles::Role;
+use validator::Validate;
 
 /// Returns all existing roles
 ///
@@ -45,12 +47,13 @@ pub async fn get_roles(principal: ReqPrincipal) -> Result<HttpResponse, ErrorRes
 )]
 #[post("/roles")]
 pub async fn post_role(
-    role_req: actix_web_validator::Json<NewRoleRequest>,
+    Json(payload): Json<NewRoleRequest>,
     principal: ReqPrincipal,
 ) -> Result<HttpResponse, ErrorResponse> {
     principal.validate_api_key_or_admin_session(AccessGroup::Roles, AccessRights::Create)?;
+    payload.validate()?;
 
-    Role::create(role_req.into_inner())
+    Role::create(payload)
         .await
         .map(|r| HttpResponse::Ok().json(r))
 }
@@ -73,12 +76,13 @@ pub async fn post_role(
 #[put("/roles/{id}")]
 pub async fn put_role(
     id: web::Path<String>,
-    role_req: actix_web_validator::Json<NewRoleRequest>,
+    Json(payload): Json<NewRoleRequest>,
     principal: ReqPrincipal,
 ) -> Result<HttpResponse, ErrorResponse> {
     principal.validate_api_key_or_admin_session(AccessGroup::Roles, AccessRights::Update)?;
+    payload.validate()?;
 
-    Role::update(id.into_inner(), role_req.role.to_owned())
+    Role::update(id.into_inner(), payload.role)
         .await
         .map(|r| HttpResponse::Ok().json(r))
 }
diff --git a/src/api/src/scopes.rs b/src/api/src/scopes.rs
index 7b1bf737f..82c1f7da3 100644
--- a/src/api/src/scopes.rs
+++ b/src/api/src/scopes.rs
@@ -1,10 +1,12 @@
 use crate::ReqPrincipal;
+use actix_web::web::Json;
 use actix_web::{delete, get, post, put, web, HttpResponse};
 use rauthy_api_types::scopes::{ScopeRequest, ScopeResponse};
 use rauthy_error::ErrorResponse;
 use rauthy_models::app_state::AppState;
 use rauthy_models::entity::api_keys::{AccessGroup, AccessRights};
 use rauthy_models::entity::scopes::Scope;
+use validator::Validate;
 
 /// Returns all existing scopes
 ///
@@ -52,11 +54,12 @@ pub async fn get_scopes(principal: ReqPrincipal) -> Result<HttpResponse, ErrorRe
 pub async fn post_scope(
     data: web::Data<AppState>,
     principal: ReqPrincipal,
-    scope_req: actix_web_validator::Json<ScopeRequest>,
+    Json(payload): Json<ScopeRequest>,
 ) -> Result<HttpResponse, ErrorResponse> {
     principal.validate_api_key_or_admin_session(AccessGroup::Scopes, AccessRights::Create)?;
+    payload.validate()?;
 
-    Scope::create(&data, scope_req.into_inner())
+    Scope::create(&data, payload)
         .await
         .map(|s| HttpResponse::Ok().json(s))
 }
@@ -81,11 +84,12 @@ pub async fn put_scope(
     data: web::Data<AppState>,
     path: web::Path<String>,
     principal: ReqPrincipal,
-    scope_req: actix_web_validator::Json<ScopeRequest>,
+    Json(payload): Json<ScopeRequest>,
 ) -> Result<HttpResponse, ErrorResponse> {
     principal.validate_api_key_or_admin_session(AccessGroup::Scopes, AccessRights::Update)?;
+    payload.validate()?;
 
-    Scope::update(&data, path.as_str(), scope_req.into_inner())
+    Scope::update(&data, path.as_str(), payload)
         .await
         .map(|s| HttpResponse::Ok().json(ScopeResponse::from(s)))
 }
diff --git a/src/api/src/sessions.rs b/src/api/src/sessions.rs
index 392a1e031..eba29fb41 100644
--- a/src/api/src/sessions.rs
+++ b/src/api/src/sessions.rs
@@ -1,6 +1,6 @@
 use crate::ReqPrincipal;
+use actix_web::web::Query;
 use actix_web::{delete, get, web, HttpResponse};
-use actix_web_validator::Query;
 use rauthy_api_types::generic::PaginationParams;
 use rauthy_api_types::sessions::{SessionResponse, SessionState};
 use rauthy_common::constants::SSP_THRESHOLD;
@@ -10,6 +10,7 @@ use rauthy_models::entity::continuation_token::ContinuationToken;
 use rauthy_models::entity::refresh_tokens::RefreshToken;
 use rauthy_models::entity::sessions::Session;
 use rauthy_models::entity::users::User;
+use validator::Validate;
 
 /// Returns all existing sessions
 ///
@@ -31,9 +32,10 @@ use rauthy_models::entity::users::User;
 #[get("/sessions")]
 pub async fn get_sessions(
     principal: ReqPrincipal,
-    params: Query<PaginationParams>,
+    Query(params): Query<PaginationParams>,
 ) -> Result<HttpResponse, ErrorResponse> {
     principal.validate_api_key_or_admin_session(AccessGroup::Sessions, AccessRights::Read)?;
+    params.validate()?;
 
     // sessions will be dynamically paginated based on the same setting as users
     let user_count = User::count().await?;
diff --git a/src/api/src/users.rs b/src/api/src/users.rs
index f07252e66..fd3d4790e 100644
--- a/src/api/src/users.rs
+++ b/src/api/src/users.rs
@@ -1,8 +1,8 @@
 use crate::ReqPrincipal;
 use actix_web::http::header::{ACCEPT, LOCATION};
 use actix_web::http::StatusCode;
+use actix_web::web::{Json, Query};
 use actix_web::{delete, get, post, put, web, HttpRequest, HttpResponse, ResponseError};
-use actix_web_validator::{Json, Query};
 use rauthy_api_types::generic::{PaginationParams, PasswordPolicyResponse};
 use rauthy_api_types::oidc::PasswordResetResponse;
 use rauthy_api_types::users::{
@@ -41,6 +41,7 @@ use rauthy_models::templates::{Error1Html, Error3Html, ErrorHtml, UserRegisterHt
 use rauthy_service::password_reset;
 use spow::pow::Pow;
 use tracing::{error, warn};
+use validator::Validate;
 
 /// Returns all existing users
 ///
@@ -66,9 +67,10 @@ use tracing::{error, warn};
 #[get("/users")]
 pub async fn get_users(
     principal: ReqPrincipal,
-    params: Query<PaginationParams>,
+    Query(params): Query<PaginationParams>,
 ) -> Result<HttpResponse, ErrorResponse> {
     principal.validate_api_key_or_admin_session(AccessGroup::Users, AccessRights::Read)?;
+    params.validate()?;
 
     let user_count = User::count().await?;
 
@@ -128,11 +130,12 @@ pub async fn post_users(
     data: web::Data<AppState>,
     req: HttpRequest,
     principal: ReqPrincipal,
-    user: Json<NewUserRequest>,
+    Json(payload): Json<NewUserRequest>,
 ) -> Result<HttpResponse, ErrorResponse> {
     principal.validate_api_key_or_admin_session(AccessGroup::Users, AccessRights::Create)?;
+    payload.validate()?;
 
-    let user = User::create_from_new(&data, user.into_inner()).await?;
+    let user = User::create_from_new(&data, payload).await?;
 
     data.tx_events
         .send_async(Event::new_user(
@@ -189,12 +192,13 @@ pub async fn get_cust_attr(principal: ReqPrincipal) -> Result<HttpResponse, Erro
 #[post("/users/attr")]
 pub async fn post_cust_attr(
     principal: ReqPrincipal,
-    req_data: Json<UserAttrConfigRequest>,
+    Json(payload): Json<UserAttrConfigRequest>,
 ) -> Result<HttpResponse, ErrorResponse> {
     principal
         .validate_api_key_or_admin_session(AccessGroup::UserAttributes, AccessRights::Create)?;
+    payload.validate()?;
 
-    UserAttrConfigEntity::create(req_data.into_inner())
+    UserAttrConfigEntity::create(payload)
         .await
         .map(|attr| HttpResponse::Ok().json(attr))
 }
@@ -216,12 +220,13 @@ pub async fn post_cust_attr(
 pub async fn put_cust_attr(
     path: web::Path<String>,
     principal: ReqPrincipal,
-    req_data: Json<UserAttrConfigRequest>,
+    Json(payload): Json<UserAttrConfigRequest>,
 ) -> Result<HttpResponse, ErrorResponse> {
     principal
         .validate_api_key_or_admin_session(AccessGroup::UserAttributes, AccessRights::Update)?;
+    payload.validate()?;
 
-    UserAttrConfigEntity::update(path.into_inner(), req_data.into_inner())
+    UserAttrConfigEntity::update(path.into_inner(), payload)
         .await
         .map(|a| HttpResponse::Ok().json(a))
 }
@@ -299,7 +304,7 @@ pub async fn get_users_register(req: HttpRequest) -> Result<HttpResponse, ErrorR
 pub async fn post_users_register(
     data: web::Data<AppState>,
     req: HttpRequest,
-    req_data: Json<NewUserRegistrationRequest>,
+    Json(payload): Json<NewUserRegistrationRequest>,
 ) -> Result<HttpResponse, ErrorResponse> {
     if !*OPEN_USER_REG {
         return Err(ErrorResponse::new(
@@ -307,8 +312,10 @@ pub async fn post_users_register(
             "Open User Registration is not allowed".to_string(),
         ));
     }
+    payload.validate()?;
+
     if let Some(restriction) = &*USER_REG_DOMAIN_RESTRICTION {
-        if !req_data.email.ends_with(restriction) {
+        if !payload.email.ends_with(restriction) {
             return Err(ErrorResponse::new(
                 ErrorResponseType::BadRequest,
                 format!(
@@ -319,7 +326,7 @@ pub async fn post_users_register(
         }
     } else if let Some(blacklist) = &*USER_REG_DOMAIN_BLACKLIST {
         for blacklisted in blacklist {
-            if req_data.email.ends_with(blacklisted) {
+            if payload.email.ends_with(blacklisted) {
                 return Err(ErrorResponse::new(
                     ErrorResponseType::BadRequest,
                     "Domain is blacklisted",
@@ -327,7 +334,7 @@ pub async fn post_users_register(
             }
         }
     }
-    if let Some(redirect_uri) = &req_data.redirect_uri {
+    if let Some(redirect_uri) = &payload.redirect_uri {
         if !*USER_REG_OPEN_REDIRECT {
             let mut allow = false;
             for uri in Client::find_all_client_uris().await? {
@@ -346,11 +353,11 @@ pub async fn post_users_register(
     }
 
     // validate the PoW
-    let challenge = Pow::validate(&req_data.pow)?;
+    let challenge = Pow::validate(&payload.pow)?;
     PowEntity::check_prevent_reuse(challenge.to_string()).await?;
 
     let lang = Language::try_from(&req).unwrap_or_default();
-    let user = User::create_from_reg(&data, req_data.into_inner(), lang).await?;
+    let user = User::create_from_reg(&data, payload, lang).await?;
 
     data.tx_events
         .send_async(Event::new_user(
@@ -438,12 +445,13 @@ pub async fn get_user_attr(
 pub async fn put_user_attr(
     path: web::Path<String>,
     principal: ReqPrincipal,
-    req_data: Json<UserAttrValuesUpdateRequest>,
+    Json(payload): Json<UserAttrValuesUpdateRequest>,
 ) -> Result<HttpResponse, ErrorResponse> {
     principal
         .validate_api_key_or_admin_session(AccessGroup::UserAttributes, AccessRights::Update)?;
+    payload.validate()?;
 
-    let values = UserAttrValueEntity::update_for_user(&path.into_inner(), req_data.into_inner())
+    let values = UserAttrValueEntity::update_for_user(&path.into_inner(), payload)
         .await?
         .drain(..)
         .map(UserAttrValueResponse::from)
@@ -494,12 +502,12 @@ pub async fn get_user_devices(
 pub async fn put_user_device_name(
     path: web::Path<String>,
     principal: ReqPrincipal,
-    payload: actix_web_validator::Json<DeviceRequest>,
+    Json(payload): Json<DeviceRequest>,
 ) -> Result<HttpResponse, ErrorResponse> {
     let user_id = path.into_inner();
     principal.validate_user_or_admin(&user_id)?;
+    payload.validate()?;
 
-    let payload = payload.into_inner();
     if let Some(name) = &payload.name {
         DeviceEntity::update_name(&payload.device_id, &user_id, name).await?;
     }
@@ -524,12 +532,12 @@ pub async fn put_user_device_name(
 pub async fn delete_user_device(
     path: web::Path<String>,
     principal: ReqPrincipal,
-    payload: actix_web_validator::Json<DeviceRequest>,
+    Json(payload): Json<DeviceRequest>,
 ) -> Result<HttpResponse, ErrorResponse> {
     let user_id = path.into_inner();
     principal.validate_user_or_admin(&user_id)?;
+    payload.validate()?;
 
-    let payload = payload.into_inner();
     let device = DeviceEntity::find(&payload.device_id).await?;
     if device.user_id.as_deref() != Some(&user_id) {
         return Err(ErrorResponse::new(
@@ -664,29 +672,26 @@ pub async fn put_user_password_reset(
     data: web::Data<AppState>,
     path: web::Path<String>,
     req: HttpRequest,
-    req_data: Json<PasswordResetRequest>,
+    Json(payload): Json<PasswordResetRequest>,
 ) -> Result<HttpResponse, ErrorResponse> {
-    password_reset::handle_put_user_password_reset(
-        &data,
-        req,
-        path.into_inner(),
-        req_data.into_inner(),
-    )
-    .await
-    .map(|(cookie, location)| {
-        if let Some(loc) = location {
-            HttpResponse::Ok()
-                .insert_header((LOCATION, loc))
-                .cookie(cookie)
-                .status(StatusCode::ACCEPTED)
-                .finish()
-        } else {
-            HttpResponse::Ok()
-                .cookie(cookie)
-                .status(StatusCode::ACCEPTED)
-                .finish()
-        }
-    })
+    payload.validate()?;
+
+    password_reset::handle_put_user_password_reset(&data, req, path.into_inner(), payload)
+        .await
+        .map(|(cookie, location)| {
+            if let Some(loc) = location {
+                HttpResponse::Ok()
+                    .insert_header((LOCATION, loc))
+                    .cookie(cookie)
+                    .status(StatusCode::ACCEPTED)
+                    .finish()
+            } else {
+                HttpResponse::Ok()
+                    .cookie(cookie)
+                    .status(StatusCode::ACCEPTED)
+                    .finish()
+            }
+        })
 }
 
 /// Get all registered Webauthn Passkeys for a user
@@ -752,10 +757,11 @@ pub async fn post_webauthn_auth_start(
     // fully new / different browser which does not have any lefter data or cookies
     principal: ReqPrincipal,
     req: HttpRequest,
-    req_data: Json<WebauthnAuthStartRequest>,
+    Json(payload): Json<WebauthnAuthStartRequest>,
 ) -> Result<HttpResponse, ErrorResponse> {
-    let purpose = req_data.into_inner().purpose;
-    let id = match purpose {
+    payload.validate()?;
+
+    let id = match payload.purpose {
         // only for a Login purpose, this can be accessed without authentication (yet)
         MfaPurpose::Login(_) => {
             // During Login, the session is allowed to be in init only state
@@ -799,7 +805,7 @@ pub async fn post_webauthn_auth_start(
         }
     };
 
-    webauthn::auth_start(&data, id, purpose)
+    webauthn::auth_start(&data, id, payload.purpose)
         .await
         .map(|res| HttpResponse::Ok().json(res))
 }
@@ -823,15 +829,17 @@ pub async fn post_webauthn_auth_start(
 pub async fn post_webauthn_auth_finish(
     data: web::Data<AppState>,
     id: web::Path<String>,
-    req_data: Json<WebauthnAuthFinishRequest>,
+    Json(payload): Json<WebauthnAuthFinishRequest>,
 ) -> Result<HttpResponse, ErrorResponse> {
+    payload.validate()?;
+
     let id = id.into_inner();
 
     // We do not need to further validate the principal here.
     // All of this is done at the /start endpoint.
     // This here will simply fail, if the secret code from the /start does not exist.
 
-    let res = webauthn::auth_finish(&data, id, req_data.into_inner()).await?;
+    let res = webauthn::auth_finish(&data, id, payload).await?;
     Ok(res.into_response())
 }
 
@@ -937,18 +945,14 @@ pub async fn post_webauthn_reg_start(
     id: web::Path<String>,
     principal: ReqPrincipal,
     req: HttpRequest,
-    req_data: Json<WebauthnRegStartRequest>,
+    Json(payload): Json<WebauthnRegStartRequest>,
 ) -> Result<HttpResponse, ErrorResponse> {
+    payload.validate()?;
+
     // If we have a magic link ID in the payload, we do not validate the active session / principal.
     // This is mandatory to make registering a passkey for a completely new account work.
-    if req_data.magic_link_id.is_some() && req.headers().get(PWD_CSRF_HEADER).is_some() {
-        password_reset::handle_put_user_passkey_start(
-            &data,
-            req,
-            id.into_inner(),
-            req_data.into_inner(),
-        )
-        .await
+    if payload.magic_link_id.is_some() && req.headers().get(PWD_CSRF_HEADER).is_some() {
+        password_reset::handle_put_user_passkey_start(&data, req, id.into_inner(), payload).await
     } else {
         principal.validate_session_auth()?;
         // this endpoint is a CSRF check exception inside the Principal Middleware -> check here!
@@ -958,7 +962,7 @@ pub async fn post_webauthn_reg_start(
         let id = id.into_inner();
         principal.is_user(&id)?;
 
-        webauthn::reg_start(&data, id, req_data.into_inner())
+        webauthn::reg_start(&data, id, payload)
             .await
             .map(|ccr| HttpResponse::Ok().json(ccr))
     }
@@ -986,18 +990,14 @@ pub async fn post_webauthn_reg_finish(
     id: web::Path<String>,
     principal: ReqPrincipal,
     req: HttpRequest,
-    req_data: Json<WebauthnRegFinishRequest>,
+    Json(payload): Json<WebauthnRegFinishRequest>,
 ) -> Result<HttpResponse, ErrorResponse> {
+    payload.validate()?;
+
     // If we have a magic link ID in the payload, we do not validate the active session / principal.
     // This is mandatory to make registering a passkey for a completely new account work.
-    if req_data.magic_link_id.is_some() {
-        password_reset::handle_put_user_passkey_finish(
-            &data,
-            req,
-            id.into_inner(),
-            req_data.into_inner(),
-        )
-        .await
+    if payload.magic_link_id.is_some() {
+        password_reset::handle_put_user_passkey_finish(&data, req, id.into_inner(), payload).await
     } else {
         principal.validate_session_auth()?;
         // this endpoint is a CSRF check exception inside the Principal Middleware -> check here!
@@ -1007,7 +1007,7 @@ pub async fn post_webauthn_reg_finish(
         let id = id.into_inner();
         principal.is_user(&id)?;
 
-        webauthn::reg_finish(&data, id, req_data.into_inner()).await?;
+        webauthn::reg_finish(&data, id, payload).await?;
         Ok(HttpResponse::Created().finish())
     }
 }
@@ -1107,19 +1107,18 @@ pub async fn get_user_webid_data(
 pub async fn put_user_webid_data(
     id: web::Path<String>,
     principal: ReqPrincipal,
-    payload: Json<WebIdRequest>,
+    Json(payload): Json<WebIdRequest>,
 ) -> Result<HttpResponse, ErrorResponse> {
-    // check if webid's are enabled globally
     if !*ENABLE_WEB_ID {
         return Ok(HttpResponse::MethodNotAllowed().finish());
     }
+    payload.validate()?;
 
     // check auth
     principal.validate_session_auth()?;
     let id = id.into_inner();
     principal.is_user(&id)?;
 
-    let payload = payload.into_inner();
     let web_id = WebId::try_new(id, payload.custom_triples.as_deref(), payload.expose_email)
         .map_err(|e| {
             ErrorResponse::new(
@@ -1158,12 +1157,12 @@ pub async fn put_user_webid_data(
 pub async fn post_user_password_request_reset(
     data: web::Data<AppState>,
     req: HttpRequest,
-    payload: Json<RequestResetRequest>,
+    Json(payload): Json<RequestResetRequest>,
     principal: ReqPrincipal,
 ) -> Result<HttpResponse, ErrorResponse> {
     principal.validate_session_auth_or_init()?;
+    payload.validate()?;
 
-    let payload = payload.into_inner();
     match User::find_by_email(payload.email).await {
         Ok(user) => user
             .request_password_reset(&data, req, payload.redirect_uri)
@@ -1224,12 +1223,13 @@ pub async fn put_user_by_id(
     id: web::Path<String>,
     req: HttpRequest,
     principal: ReqPrincipal,
-    user: Json<UpdateUserRequest>,
+    Json(payload): Json<UpdateUserRequest>,
 ) -> Result<HttpResponse, ErrorResponse> {
     principal.validate_api_key_or_admin_session(AccessGroup::Users, AccessRights::Update)?;
+    payload.validate()?;
 
     let (user, user_values, is_new_admin) =
-        User::update(&data, id.into_inner(), user.into_inner(), None).await?;
+        User::update(&data, id.into_inner(), payload, None).await?;
 
     if is_new_admin {
         data.tx_events
@@ -1264,16 +1264,16 @@ pub async fn put_user_self(
     data: web::Data<AppState>,
     id: web::Path<String>,
     principal: ReqPrincipal,
-    user: Json<UpdateUserSelfRequest>,
+    Json(payload): Json<UpdateUserSelfRequest>,
 ) -> Result<HttpResponse, ErrorResponse> {
     principal.validate_session_auth()?;
+    payload.validate()?;
 
     // make sure the logged in user can only update itself
     let id = id.into_inner();
     principal.is_user(&id)?;
 
-    let (user, user_values, email_updated) =
-        User::update_self_req(&data, id, user.into_inner()).await?;
+    let (user, user_values, email_updated) = User::update_self_req(&data, id, payload).await?;
     if email_updated {
         Ok(HttpResponse::Accepted().json(user.into_response(user_values)))
     } else {
diff --git a/src/error/src/error_impls.rs b/src/error/src/error_impls.rs
index c62fe0d96..b3558119e 100644
--- a/src/error/src/error_impls.rs
+++ b/src/error/src/error_impls.rs
@@ -19,6 +19,7 @@ use std::string::FromUtf8Error;
 use svg_hush::FError;
 use time::OffsetDateTime;
 use tracing::{debug, error, trace};
+use validator::ValidationError;
 
 const APPLICATION_JSON: &str = "application/json";
 const HEADER_DPOP_NONCE: &str = "DPoP-Nonce";
@@ -346,6 +347,16 @@ impl From<validator::ValidationErrors> for ErrorResponse {
     }
 }
 
+impl From<validator::ValidationError> for ErrorResponse {
+    fn from(value: ValidationError) -> Self {
+        trace!("{:?}", value);
+        ErrorResponse::new(
+            ErrorResponseType::BadRequest,
+            format!("Payload validation error: {:?}", value),
+        )
+    }
+}
+
 impl From<serde_json::Error> for ErrorResponse {
     fn from(value: Error) -> Self {
         trace!("{:?}", value);


Test patch: