My clients authorize through a token in the Authorization
header which needs to be checked for each request. If this header is missing or I cannot find a corresponding user, I want to return the HTTP code Unauthorized
, else I want to handle the request normally.
Currently I have a lot of duplicate code because I am checking for this header in every request handler.
The actix docs suggest in the very first paragraph that it is possible to halt request processing to return a response early
.
How can this be achieved?
Since I have not found an example that implements this behavior I tried to come up with my own middleware function, but it won't compile.
I have already boxed the return values in order to overcome the problem of returning two different types (ServiceResponse
and Map
), so the problem asked in How do I conditionally return different types of futures? is not the issue. It is more that I do not know which types with which trait implementations are exactly required as return value for this wrap_fn
function. The ones I have right now do not work.
App::new()
.wrap(Cors::new().allowed_origin("http://localhost:8080"))
.register_data(state.clone())
.service(
web::scope("/routing")
.wrap_fn(|req, srv| {
let unauth: Box<dyn IntoFuture<Item = ServiceResponse>> = Box::new(ServiceResponse::new(req.into_parts().0, HttpResponse::Unauthorized().finish()));
let auth_header = req.headers().get("Authorization");
match auth_header {
None => unauth,
Some(value) => {
let token = value.to_str().unwrap();
let mut users = state.users.lock().unwrap();
let user_state = users.iter_mut().find(|x| x.auth.token == token);
match user_state {
None => unauth,
Some(user) => {
Box::new(srv.call(req).map(|res| res))
}
}
}
}
})
.route("/closest", web::get().to(routing::find_closest))
.route("/fsp", web::post().to(routing::fsp))
.route("/preference", web::get().to(routing::get_preference))
.route("/preference", web::post().to(routing::set_preference))
.route("/find_preference", web::post().to(routing::find_preference))
.route("/reset", web::post().to(routing::reset_data)),
)
.bind("0.0.0.0:8000")
.expect("Can not bind to port 8000")
.run()
.expect("Could not start sever");
There are two errors that I am getting upon compiling.
1.
error[E0191]: the value of the associated types `Future` (from the trait `futures::future::IntoFuture`), `Error` (from the trait `futures::future::IntoFuture`) must be specified
--> src/server/mod.rs:36:41
|
36 | let unauth: Box<dyn IntoFuture<Item = ServiceResponse>> =
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
| associated type `Future` must be specified
| associated type `Error` must be specified
2.
error[E0277]: the trait bound `dyn futures::future::IntoFuture<Item = actix_web::service::ServiceResponse>: futures::future::Future` is not satisfied
--> src/server/mod.rs:35:22
|
35 | .wrap_fn(|req, srv| {
| ^^^^^^^ the trait `futures::future::Future` is not implemented for `dyn futures::future::IntoFuture<Item = actix_web::service::ServiceResponse>`
|
= note: required because of the requirements on the impl of `futures::future::Future` for `std::boxed::Box<dyn futures::future::IntoFuture<Item = actix_web::service::ServiceResponse>>`
You can create your own type, Authorized
, implement FromRequest
for it and define Authorized
as an argument in the handlers that should be checked for authorization.
Simplified example:
use actix_web::dev::Payload;
use actix_web::error::ErrorUnauthorized;
use actix_web::{web, App, Error, FromRequest, HttpRequest, HttpResponse, HttpServer};
fn main() {
HttpServer::new(move || App::new().route("/", web::to(index)))
.bind("127.0.0.1:3000")
.expect("Can not bind to '127.0.0.1:3000'")
.run()
.unwrap();
}
fn index(_: Authorized) -> HttpResponse {
HttpResponse::Ok().body("authorized")
}
struct Authorized;
impl FromRequest for Authorized {
type Error = Error;
type Future = Result<Self, Error>;
type Config = ();
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
if is_authorized(req) {
Ok(Authorized)
} else {
Err(ErrorUnauthorized("not authorized"))?
}
}
}
fn is_authorized(req: &HttpRequest) -> bool {
if let Some(value) = req.headers().get("authorized") {
// actual implementation that checks header here
dbg!(value);
true
} else {
false
}
}
This code yields:
$ curl localhost:3000
not authorized⏎
$ curl localhost:3000 -H 'Authorized: i am root'
authorized⏎
You could probably do something in the same lines with middlewares, but I have not got my head around the middleware abstraction. Also, you might want to provide useful information to the handlers, like username:
struct Authorized {
username: String
}
I'm kind of late to the party but the best way to do this from within Actix middleware is using futures::future::Either
. You can see how it's used here: https://github.com/actix/examples/blob/master/middleware/middleware/src/redirect.rs.
The left hand side of Either
will be a Future which passes the response to the next stage in the chain. The right hand side will be a response (usually HttpResponse
) if you wish to return the response early.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With