peaske7 / rocket-firebase-auth

Firebase auth with Rocket, batteries included
MIT License
8 stars 3 forks source link

Implement as a request guard #4

Closed rgarlik closed 1 year ago

rgarlik commented 1 year ago

Hey all,

I've come across this crate and I see that it's implemented the firebase authentication as a State. This works, however wouldn't it make more sense to implement the mechanism as a request guard?

This would lift the logic from the controller function body from this:

#[get("/")]
async fn hello_world(state: &State<ServerState>, token: BearerToken) -> Status {
    let token = state.auth.verify(&token).await; // verify token

    match token // extract uid from decoded token
    {
        Ok(token) => {
            println!("Authentication succeeded with uid={}", token.uid);
            Status::Ok
        }
        Err(_) => {
            println!("Authentication failed.");
            Status::Forbidden
        }
    }
}

..to something like this:

#[get("/")]
async fn hello_world(firebaseGuard: FirebaseGuard) -> Status {
    // no auth logic needed here
    // the guard automatically responds with Forbidden if the token is invalid
    // the function body only handles actual business logic

    println!("If needed, you can access the token and its claims: {}", firebaseGuard.uid);
    Status::Ok
}

I'm implementing a firebase guard myself for a web project of mine, so I can contribute some of my code if you'd like. However I'm not sure if it's gonna fit well into your library. I'm new to Rust however so that might not be a good idea.

rgarlik commented 1 year ago

I can see you implemented the BearerToken as a guard. Why not go all the way and check if the token is valid with Firebase inside the guard? That's the whole point of "guards" that they guard the endpoint and reject any traffic that's invalid.

It's possible to reference State from within a guard so the guard can have access to the Firebase auth state.

rgarlik commented 1 year ago

Here's my very "glued together" implementation of a guard that could be used for verification that I've implemented using modules from rocket-firebase-auth:

use rocket::{
    http::Status,
    request::{self, FromRequest, Request},
};
use rocket_firebase_auth::{errors::Error, DecodedToken, FirebaseAuth};

// Firebase is a request guard that verifies the Firebase JWT's validity
pub struct FirebaseGuard {
    // The JWT token with all its claims
    pub token: DecodedToken,
}

#[rocket::async_trait]
impl<'r> FromRequest<'r> for FirebaseGuard {
    type Error = FirebaseError;

    async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
        // Is header present?
        match req.headers().get_one("Authorization") {
            Some(header_raw) => {
                let (header_name, header_content) = header_raw.split_at(7);
                // Is header in the correct format?
                if header_name != "Bearer " {
                    request::Outcome::Failure((
                        Status::Unauthorized,
                        FirebaseError::IncorrectHeaderFormat,
                    ))
                } else {
                    // Find the FirebaseAuth state
                    match req.rocket().state::<FirebaseAuth>() {
                        // Verify if the token is valid
                        Some(auth) => match auth.verify_token(header_content).await {
                            Ok(t) => {
                                // Token is valid
                                let firebase_guard = FirebaseGuard { token: t };
                                request::Outcome::Success(firebase_guard)
                            }
                            Err(e) => request::Outcome::Failure((
                                // Token is invalid
                                Status::Unauthorized,
                                FirebaseError::JwtFailedValidation(e),
                            )),
                        },
                        // FirebaseAuth state not found
                        None => request::Outcome::Failure((
                            Status::InternalServerError,
                            FirebaseError::MissingFirebaseState,
                        )),
                    }
                }
            }
            None => request::Outcome::Failure((Status::Unauthorized, FirebaseError::MissingHeader)),
        }

        // Check if Firebase JWT token is valid
    }
}

// This custom error would of course be replaced with the rocket-firebase-auth internal Error type
#[derive(Debug)]
pub enum FirebaseError {
    MissingHeader,
    IncorrectHeaderFormat,
    JwtFailedValidation(Error),
    MissingFirebaseState,
}

Usage is very identical to my original comment's usage example up above.

peaske7 commented 1 year ago

Sorry for the late response, and thanks for opening an issue! This sounds like a great idea! Honestly, when you show the before and after of using RequestGuards, it's pretty obvious that this is something we should add to the library. You can open a PR if you're comfortable, or I can work on it, but it seems you are already halfway there. What do you think?

rgarlik commented 1 year ago

Hey @DrPoppyseed, I can implement it, no problem. I'll be adding a PR within a few days.

peaske7 commented 1 year ago

@rgarlik Sounds awesome!

peaske7 commented 1 year ago

Closing issue as guard has been implemented. Let me know if anyone wants to reopen this issue.