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" +
" }";
}
}
@saai32
Copy link

saai32 commented Jan 23, 2021

this is what I was looking for , but then I do not have admin right is there a way I could define location to Unpack ? getting loading of unpack
image

@gellweiler
Copy link
Author

gellweiler commented Jan 24, 2021

Hi @saai32,

The code should work fine without admin rights. But if your organization has disabled loading extensions in Chrome as it appears to be in your case then this code won't work. Unfortunately I don't know of any way to bypass this restriction in selenium.

Greetings,

Sebastian

@saai32
Copy link

saai32 commented Jan 26, 2021 via email

@SergeyRim
Copy link

Excellent! Thanks a lot for this solution.

@jenspettersson68
Copy link

Hi,
Nice solution, thanks, works perfect to get the extension running, but I might have a problem with domains.
As you know it was possible to do this through the URL, which I have done before. But my testuser is in another domain, so the url was like
https://username@domain:Password@example.com
I am not sure if this is a problem I am facing, or I am redirected, cause I cannot get this to work. Is there someone else who have some similar experience with domainusers? And as said, it worked before through URL authentication. Some suggestions?

@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