goose icon indicating copy to clipboard operation
goose copied to clipboard

How can I test a site behind Basic Auth

Open klausi opened this issue 1 year ago • 4 comments

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?

klausi avatar Jan 16 '25 13:01 klausi

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

jeremyandrews avatar Jan 17 '25 12:01 jeremyandrews

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 avatar Jan 21 '25 15:01 klausi

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

alecsmrekar avatar Aug 13 '25 15:08 alecsmrekar

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

jeremyandrews avatar Aug 14 '25 08:08 jeremyandrews