12

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>>`
ThinkPat
  • 133
  • 1
  • 9
  • 1
    Please paste the exact and entire error that you're getting — that will help us to understand what the problem is so we can help best. Sometimes trying to interpret an error message is tricky and it's actually a different part of the error message that's important. – Shepmaster Sep 11 '19 at 16:01
  • It looks like your question might be answered by the answers of [How do I conditionally return different types of futures?](https://stackoverflow.com/q/51885745/155423). If not, please **[edit]** your question to explain the differences. Otherwise, we can mark this question as already answered. – Shepmaster Sep 11 '19 at 16:01

2 Answers2

13

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
}
arve0
  • 3,424
  • 26
  • 33
  • See the docs here for an updated example: https://docs.rs/actix-web/2.0.0/actix_web/trait.FromRequest.html – MrBigglesworth Mar 15 '20 at 14:58
  • 4
    Has anyone achieved this using middleware? The Rust Cookbook (Packt 2019) has an outdated example using Eithers and the Actix page doesn't show how to break the service chain early. https://actix.rs/docs/middleware/. Kinda stuck myself. – cloudsurfin Aug 27 '20 at 18:18
5

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.

tobihans
  • 363
  • 3
  • 10
joshbenaron
  • 51
  • 1
  • 1