4

I have dove into the wonderful world of Rust and the Actix-Web the past few weeks and I am working on building various types of authentication through a piece of actix-web middleware. I have all of the auth logic completely figured out, but I cannot figure out how to return an unauthorized HTTP response from the middleware if a user fails an authentication check.

Here is how the Service trait is defined. Very standard from what I have seen.

impl<S, B> Service<ServiceRequest> for AuthMiddleware<S>
  where
      S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>
          + 'static,
      S::Future: 'static,
      B: 'static
  {
      type Response = ServiceResponse<B>;
      type Error = Error;
      type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;

And here is the end of call() where I am attempting return a particular response based on if the variable authenticate_fail is true.

let svc = self.service.clone();
if authenticate_fail {
    return Box::pin(async move {     
        let res = req.into_response(
            HttpResponse::Unauthorized()
            .finish()
         );
         Ok(res)
    })
}

The issue I am having is that Rust yells at me because the res variable is ServiceResponse<AnyBody> when it needs to be ServiceResponse<B>.

Now I can see that the reason for the issue seems to lie with the fact that I am using Actix Web 4 in which the into_response() method will return a Response object but it will have the type <AnyBody> rather than <B>. I know that I could go to Actix 3.3.2 to fix the issue, but I am hoping somebody might be able to either explain what I am doing incorrectly here or show me whatever is considered the correct way in Actix 4 to return an unauthorized response from middleware.

I am still very new to Rust, so I am sure that there could be something here that I am not understanding fully.

Thanks!

CivAsnem
  • 41
  • 2

1 Answers1

0

In actix 4.0 they introduce a new type called Either to use it as Left or Right result based on your need.

For example Left could be the result of other middleware responses and Right could be the result of your middleware.

In the below code, I had to return UNAUTHORIZED in case the JWT token is Invalid or return other middleware response

use pin_project::pin_project;
use std::{
    env,
    marker::PhantomData,
    pin::Pin,
    task::{Context, Poll},
};

use actix_utils::future::{ok, Either, Ready};
use actix_web::{
    body::{EitherBody, MessageBody},
    dev::{Service, ServiceRequest, ServiceResponse, Transform},
    http::{Method, StatusCode},
    Error, HttpResponse,
};
use futures::{ready, Future};
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};

use crate::modules::user::models::Claims;
pub struct Authentication;

impl<S, B> Transform<S, ServiceRequest> for Authentication
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: MessageBody,
{
    type Response = ServiceResponse<EitherBody<B>>;
    type Error = Error;
    type InitError = ();
    type Transform = AuthenticationMiddleware<S>;
    type Future = Ready<Result<Self::Transform, Self::InitError>>;

    fn new_transform(&self, service: S) -> Self::Future {
        ok(AuthenticationMiddleware { service })
    }
}
pub struct AuthenticationMiddleware<S> {
    service: S,
}

impl<S, B> Service<ServiceRequest> for AuthenticationMiddleware<S>
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: MessageBody,
{
    type Response = ServiceResponse<EitherBody<B>>;

    type Error = Error;

    type Future = Either<AuthenticationFuture<S, B>, Ready<Result<Self::Response, Self::Error>>>;

    fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        self.service.poll_ready(cx)
    }

    fn call(&self, req: ServiceRequest) -> Self::Future {
        let mut authenticate_pass = false;

        if Method::OPTIONS == *req.method() {
            authenticate_pass = true;
        }

        let auth = req.headers().get("Authorization");
        if auth.is_some() {
            let _split: Vec<&str> = auth
                .unwrap()
                .to_str()
                .unwrap()
                .trim()
                .split("Bearer")
                .collect();
            let token = _split[1].trim();
            let secrt_key = env::var("SECRET_KEY").expect("SECRET_KEY in .env file is missing");
            let key = secrt_key.as_bytes();

            if decode::<Claims>(
                token,
                &DecodingKey::from_secret(key),
                &Validation::new(Algorithm::HS512),
            )
            .is_ok()
            {
                authenticate_pass = true;
            }
        }

        if authenticate_pass {
            Either::left(AuthenticationFuture {
                fut: self.service.call(req),
                _phantom: PhantomData,
            })
        } else {
            let res = HttpResponse::with_body(StatusCode::UNAUTHORIZED, "Invalid JWT Token");
            Either::right(ok(req
                .into_response(res)
                .map_into_boxed_body()
                .map_into_right_body()))
        }
    }
}

#[pin_project]
pub struct AuthenticationFuture<S, B>
where
    S: Service<ServiceRequest>,
{
    #[pin]
    fut: S::Future,
    _phantom: PhantomData<B>,
}

impl<S, B> Future for AuthenticationFuture<S, B>
where
    B: MessageBody,
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
{
    type Output = Result<ServiceResponse<EitherBody<B>>, Error>;

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        let res = match ready!(self.project().fut.poll(cx)) {
            Ok(res) => res,
            Err(err) => return Poll::Ready(Err(err.into())),
        };

        Poll::Ready(Ok(res.map_into_left_body()))
    }
}
Koptan
  • 85
  • 1
  • 8