How to retrieve HTTP headers from a request in Rocket? - rust

I would do something like this using flask in Python:
#app.route('/login/', methods=['POST'])
def login():
token = request.headers["token"]
I cannot figure out how to access the token header and store it as a String variable.
#![feature(proc_macro_hygiene, decl_macro)]
use rocket::{
config::{Config, Environment},
*,
};
fn main() {
let config = Config::build(Environment::Production)
.address("0.0.0.0")
.port(PORT)
.finalize()
.unwrap();
rocket::ignite().mount("/", routes![login]).launch();
}
#[post("/login")]
fn login() {
// Retrieve headers from request.
}

Ibraheem Ahmed's answer was useful, but I could not figure out how to use Infallible. I solved the issue by doing:
struct Token(String);
#[derive(Debug)]
enum ApiTokenError {
Missing,
Invalid,
}
impl<'a, 'r> FromRequest<'a, 'r> for Token {
type Error = ApiTokenError;
fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
let token = request.headers().get_one("token");
match token {
Some(token) => {
// check validity
Outcome::Success(Token(token.to_string()))
}
None => Outcome::Failure((Status::Unauthorized, ApiTokenError::Missing)),
}
}
}

Rocket handlers are based on Request Guards. You do not directly access the request in your handler. Instead, you create a type that implements FromRequest.
You can create a token struct that holds a string:
struct Token(String);
And implement FromRequest for the token:
impl<'a, 'r> FromRequest<'a, 'r> for Token {
type Error = Infallible;
fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
let token = request.headers().get_one("token");
match token {
Some(token) => {
// check validity
Outcome::Success(Token(token.to_string()))
},
// token does not exist
None => Outcome::Failure(Status::Unauthorized)
}
}
}
Now you can use that Token as a request guard:
#[post("/login")]
fn login(token: Token) {
}
If the from_request method for Token fails, a Status::Unauthorized will be returned. Otherwise, your handler will be called, and you can handle the authentication logic.

Related

what is the correct way of implementing auth in actix_web?

