Network Programming in Rust #3 - Authentication

Migo·2024년 5월 28일

pingora

목록 보기
4/4

Pingora provides extensive customizability to your network programming. For more information, read: the this article.

Authentication

While there are a lot of different definition for user authentication, in a practical sense, it can be defined as the act of verifying user has access to a certain resource. In that process we validate if "something" provided by client is really the thing generated by server, using asymmetric key and so on. However, entire discussion on how it works will be beyond the scope of this post.

Authentication in pingora

Again, I'm going to use request_filter to extract header value and validate if it has expired and if not, I'll put extracted user id to X-USER-ID header. So at the higher level, the filter function would look like:

impl ProxyHttp for MyProxy {
   async fn request_filter(&self, session: &mut Session, ctx: &mut Self::CTX) -> Result<bool> {
        let req_header = session.req_header_mut();

        if req_header.uri.path().starts_with("/auth") {
            let bearer_token: BearerToken = (&req_header.headers).try_into()?;
            let user_id = bearer_token.get_user_id()?;
            req_header.insert_header("X-USER-ID", user_id).unwrap();
        };

        Ok(false)
    }
	...
}

Infallible conversion and error handling

Obviously, by the type you see that I used a lot of question mark, which is in Rustacean parlance for error propagation up to the caller. So, those who have decent amount of understanding over Rust type system will be able to tell I have to return Error type that was decided on ProxyHttp trait - which is pingora::Error.

Once you understand what's happening here, the way you keep the type promise is as easy as making a couple of conversion rule.

// the type into which "&HeaderMap<HeaderValue>" will be converted

#[derive(Debug)]
pub struct BearerToken(String);

impl TryFrom<&HeaderMap<HeaderValue>> for BearerToken {
    fn try_from(value: &HeaderMap<HeaderValue>) -> Result<Self, Self::Error> {
        value
            .get("Authorization")
            .map(TryInto::try_into)
            .ok_or(Error::explain(
                HTTPStatus(401),
                "Authorization token required",
            ))?
    }
    type Error = std::boxed::Box<pingora::Error>;
}

Here, I actually one more conversion logic from HeaderValue to BearerToken on purpose. I recommend getting your hands dirty by making that on your own!

profile
Dude with existential crisis

0개의 댓글