Authorize requests on actix-web middleware

I'm using actix-web to implement my webserver and actix_web_httpauth as a middleware to validate authenticated http requests.

async fn validator(
    req: ServiceRequest,
    _credentials: BearerAuth,
) -> Result<ServiceRequest, (Error, ServiceRequest)> {
    Ok(req)
}

pub async fn server() -> Result<(), impl StdError> {
    HttpServer::new(move || {
        let auth = HttpAuthentication::bearer(validator);
        App::new()
            .wrap(Logger::default())
            .wrap(auth)
[...]
}

In order to access my swagger ui I need to authorize myself. How to do this? - Working with authorization is a new field for me.

1 Like

I highly recommend that you familiarise yourself with Oauth 2.0 (the authentication protocol underlying bearer tokens that you want to use). It may be daunting at first, but very rewarding. OpenID Connect (OIDC is build on top of Oauth 2.0 and basically defines a set of technologies and APIs for the parts Oauth 2.0 leaves unspecified) is also something that'd be useful to know in my opinion. I for one use OIDC, which allows me to easily check the genuineness of the Jason Web Token passed as bearer token in a HTTP request by verifying the signature against the JSON Web Key Set of my authorization server (I use keycloak).

4 Likes

Thank you! So if I understand this right I need to add the Authorazation header or URI parameter and also a client (which would be the middleware)?
So OIDC is a tool/service which can be used to create bearer tokens and is an authorization server?

1 Like

Yes, you should provide the Authorization header field as part of your request to your resource server (the actix-web server you are building). As per your middleware snippet, you want to use the bearer authorization scheme, so your request header should have a field that looks like this:

Authorization: Bearer <token>

where the token is an access token provided by your authorization server.

OIDC is more like a protocol that is implemented by your authorization server. Like I said, I use keycloak as my authorization server[1], which is an open source IAM (Identity Access Management) server that supports OIDC.

Like I said above, in my authorization middleware I verify the access token from the Authorization header against the key chain of my authorization server (I use the jwks-client crate for that).

After verification—based on how you want to model the access control (i.e. user-based access control, role-based access control, etc.) to the resources provided by your actix-web server—you can then use the data inside the token to make sure the end-user who issued the token has access to the requested resource.

As a simple example, imagine you have user-based access control and the endpoint for the user data from your resource server is /user/{username}/. In your middleware you could extract the username from the request's URI and compare it against the sub field of the token, making sure they are equal (if only the user with username can access that resource).


  1. There are of course other solutions and implementations. IAM services are provided by all mayor cloud distributors. Auth0 is also worth mentioning (their documentation is superb). ↩︎

2 Likes

And, for example, I want to have access to Swagger without the auth-process during the development time. Is this also possible? Because I test my URIs a lot via Swagger.

Sure, just don't add the authentication middleware to the swagger routes.

1 Like

Does this mean I have to add the authentication middleware to each single route?

.service(
     web::scope("/dashboard/user")
         .service(get_user_by_id).wrap(auth.clone())
)
.service(
    web::scope("/dashboard")
         .service(get_all_statistics).wrap(auth.clone())
         .service(static_scan).wrap(auth.clone())
         .service(get_cookies).wrap(auth.clone())
)

Or is it possible to do add the middleware and call something like an exception for routes that have a specific string?

That would mean that you have to match the request URI inside your middleware which I would highly discourage you from doing—from a clean code perspective. You should only register your middleware to the routes where it is actually needed.

No, in your example you could implement it for each scope:

.service(
     web::scope("/dashboard/user")
         .wrap(auth.clone())
         .service(get_user_by_id)
)
.service(
    web::scope("/dashboard")
         .wrap(auth.clone())
         .service(get_all_statistics)
         .service(static_scan)
         .service(get_cookies)
)
1 Like