I've been doing rust for just short while but didn't much struggle until this issue.
I want to authenticate each request, and make returned value (from auth service) available inside requests.
I've read implementing FromRequest for the struct returned by auth server should make it available.
But I have issues implementing it correctly. It requires extract and from_request functions both returning self, and if I understand it a little correctly, extract is being called the first, so I should set it somewhere in request extensions, and get it from there inside from_request. I have following code:
impl FromRequest for CustomClaim {
type Error = actix_web::Error;
type Future = std::pin::Pin<Box<dyn std::future::Future<Output = Result<Self, Self::Error>>>>;
fn extract(req: &HttpRequest) -> Self::Future {
let auth_header = req
.headers()
.get("Authorization")
.unwrap()
.to_str()
.unwrap()
.to_owned();
let boxed_future = Box::pin(async move {
let claim_future = get_claim(&auth_header).await;
claim_future
});
req.extensions_mut().insert(boxed_future);
boxed_future
}
fn from_request(req: &HttpRequest, payload: &mut actix_web::dev::Payload) -> Self::Future {
let boxed_future = req.extensions().get::<Self::Future>().unwrap();
boxed_future
}
}
async fn get_claim(auth_header: &str) -> Result<CustomClaim, actix_web::Error>
But from_request doesn't compile because it returns a reference (error message: mismatched types expected struct `Pin\<Box\<(dyn futures::Future\<Output = Result\<auth::CustomClaim, actix_web::Error\>\> + 'static)\>\>`found reference`&Pin\<Box\<dyn futures::Future\<Output = Result\<auth::CustomClaim, actix_web::Error\>\>\>\>` )
Doing .clone() doesn't help because clone is not implemented on that future type so it still returns a reference. Can I implement clone for the future? Is my approach wrong?
Ok so I feel little dumb but the answer is - you (or I) don't need the extract function. Only from_request is required.
So solution is, move the code from extract function to from_request, omit extract, and don't set anything on the request:
impl FromRequest for CustomClaim {
type Error = actix_web::Error;
type Future = std::pin::Pin<Box<dyn std::future::Future<Output = Result<Self, Self::Error>>>>;
fn from_request(req: &HttpRequest, _payload: &mut actix_web::dev::Payload) -> Self::Future {
let auth_header = req
.headers()
.get("Authorization")
.unwrap()
.to_str()
.unwrap()
.to_owned();
Box::pin(async move {
let claim_future = get_claim(&auth_header).await;
claim_future
})
}
}
Then it's possible to add a parameter in authenticated handlers:
#[post("/authed-post")]
async fn authed_post(
_req: HttpRequest,
claim: CustomClaim,
// ...
) -> impl Responder

Map domain result into http result

I'm trying to build an HTTP Server using Actix web framework in Rust.
I'm used to segregate Business Model and Business Error from HttpResponse.
For doing that, I've my service, CredentialService, that exposes a method that returns a result Result<String, CredentialServiceError>.
My WebServer exposes an API POST /login that accepts username and password and returns a JWT.
The enum `CredentialServiceError' is the following
use derive_more::{Display, Error};
#[derive(Debug, Display, Error)]
pub enum CredentialServiceError {
NoCredentialFound,
ErrorOnGeneratingJWT,
}
My handler is something like:
async fn login(request_body: web::Json<LoginRequest>, credential_service: web::Data<CredentialService>) -> Result<HttpResponse> {
let request_body: LoginRequest = request_body.0;
let jwt = credential_service.login(request_body.username, request_body.password);
let response: Result<LoginResponse, CredentialServiceError> = jwt.map(|jwt| {
LoginResponse { jwt }
});
response.into()
}
I receive this error:
the trait bound `std::result::Result<actix_web::HttpResponse, actix_web::Error>: std::convert::From<std::result::Result<model::LoginResponse, credential_service::CredentialServiceError>>` is not satisfied
the trait `std::convert::From<std::result::Result<model::LoginResponse, credential_service::CredentialServiceError>>` is not implemented for `std::result::Result<actix_web::HttpResponse, actix_web::Error>`
help: the following implementations were found:
<std::result::Result<(), idna::uts46::Errors> as std::convert::From<idna::uts46::Errors>>
<std::result::Result<(), ring::error::Unspecified> as std::convert::From<ring::bssl::Result>>
<std::result::Result<miniz_oxide::MZStatus, miniz_oxide::MZError> as std::convert::From<&miniz_oxide::StreamResult>>
<std::result::Result<miniz_oxide::MZStatus, miniz_oxide::MZError> as std::convert::From<&miniz_oxide::StreamResult>>
and 2 others
note: required because of the requirements on the impl of `std::convert::Into<std::result::Result<actix_web::HttpResponse, actix_web::Error>>` for `std::result::Result<model::LoginResponse, credential_service::CredentialServiceError>`rustc(E0277)
I've also tried to implement
impl error::ResponseError for CredentialServiceError { ... }
impl Into<HttpResponse> for LoginResponse { ... }
The the error doesn't change.
So, how can I convert Result<String, CredentialServiceError> into Result<HttpResponse> ?
So the problem you are facing is, that you want to convert your result into the Result that actix_web functions expect. I hope I understood that correctly.
I don't see an other way than to map your Result to the expected one. There are different ways you could accomplish that. If you would want to keep the implementation details in your LoginResponse struct you could use the Into trait.
impl Into<HttpResponse> for LoginResponse {
fn into(self) -> HttpResponse {
HttpResponse::Ok().body(self.jwt)
}
}
If you don't really care you can use the map functions provided by Result.
fn map_credential_error(error: CredentialServiceError) -> actix_web::error::Error {
match error {
CredentialServiceError::NoCredentialFound => {
actix_web::error::ErrorUnauthorized("Not authorized")
}
CredentialServiceError::ErrorOnGeneratingJWT => {
actix_web::error::ErrorInternalServerError("Something went wrong generating your jwt")
}
}
}
So in the end your login function would look something like this.
async fn login(
request_body: web::Json<LoginRequest>,
credential_service: web::Data<CredentialService>,
) -> Result<HttpResponse> {
let request_body: LoginRequest = request_body.0;
let jwt = credential_service.login(request_body.username, request_body.password);
let response: Result<HttpResponse, CredentialServiceError> =
jwt.map(|jwt| LoginResponse { jwt }.into());
response.map_err(map_credential_error)
}
Hope I could help and that I understood your question.
In addition to #Julius-Kreutz's answer you may consider impl ResponseError trait Source from Chanda, Abhishek. For example if your CredentialServiceError is an enum like:
#[derive(Debug, Display)]
pub enum CredentialServiceError {
#[display(fmt = "Internal Server Error")]
InternalServerError,
#[display(fmt = "BadRequest: {}", _0)]
BadRequest(String),
#[display(fmt = "JWKSFetchError")]
JWKSFetchError,
}
Then yuo can do something like:
impl ResponseError for CredentialServiceError {
fn error_response(&self) -> HttpResponse {
match self {
CredentialServiceError::InternalServerError => {
HttpResponse::InternalServerError().json("Internal Server Error, Please try later")
}
CredentialServiceError::BadRequest(ref message) => HttpResponse::BadRequest().json(message),
ServiceError::JWKSFetchError => {
HttpResponse::InternalServerError().json("Could not fetch JWKS")
}
}
}
}
To enable the .into()

