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?
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
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())
}