Description
What's missing?
I want to implement an OAuth Client in Rocket.
For that I have decided to keep some stuff, like currently authenticated users and csrf tokens in rocket's managed state.
e.g. Twitch and Discord pass a csrf state back in the state
query parameter.
I now want to add a Request Guard on that state
query parameter, which requires me to write a FromForm
(or FromFormField
) implementation.
The issue with writing such an implementation is, that I cannot get the csrf tokens out of rocket's managed state.
Also in such a scenario it is very unclear, how an error will be displayed to the end user.
With FromRequest
it is pretty clear to me what will happen (there is good documentation and the method signatures makes it clear what will happen).
With FromForm
I am still stuck thinking: If I return an error, how will this affect the actual "main responder method body" and how (if at all) are errors from FromForm
displayed in the response?
e.g.
struct CSRF(String);
impl FromFormField for CSRF { /*.... some magic impl here .... */}
//When would this stop running?
#[rocket::get("/test?<state>")]
async fn test(state: CSRF) -> &'static str { "Hello World" }
Ideal Solution
Writing query parameter/"form" request guards would be possible.
query parameter request guards would allow the conversion and validation of one query parameter, whilst referencing rocket managed state (similarly to FromRequest
).
When the validation fails, it should be clear what happens after from the method signature and documentation (similarly to FromRequest
)
Why can't this be implemented outside of Rocket?
Implementing a feature like this outside of Rocket (in the way I would want it, without any drawbacks) would likely require redoing how requests are dispatched to request handlers.
I imagine, that that part of rocket is non-extendable.
Are there workarounds usable today?
Yes. I can think of 2 methods:
One could just implement FromRequest
for a custom CSRF struct, which accesses a compile-time specified (e.g. using type constant arguments) query parameter using Request::query_value
and gets all valid csrf's from managed rocket state.
Then a validation can be performed without issue, and if needed a request handler can be blocked from
running.
This method relies on Request::query_value
, which is noted as: "Warning
This method exists only to be used by manual routing and should never be used in a regular Rocket application."
Therefore I think that this approach is discouraged by Rocket
That being said, I for now went with this approach, since it's the easiest and closest to what I want:
- Csrf Request Guard Definition: https://github.com/C0D3-M4513R/MeAndTheBoisBot/blob/d99ff57519eee755199e5f569b156591508e0d1a/src/rocket/csrf.rs
- Csrf Request Guard Usage: https://github.com/C0D3-M4513R/MeAndTheBoisBot/blob/d99ff57519eee755199e5f569b156591508e0d1a/src/rocket/twitch/oauth/ok.rs#L11-L12 (state is the csrf token. It's not really being used here. The twitch oauth library is just a bit silly with how they expect you to use csrf's)
One could make a custom macro, which wraps each applicable request handler function to add csrf validation.
Downside: Writing such a macro (depending on versatility) might be really hard.
Especially when going into the territory of proc_macros, which change the function return type and transparently wrap it.
e.g.
//before application of custom::csrf_wrapper
#[rocket::get("/test?<state>")]
#[custom::csrf_wrapper("state")] //duplicated specifier
async fn test(state: &str) -> &'static str { "Hello World" }
//after application of custom::csrf_wrapper
#[rocket::get("/test?<state>")]
#[custom::csrf_wrapper("state")] //duplicated specifier
async fn test<'r>(state: &'r str) -> impl rocket::response::Responder<'r, 'static> {
#[derive(rocket::response::Responder)]
enum __impl_Error {
OriginalFunction(&'static str),
CSRFError(rocket::response::content::RawHTML<&'static str>),
}
if let Err(err) = some_verify_csrf_function(state).await {
return __impl_Error::CSRFError(err);
}
//Original Function
async fn test(state: &str) -> &'static str { "Hello World" }
__impl_Error::OriginalFunction(test(state).await)
}
Alternative Solutions
No response
Additional Context
I have not been using rocket for a long time. I have started using rocket 1-2 days ago. I also have no clue how this library/framework works internally.
System Checks
- I do not believe that this feature can or should be implemented outside of Rocket.
- I was unable to find a previous request for this feature.