Search code examples
node.jsrustfetch-apibearer-tokenactix-web

401: Not Authorized error only when requesting data using fetch


I was trying to get response from an API running on localhost, I had gotten a generated bearer token from the login endpoint and now I just wanted to use the token to get the user claims on it. My fetch request is as follows:

const token = "MY_TOKEN";

(async () => {
    let response = await fetch('http://127.0.0.1:8080/auth/restricted/me', {
        method: 'GET',
        headers: {
            'Authorization': `Bearer ${token}`
        }
    });

    console.log(response.status);
})();

This always results in a 401 Unauthorized error.

However if I try do the same request using some other testing client like postman or insomnia or even the thunderbird extension for VS code, it works properly. What am I doing wrong?

Insomnia Working Screenshot

The middleware for checking the Bearer token is a Rust Actix Web middleware using the jwt_simple package. Here is the code for it as well if it helps:

use actix_web::{dev::ServiceRequest, web::Data};
use actix_web_httpauth::extractors::{
    bearer::{self, BearerAuth},
    AuthenticationError,
};
use jwt_simple::prelude::MACLike;

use crate::{models::user::User, AppState};

pub async fn validator(
    req: ServiceRequest,
    credentials: BearerAuth,
) -> Result<ServiceRequest, (actix_web::Error, ServiceRequest)> {
    let key = &req
        .app_data::<Data<AppState<User>>>()
        .clone()
        .unwrap()
        .signing_key;
    match key.verify_token::<User>(credentials.token(), None) {
        Ok(_) => return Ok(req),
        Err(_) => {
            println!("Credential Token {} did not verify!", credentials.token());
            let config = req
                .app_data::<bearer::Config>()
                .cloned()
                .unwrap_or_default()
                .scope("home");

            return Err((AuthenticationError::from(config).into(), req));
        }
    }
}

I have tried setting the mode property of fetch to be "cors" so far, but the server logs don't say much other than 401 authorized which either means it's having a problem getting the token or the token gets modified at some point.

EDIT: Removed content-type from headers


Solution

  • It seems I have understood why the problem was happening, in my gateway service, I was redirecting all of my requests like so:

    App::new()
                .wrap(Logger::default())
                .wrap(cors)
                .wrap(ratelimit_middleware)
                .service(ping)
                .wrap_fn(|req, srv| {
                    log::info!("{:#?}", req.headers());
                    srv.call(req)
                })
                .service(
                    web::scope("/auth")
                        .service(web::redirect(
                            "/ping",
                            format!(
                                "http://{}:{}/ping",
                                config_clone.auth_service.address, config_clone.auth_service.port
                            ),
                        ))
    )
    

    This doesn't work on most HTTP clients, because the Authorization header is stripped off from the request when a redirect happens, this is due to security reasons by default.

    To fix this, we would have to use the Client API for which I used the awc crate to copy the headers into a new request and make the request through the gateway service like so:

    Add the awc crate:

    cargo add awc
    

    Then add the forward request service

    async fn forward_request(req: HttpRequest) -> HttpResponse {
        let target_url = "http://authorization-service"; // Replace with the actual URL of the authorization microservice
        let client = awc::Client::default();
    
        // Create a new request and copy necessary headers
        let forwarded_req = client
            .request_from(target_url, req.head())
            .no_decompress()
            .insert_header((
                "Authorization",
                req.headers().get("Authorization").unwrap().clone(),
            ));
    
        // Send the forwarded request and return the response
        let mut response = forwarded_req.send().await.unwrap();
        HttpResponse::build(response.status()).body(response.body().await.unwrap())
    }