Skip to content

Instantly share code, notes, and snippets.

@gellweiler
Last active October 18, 2023 21:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gellweiler/ab0ecb67afb8063a1d276c116bfb26e2 to your computer and use it in GitHub Desktop.
Save gellweiler/ab0ecb67afb8063a1d276c116bfb26e2 to your computer and use it in GitHub Desktop.
Use basic auth with selenium

This java class provides a simple way to achieve basic auth in recent versions of selenium (current version 3.x) with recent versions of google chrome (current year 2020). It dynamically generates a tiny extension that will add the Authorization header for you.

How to use it:

    // Add basic auth header with a chrome extension on every request
    File authExtension = new SeleniumChromeAuthExtensionBuilder()
        .withBasicAuth("Ali Baba", "Open sesame")
        .withBaseUrl("https://example.org/*")
        .build();

    try {
        ChromeOptions chromeOptions = new ChromeOptions();
        chromeOptions = chromeOptions.addExtensions(authExtension);
        webDriver = new ChromeDriver(chromeOptions);
    } finally {
        authExtension.delete();
    }
import org.apache.commons.lang3.StringEscapeUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* This class can be used to generate a minimal browser extension that
* will force Chrome to send out an Authorize header on every request.
*
* This is the only reliable way that has worked for me with Selenium 3
* to use basic auth.
*
* Alternatives to this approach:
* - Add the basic auth credentials to the url (does not work in recent versions of FF and Chrome)
* - Using a proxy that adds the header (LittleProxy). This should probably work,
* but https with proxies can be a pain in the ass. Also the last release of LittleProxy is from 2017.
* Launching an extra server for every test would also unnecessarily increase the complexity of tests.
* - Using selenium as the user would by filling out the username, password text boxes and clicking on the login button.
* This works, but with login forms adding captcha and other deterrents to stop bots can be unreliable.
* - Use a cookie for authentication and add that to the selenium browser. This works great if you can get
* a valid session cookie.
*
* Useful links:
* - https://stackoverflow.com/a/35293026
* - https://devopsqa.wordpress.com/2018/08/05/handle-basic-authentication-in-selenium-for-chrome-browser/
* - https://stackoverflow.com/a/27936481
* - https://sqa.stackexchange.com/questions/12892/how-to-send-basic-authentication-headers-in-selenium
*/
public class SeleniumChromeAuthExtensionBuilder {
private String headerJsCode = "";
private String baseUrl = "<all_urls>";
/**
* Add a header to every request.
*
* @param name The name of the header to add.
* @param value The value of the header.
*/
public SeleniumChromeAuthExtensionBuilder withStaticHeader(String name, String value) {
String nameEscaped = StringEscapeUtils.escapeEcmaScript(name);
String valueEscaped = StringEscapeUtils.escapeEcmaScript(value);
headerJsCode += "headers.push({name: \"" + nameEscaped + "\", value: \"" + valueEscaped + "\"});\n";
return this;
}
/**
* Add basic auth credentials to every requests.
*
* @param username The username for authentication
* @param password The password for authentication
*/
public SeleniumChromeAuthExtensionBuilder withBasicAuth(String username, String password) {
String encodeUserPass = Base64.getEncoder().encodeToString((username + ":" + password).getBytes(StandardCharsets.UTF_8));
return withStaticHeader("Authorization", "Basic " + encodeUserPass);
}
/**
* If you set a base url only requests to urls starting with this base url will be modified.
* Can contain wildcards (*). The special value <all_urls> matches all urls (default).
*/
public SeleniumChromeAuthExtensionBuilder withBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
return this;
}
/**
* Builds the extension as a zip archive.
* Please delete this file after installing it.
*
* @return The generated zip file
*/
public File build() throws Exception {
File tempFile = File.createTempFile("selenium-chrome-auth-", ".tmp.zip");
ZipOutputStream out = new ZipOutputStream(new FileOutputStream(tempFile));
// Add manifest.json
{
ZipEntry e = new ZipEntry("manifest.json");
out.putNextEntry(e);
out.write(generateManifestJson().getBytes(StandardCharsets.UTF_8));
out.closeEntry();
}
// Add background.js
{
ZipEntry e = new ZipEntry("background.js");
out.putNextEntry(e);
out.write(generateBackroundJs().getBytes(StandardCharsets.UTF_8));
out.closeEntry();
}
out.close();
return tempFile;
}
private String generateBackroundJs() {
return "chrome.webRequest.onBeforeSendHeaders.addListener(\n" +
" function(e) {\n" +
" var headers = e.requestHeaders;\n" +
headerJsCode +
" return { requestHeaders: headers };\n" +
" },\n" +
" {urls: [\"" + StringEscapeUtils.escapeEcmaScript(baseUrl) + "\"]},\n" +
" ['blocking', 'requestHeaders' , 'extraHeaders']\n" +
");";
}
private String generateManifestJson() {
return "{\n" +
" \"manifest_version\": 2,\n" +
" \"name\": \"Authentication for selenium tests\",\n" +
" \"version\": \"1.0.0\",\n" +
" \"permissions\": [\"<all_urls>\", \"webRequest\", \"webRequestBlocking\"],\n" +
" \"background\": {\n" +
" \"scripts\": [\"background.js\"]\n" +
" }\n" +
" }";
}
}
@gellweiler
Copy link
Author

Hi @jenspettersson68. I assume you tried the obvious:

 File authExtension = new SeleniumChromeAuthExtensionBuilder()
      .withBasicAuth("usrname@domain", "password")
      .withBaseUrl("https://example.org/*")
      .build();

This should work. Tough I haven't tried it myself.

@vasagle-gleblu
Copy link

I don't suppose you have any advice for converting this to C#?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment