DEV Community

Cover image for Making a custom Guard in Actix Web
Roque
Roque

Posted on

Making a custom Guard in Actix Web

The premisse here is simple, I want to make a guard to wrap my scope/route/service, and in that guard I can put things like JWT token validation! So thats what this article is all about, organizing and guarding your routes.

First step: Setup

=====

We start our project with the good old cargo new _good_project_name_here, then we go to our main.rs and paste the basic example of the docs:

#[actix_web::main]  
async fn main() -> std::io::Result<()> {  
    HttpServer::new(|| {  
        App::new()  
            .service(hello)  
            .service(echo)  
            .route("/hey", web::get().to(|| async {
                 HttpResponse::Ok().body("Hello") })))
            })  
    .bind(("127.0.0.1", 8080))?  
    .run()  
    .await  
}
Enter fullscreen mode Exit fullscreen mode

We'll start by laying the groundwork for our next steps, our folder structure looks like this at the start, though most of it is empty:

.
|── Cargo.lock
|── Cargo.toml
|── src
|── handlers
| ├── mod.rs
| ├── unauthorized_handler.rs
| └── user_handler.rs
|── main.rs
|── utils
|── custom_guards.rs
|── mod.rs

  • I like to divide my handlers by the feature that they represent (like users or books) this results in more files but I think its worth the hassle in the long run.

  • At this point, is a fair warning that I'm have followed the Getting Started in Acticx Web documentation, and know what a "mod.rs" file does, if you don't I STRONGLY recommend that you go check the Docs by clicking Here, its really good.

User Handler

Here we're gonna do two things, first, create a simple get request that gives us a cute json response, the second, is create a function that lets us create a service, with the scope "/user" who will contain all requests related to a user.

First things first, that our get request:

// user_handler.rs

#[get("/")]
async fn get_all_users() -> HttpResponse {
    HttpResponse::Ok().json(json!({"test": "test"}))
}
Enter fullscreen mode Exit fullscreen mode

Then, remember that we're not in main.rs, so we need to get this route there some way, and we also want it to work inside a scope and be protected by a guard, to do that, we need the following code:

// user_handler.rs

pub fn user_scoped_config(cfg: &mut web::ServiceConfig) {
    cfg.service(web::scope("/user")
        .guard(guard::fn_guard(|ctx| true)) // Will change this soon
        .service(get_all_users),
    );
}
Enter fullscreen mode Exit fullscreen mode

The line .guard(guard::fn_guard(|ctx| true)) is where our custon guard will rest, its a fn_guard() as you can see, and it can take a function that get our context as parameter.

Custom Guard

Now we'll create the function that will be our mighty guard! OUr objective here is to create a a funciton that recieves our context and returns a boolean, inside this function we can access our request data and decide if it can have access to our routes.
Here I'll call it verify_token just as a reminder that it can be used for that, this function will only verify if the Authorization header exists, if not, access will be denied,

//custom_guards.rs

pub fn verify_token(ctx: &GuardContext) -> bool {
    let auth_header = ctx.head().headers().get("authorization");
    if auth_header.is_none() {
        HttpResponse::Unauthorized().json(json!({"error" : "Acesso negado"}));
        return false;
    } else {
        return true;
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, we need to change our fn_guard inside user_handler.rs, to look like this:

// user_handler.rs

pub fn user_scoped_config(cfg: &mut web::ServiceConfig) {
    cfg.service(web::scope("/user")
        .guard(guard::fn_guard(verify_token)
        .service(get_all_users),
    );
}
Enter fullscreen mode Exit fullscreen mode

And done! Our routes are protected from requests without the Authorization Header!

Bonus: Handle unauthorized requests

Well, now that we blocked the access, we migth want to send a pretty response explaining why said request was blocked, for that we can use a default_service! Is quite simple, if a request fails a guard of a scope, it will try to match with other route in its father (in this case, our root app) so we can create a handler that will take care of that!

Inside unauthorized_handler.rs insert the following code:

pub async fn handle_unauthorized() -> HttpResponse {
    HttpResponse::Unauthorized().json(json!({"error": "Unauthorized"}))
}
Enter fullscreen mode Exit fullscreen mode

Grand finale

Now we just need to use those beautiful thins inside our main function!
If you hook it all up in the right way you main should look like this:

mod handlers;
mod utils;

use actix_web::{
    web,
    App,
    HttpServer,
};

use handlers::{
    unauthorized_handler::handle_unauthorized,
    user_handler::user_scoped_config,
};

struct ApiGlobalState {}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let app_data = web::Data::new(ApiGlobalState {});
    HttpServer::new(move || {
        App::new()
            .app_data(app_data.clone())
            .configure(user_scoped_config)
            .default_service(web::route().to(handle_unauthorized))
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}
Enter fullscreen mode Exit fullscreen mode

And it's done! Hope that this was a good reading for you, and if you think I made some mistake, please reach out to me, I would love to get better with your help!
Thanks and have a nice time :)

Top comments (0)