I'm writing a middleware to validate a jwt token and add current authicated user to the request instance for protected routes in Actix web.
here is my code
use std::{
future::{ready, Ready},
rc::Rc
};
use actix_web::{
body::EitherBody,
dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
http::header::AUTHORIZATION,
Error, HttpMessage, HttpResponse,
};
use futures_util::{future::LocalBoxFuture, FutureExt};
use serde_json::json;
struct AuthUser {
id: usize,
email: Option<String>
}
pub struct JwtAuthChecker;
impl<S, B> Transform<S, ServiceRequest> for JwtAuthChecker
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static, // update here
S::Future: 'static,
B: 'static,
{
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 {
ready(Ok(AuthenticationMiddleware {
service: Rc::new(service), // convert S to Rc<S>
}))
}
}
pub struct AuthenticationMiddleware<S> {
service: Rc<S>,
}
impl<S, B> Service<ServiceRequest> for AuthenticationMiddleware<S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static, // update here
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<EitherBody<B>>;
type Error = Error;
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
forward_ready!(service);
fn call(&self, req: ServiceRequest) -> Self::Future {
// Do something with the request here
let auth = req.headers().get(AUTHORIZATION);
if auth.is_none() {
let response = json!({
"status": false,
"message": "Unauthorized"
});
let http_res = HttpResponse::Unauthorized().json(response);
let (http_req, _) = req.into_parts();
let res = ServiceResponse::new(http_req, http_res);
// Map to R type
return (async move { Ok(res.map_into_right_body()) }).boxed_local();
}
let jwt_token = get_jwt_token_from_header(auth.unwrap().to_str().unwrap());
// Clone the service to keep reference after moving into async block
let service = self.service.clone();
async move {
// Getting some data here (just demo code for async function)
let user = get_some_data(jwt_token).await;
req.extensions_mut().insert(user);
// Continue with the next middleware / handler
let res = service.call(req).await?;
// Map to L type
Ok(res.map_into_left_body())
}.boxed_local()
}
}
async fn get_some_data(token: &str) -> AuthUser {
// will fetch user data from token payload here
let user = AuthUser {
id: 1,
email: Some(String::from("[email protected]"))
};
return user;
}
fn get_jwt_token_from_header(jwt_token: &str) -> &str {
let bytes = jwt_token.as_bytes();
let token_len = jwt_token.len();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
let next_index = i + 1;
return &jwt_token[next_index..token_len];
}
}
return &jwt_token[0..token_len];
}
I'm getting confused inside the async block in the call() method how I can validate the jwt token here and put the user instance in the current request instance?
I have followed this tutorial on ginkcode but didn't get it properly