Returning an unauthorized response in actix-web middleware in Rust

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!
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()))
}
}

How should I enforce Deserialization types for jsonwebtokens in rust?

The following is a token creation tool I have created using jsonwebtoken.
I want to somehow enforce the expected token type such that if I pass a token string in and tell it the claim set I expect, it wont return a successful result.
Below includes test cases with comments about where I think this service should fail, and a comment in the code where I think the assertion should take place.
How can I enforce these claim types to be sure I get the token type I want?
use jwt;
use jwt::{ Header, Validation };
use std::convert::AsRef;
use serde::de::DeserializeOwned;
use serde::Serialize;
#[derive(Debug, Serialize, Deserialize, PartialEq)]
enum TokenType {
User,
Reg,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct RegisterClaims {
typ:TokenType,
org_name:String,
name:String,
email:String,
exp: usize,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct UserClaims {
typ:TokenType,
usr_id:String,
sub:String,
exp: usize,
}
#[derive(Debug)]
pub struct InvalidToken {
cause: String,
}
pub struct TokenFactory {
secret:String,
}
impl TokenFactory {
pub fn new(secret:String) -> TokenFactory {
TokenFactory {
secret
}
}
pub fn validate<T: DeserializeOwned>(&self, raw_token:String) -> Result<T, InvalidToken> {
match jwt::decode::<T>(&raw_token, self.secret.as_ref(), &Validation::default()) {
Ok(tokendata) => {
/*
some how assert the type of T to match and return an Err if not matched
What
*/
Ok(tokendata.claims)
},
Err(err) => {
// todo: in the future check error kind and give better errors
Err(InvalidToken{
cause: err.to_string()
})
}
}
}
pub fn mint_token<T: Serialize>(&self, claims:&T) -> String {
jwt::encode(&Header::default(), claims, self.secret.as_ref()).unwrap()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::util;
use std::borrow::Borrow;
#[test]
fn test_valid() {
let usr = UserClaims {
typ: TokenType::User,
sub:"foobar#gmail.com".to_string(),
exp:util::current_time_secs()+1,
usr_id:"usr-1234".to_string(),
};
let tf = TokenFactory::new("my_sceret".to_string());
let token = tf.mint_token(usr.borrow());
let usr_claims: UserClaims = tf.validate(token).unwrap();
assert_eq!(usr.sub, usr_claims.sub);
}
#[test]
fn test_mixed() {
let reg = RegisterClaims {
typ:TokenType::Reg,
org_name:"foo".to_string(),
name:"bar".to_string(),
email:"foobar#inc".to_string(),
exp:util::current_time_secs()+1,
};
let tf = TokenFactory::new("my_sceret".to_string());
let token = tf.mint_token(reg.borrow());
let usr_claims: UserClaims = tf.validate(token).unwrap(); // want it to fail here
assert_eq!(reg, usr_claims); // fails here
}
}
The solution I ended up using was to implement the following train for each claim struct I have.
trait ClaimType {
fn is_type() -> TokenType;
fn has_type(&self) -> TokenType;
}
Then in my validate I did
if T::is_type() == tokendata.claims.has_type() {
return Ok(tokendata.claims);
}
Err(InvalidToken{
cause: "Wrong token type".to_string()
})
Im sure there is probably a way to use a macro to impl the trait for me, or have the library itself enforce some deserialization check. But the above gets it done

Could I get response body text and modify it from Actix-Web middleware?

Im developing an app with rust and actix-web framwork, and I inserted some middleware instance to the app.
I planed the middleware would modify the response's body text and return the response in the call() method, but I couldn't find the solution. I can't find a sample code that enables getting text from ServiceResponse and modifying it.
Could you guys help me with some sample code that getting response's body text and modifying it?
Following is sample code that I used. I added a comment to inform what I want in SayHiMiddleware->call() of "sample.rs"
// sample.rs
use actix_service::{Service, Transform};
use actix_web::{dev::ServiceRequest, dev::ServiceResponse, Error};
use futures::future::{ok, FutureResult};
use futures::{Future, Poll};
// There are two steps in middleware processing.
// 1. Middleware initialization, middleware factory gets called with
// next service in chain as parameter.
// 2. Middleware's call method gets called with normal request.
pub struct SayHi;
// Middleware factory is `Transform` trait from actix-service crate
// `S` - type of the next service
// `B` - type of response's body
impl<S, B> Transform<S> for SayHi
where
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Request = ServiceRequest;
type Response = ServiceResponse<B>;
type Error = Error;
type InitError = ();
type Transform = SayHiMiddleware<S>;
type Future = FutureResult<Self::Transform, Self::InitError>;
fn new_transform(&self, service: S) -> Self::Future {
ok(SayHiMiddleware { service })
}
}
pub struct SayHiMiddleware<S> {
service: S,
}
impl<S, B> Service for SayHiMiddleware<S>
where
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Request = ServiceRequest;
type Response = ServiceResponse<B>;
type Error = Error;
type Future = Box<dyn Future<Item = Self::Response, Error = Self::Error>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.service.poll_ready()
}
fn call(&mut self, req: ServiceRequest) -> Self::Future {
println!("Hi from start. You requested: {}", req.path());
Box::new(self.service.call(req).and_then(|res| {
// I want to get response body text and print the text.
// But I couldnt get the text from ServiceResponse instance..... help me guys T.T
// And is there way to combine with previous response body text and new text?????
// example (res->body->text is "aaaaa")) and I want to append new text to the string ( "aaaaa" + "bbbbb" )
println!("Hi from response");
Ok(res)
}))
}
}
// main.rs
use actix_web::{web, App, HttpServer};
use actix_service::Service;
use futures::future::Future;
#[allow(dead_code)]
mod redirect;
#[allow(dead_code)]
mod read_request_body;
#[allow(dead_code)]
mod read_response_body;
#[allow(dead_code)]
mod simple;
fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "actix_web=debug");
env_logger::init();
HttpServer::new(|| {
App::new()
.wrap(redirect::CheckLogin)
.wrap(read_request_body::Logging)
.wrap(read_response_body::Logging)
.wrap(simple::SayHi)
.wrap_fn(|req, srv| {
println!("Hi from start. You requested: {}", req.path());
srv.call(req).map(|res| {
println!("Hi from response");
res
})
})
.service(web::resource("/login").to(|| {
"You are on /login. Go to src/redirect.rs to change this behavior."
}))
.service(
web::resource("/").to(|| {
"Hello, middleware! Check the console where the server is run."
}),
)
})
.bind("127.0.0.1:8080")?
.run()
}
Thank you...
This is what I got working. I think there might be a better way but examples are light.
/// Extracts a response body of type T from `input`
/// Example:
///
/// let result: MyResponseBodyContract = TestContext::map_body(&mut resp).await;
pub async fn map_body<T>(input: &mut ServiceResponse<Body>) -> T
where T: DeserializeOwned
{
let mut body = input.take_body();
let mut bytes = BytesMut::new();
while let Some(item) = body.next().await {
bytes.extend_from_slice(&item.unwrap());
}
let result: T = serde_json::from_slice(&bytes)
.unwrap_or_else(|_| panic!("map_body failed during deserialization"));
return result;
}
My buddy #stephaneyfx helped me refactor this into something nicer.
#[async_trait(?Send)]
pub trait ParseResponse {
/// Extracts a response body of type T from `input`
/// Example:
///
/// let result: MyContractType = resp.parse().await.unwrap();
async fn parse<T: DeserializeOwned>(&mut self) -> Result<T, Box<dyn std::error::Error>>;
}
#[async_trait(?Send)]
impl ParseResponse for ServiceResponse<Body> {
async fn parse<T>(&mut self) -> Result<T, Box<dyn std::error::Error>>
where
T: DeserializeOwned,
{
let bytes = self.take_body().try_fold(Vec::new(), |mut acc, chunk| async {
acc.extend(chunk);
Ok(acc)
});
Ok(serde_json::from_slice(&bytes.await?)?)
}
}

Resources