DEV Community

Nic
Nic

Posted on • Originally published at coderscat.com on

Rust Warp: Use Cookie for Authorization

Warp is a composable, web server in Rust. It's code is very small and focus on speed.

The fundamental building block of warp is the Filter, they can be combined and composed to express rich requirements on requests.

But it's actually not easy to use if you are not familiar with it's concept, and the type system will also scare some beginners.

For example, I actually spend some time to figure out how to use Cookie for authorization.

Suppose we have defined a Struct to represent the User:


#[derive(Debug, Deserialize)]
pub struct User {
    username: String,
    password: String,
}
Enter fullscreen mode Exit fullscreen mode

And we have a logic to authorize whether the login request has a valid username and password, the detail implementation depends on your code:

pub fn verify_user(user: &User) -> bool {
    ....
}
Enter fullscreen mode Exit fullscreen mode

The question is how to set a Cookie in response and how to verify each request after authorization.

Set a cookie

When a user send login request and passed the authorization, we use reply with with_header to set a cookie to store a token, which will be used for later requests:

 let login = warp::path!("api" / "login")
        .and(warp::post())
        .and(warp::body::json())
        .map(|user: auth::User| {
            if auth::verify_user(&user) {
                let token = auth::gen_token();
                warp::reply::with_header(
                    token.clone(),
                    "set-cookie",
                    format!("token={}; Path=/; HttpOnly; Max-Age=1209600", token),
                )
                .into_response()
            } else {
                warp::reply::with_status("failed", http::StatusCode::UNAUTHORIZED).into_response()
            }
        });
    let routes = routes.or(login);
Enter fullscreen mode Exit fullscreen mode

Use cookie for Authorization

To authorize request, we need to implement a filter in Warp, and use it like this, here we use Filter::untuple_one to unroll nested tuple layers from extractions.

  let verify = warp::path!("api" / "verify")
        .and(warp::get())
        .and(auth_validation())
        .untuple_one()
        .map(|| warp::reply::reply().into_response());
    let routes = routes.or(verify);
Enter fullscreen mode Exit fullscreen mode

And the auth_validation will call another built-in filter warp::cookie to extract the token from request header:

struct Unauthorized;

impl reject::Reject for Unauthorized {}

fn auth_validation() -> impl Filter<Extract = ((),), Error = Rejection> + Copy {
    warp::cookie::<String>("token").and_then(|token: String| async move {
        println!("token: {}", token);
        if let Some(true) = auth::verify_token(&token) {
            Ok(())
        } else {
            Err(warp::reject::custom(Unauthorized))
        }
    })
}

Enter fullscreen mode Exit fullscreen mode

Some thoughts

Even I have spent some time writing code in Rust, I still need to learn some new concepts or some details in a Web framework such as Warp. From my experience, there are some unnatural part for users who used to some other programming languages. For example, the return type of a filter:

impl Filter<Extract = ((),), Error = Rejection> + Copy
Enter fullscreen mode Exit fullscreen mode

It's just not so easy to say what this mean? And why we need to call untuple_one with the filter?

I know we must obey the rules of type system, and we need to add more annotations when writing Rust code, it's just not so easy for learning as other programming languages.

Even so, I'm still having fun with it for it bringing some new things for programming.

Top comments (0)