How can I test a site behind Basic Auth
Problem: I want to add a HTTP Basic Auth header to all requests because the site is protected.
I can do this on the initial user request:
async fn loadtest_index(user: &mut GooseUser) -> TransactionResult {
let reqwest_request_builder = user
.get_request_builder(&GooseMethod::Get, "/")?
.basic_auth("user", Some("password"));
// Add the manually created RequestBuilder and build a GooseRequest object.
let goose_request = GooseRequest::builder()
.set_request_builder(reqwest_request_builder)
.build();
// Make the actual request.
let goose_metrics = user.request(goose_request).await?;
let validate = &Validate::builder().status(200).text("Example").build();
validate_and_load_static_assets(user, goose_metrics, &validate).await?;
Ok(())
}
But then the basic auth header will not propagate to the asset requests for JS, CSS and images.
How can I do that?
Please take a look at this issue, where I detailed how to log a user in one time and then to share that client across all requests https://github.com/tag1consulting/goose/issues/594
The problem is that you need to set the basic auth credentials on the individual request level and not on the client level. My workaround is to use a helper function that I use whenever I send a request:
async fn send_request(
user: &mut GooseUser,
path: &str,
) -> Result<GooseResponse, Box<TransactionError>> {
let mut reqwest_request_builder = user.get_request_builder(&GooseMethod::Get, path)?;
let basic_auth = env::var("BASIC_AUTH");
if let Ok(basic_auth) = basic_auth {
// Split the BASIC_AUTH string into two parts, separated by a colon.
let parts: Vec<&str> = basic_auth.split(':').collect();
reqwest_request_builder = reqwest_request_builder.basic_auth(parts[0], Some(parts[1]));
}
// Add the manually created RequestBuilder and build a GooseRequest object.
let goose_request = GooseRequest::builder()
.set_request_builder(reqwest_request_builder)
.build();
// Make the actual request.
let goose_response = user.request(goose_request).await?;
Ok(goose_response)
}
@klausi You can do this:
attack
.register_scenario(
scenario!("Sitemap")
.register_transaction(transaction!(setup_custom_client).set_on_start())
.register_transaction(...),
)
.execute()
.await?;
async fn setup_custom_client(user: &mut GooseUser) -> TransactionResult {
use reqwest::{header, Client};
let mut headers = header::HeaderMap::new();
headers.insert(
"Authorization",
header::HeaderValue::from_str("your header").unwrap(),
);
let builder = Client::builder().default_headers(headers);
user.set_client_builder(builder).await?;
Ok(())
}
A slightly more complete example of creating a custom client with basic auth, from a load test I recently wrote for a client -- it's essentially the same as Alec's example but with a few more defaults -- see https://docs.rs/goose/latest/goose/goose/struct.GooseUser.html#method.set_client_builder for other options you may want to set at the same time:
async fn setup_basic_auth_client(user: &mut GooseUser) -> TransactionResult {
let mut headers = HeaderMap::new();
// Get credentials from environment variables
let username = env::var("BASIC_AUTH_USERNAME").expect("BASIC_AUTH_USERNAME not set");
let password = env::var("BASIC_AUTH_PASSWORD").expect("BASIC_AUTH_PASSWORD not set");
let credentials = format!("{}:{}", username, password);
let encoded = base64::engine::general_purpose::STANDARD.encode(credentials.as_bytes());
let auth_value = format!("Basic {}", encoded);
headers.insert(AUTHORIZATION, HeaderValue::from_str(&auth_value).unwrap());
let builder = Client::builder()
.user_agent(APP_USER_AGENT)
.default_headers(headers)
.cookie_store(true);
user.set_client_builder(builder).await?;
Ok(())
}