How to use Selenium to inject Okta authentication tokens into Rest Assured

Francislainy Campos
HMH Engineering
Published in
6 min readSep 29, 2022

--

(From pom file up to Docker and Jenkins CI)

We recently got into a situation where we needed to test some of our backend APIs and got a little bit unsure how to proceed since they require a bearer token that is retrieved from an Okta login. This token can be seen under session storage when inspecting the application.

Okta token on session storage displayed through the page inspector.
okta token on session storage

Although we did try to figure out a way to retrieve this from other API calls, the best solution we ended up with was to do the flow as a user would, navigating first into the UI and retrieving the token from there.

Let me try to show you through this tutorial how we did this. As the focus here would be the integration between the Rest Assured and Selenium frameworks, we’ll assume you may already have some familiarity with them. We’ll also show you how to configure the docker and Jenkins files to run this from a CI tool.

The first thing we’ll do is to add the Rest Assured and Selenium dependencies to our pom file:

<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>4.5.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>json-schema-validator</artifactId>
<version>4.5.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>3.10.0</version>
<scope>test</scope>
</dependency>

Let’s just mention that this project was configured within a Spring project, which uses JUnit as the default test runner. But it should work within any Maven project as long as it includes JUnit there. However, it can also be adapted for other test runners if needed.

Selenium

For the Selenium navigation to the UI, we’ll create a helper class as follows:

public class BrowserHelper {

static WebDriver driver;

public static String getTokenFromSelenium() {

setUpDriver();
navigateToLoginPage();

typeEmailAndPassword();
return getToken();
}

private static void setUpDriver() {

WebDriverManager.chromedriver().setup();
ChromeOptions options = new ChromeOptions();
options.addArguments(
"--headless",
"--disable-gpu",
"--window-size=1920,1200",
"--ignore-certificate-errors",
"--disable-extensions",
"--no-sandbox",
"--disable-dev-shm-usage"
);

driver = new ChromeDriver(options);
driver.manage().deleteAllCookies();
}

private static void navigateToLoginPage() {

driver.get(url);
}

private static void typeEmailAndPassword() {

WebDriverWait wait = new WebDriverWait(driver, 180);
wait.until(visibilityOfElementLocated(By.id("okta-signin-username")));

driver.findElement(By.id("okta-signin-username")).sendKeys(defaultUser);
driver.findElement(By.id("okta-signin-password")).sendKeys(defaultPassword);
driver.findElement(By.id("okta-signin-submit")).click();
}

private static String getToken() {

WebDriverWait wait = new WebDriverWait(driver, 50);
wait.until(visibilityOfElementLocated(By.xpath("//*[contains(text(), 'Hello')]")));

String hnm_auth = getItemFromSessionStorage(driver, "HNM_AUTH");

return getTokenFromSessionStorage(hnm_auth);
}

public static String getItemFromSessionStorage(WebDriver driver, String key) {

JavascriptExecutor js = (JavascriptExecutor) driver;
return (String) js.executeScript(String.format(
"return sessionStorage.getItem('%s');", key));
}

public static String getTokenFromSessionStorage(String hnmAuto)
{
String pattern = "(?<=token\":\")(.*)(?=\",\"userId)";
return getStringFromPattern(hnmAuto, pattern);
}

public static void cleanUp() {

driver.close();
driver.quit();
}
}

Let me explain the main points here:

As we are using the UI just as a helper to retrieve the token, we’ll run our tests headless to avoid the overhead of loading the full browser before starting our tests. This requires a few extra arguments as shown below. If you would prefer to see the browser UI, you don’t need to do this, however you may need some extra configurations for your Docker image to work, which we are not including here.

Also, to avoid us manually needing to download the chrome driver and paste it into our repository as well as manage its versions, we’re using the WebdriverManager class to do this for us automatically.

private static void setUpDriver() {

WebDriverManager.chromedriver().setup();
ChromeOptions options = new ChromeOptions();
options.addArguments(
"--headless",
"--disable-gpu",
"--window-size=1920,1200",
"--ignore-certificate-errors",
"--disable-extensions",
"--no-sandbox",
"--disable-dev-shm-usage"
);

driver = new ChromeDriver(options);
driver.manage().deleteAllCookies();
}

As we mentioned before and as our token is inside session storage, next we are waiting for the login to finish loading (user sees inner ‘Hello’ text) and then retrieving the token from there.

private static String getToken() {

WebDriverWait wait = new WebDriverWait(driver, 50);
wait.until(visibilityOfElementLocated(By.xpath("//*[contains(text(), 'Hello')]")));

String hnm_auth = getItemFromSessionStorage(driver, "HNM_AUTH");

return getTokenFromSessionStorage(hnm_auth);
}

public static String getItemFromSessionStorage(WebDriver driver, String key) {

JavascriptExecutor js = (JavascriptExecutor) driver;
return (String) js.executeScript(String.format(
"return sessionStorage.getItem('%s');", key));
}

Please notice we need to add the Javascript executor to access the session storage values.

As we’re retrieving our token while it’s still surrounded by some extra text, we need to use a bit of regex to isolate it and that’s what the below method is doing.

public static String getTokenFromSessionStorage(String hnmAuto) {

String pattern = "(?<=token\":\")(.*)(?=\",\"userId)";
return getStringFromPattern(hnmAuto, pattern);
}

And to make sure our driver connection is closed after the test execution, we are closing and quitting the driver here:

public static void cleanUp() {

driver.close();
driver.quit();
}

This is it for the Selenium code. We now have our token that we can add as a header for Rest Assured. :)

Rest Assured

This is how we make sure we have the token in hand before using it with Rest Assured:

class EDSIT {String bearerAuthorizationHeader;    @BeforeAll
void setUp() throws JSONException {

triggerUIWithSelenium();
}
// your test methods here. private void triggerUIWithSelenium() {

bearerAuthorizationHeader = "Bearer " + getTokenFromSelenium();
log.debug(bearerAuthorizationHeaderOwner);
BrowserHelper.cleanUp();
}
}

Then when configuring Rest Assured, we can inject it like:

private Response getResponseFromApi(String jsonBody) {

RequestSpecification rq = getRequestSpecification()
.header("Authorization", bearerAuthorizationHeaderOwner);

return rq.body(jsonBody).post(url);
}

The RequestSpecification class being where we have our Rest Assured set up as in:

public static RequestSpecification getRequestSpecification() {

return
given()
.contentType(ContentType.JSON)
.accept(ContentType.JSON)
.when()
.log()
.everything();
}

Then we can now have a test similar to:

@Test
void myTest() {

Response response = getResponseFromApi(jsonBody);
assertEquals(200, response.getStatusCode());
}

And we’re done here and you can run your Running our tests with Maven such as in mvn integration-test -Dtest=com.my.package.EDSIT or through your favourite IDE.

Dockerfile and Jenkins CI configuration

We have our tests running fine within our local environment, but this of course implies we have everything we need already pre-installed there. Maven, Java, Chrome, etc. However, when we try this from a CI, we can’t assume this is also there (it most likely won’t be), so we need to make sure that when configuring our CI, along with our tests, we are also bringing everything we need to run them there. And that’s why we need Docker and more specifically, a Dockerfile. It will contain the instructions on how to install everything we need.

Now back to the ‘Show me the Code’ part:

FROM your.docker.hub/base-ubuntu:16.04-amazon-corretto-17

ENV DEBIAN_FRONTEND noninteractive

RUN apt update -y && apt install -y gnupg wget ca-certificates

RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add - \
&& sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list' \
&& apt-get clean \
&& apt-get update \
&& apt-get install curl -y \
dpkg

RUN curl -sL https://deb.nodesource.com/setup_14.x | bash -

RUN apt-get install -y google-chrome-stable

WORKDIR /project

Here we are using a base image that already contains Java pre-installed.

FROM your.docker.hub/base-ubuntu:16.04-amazon-corretto-17

And as we’re running our tests using the maven wrapper (./mvnw instead of mvn), which is committed together with our code, we don’t need to explicitly install Maven here.

An explorer view of the .mvn directory showing contents of the wrapper directory.
Maven wrapper

However, we do need to install the Chrome browser, and that’s what this next line is doing:

RUN apt-get install -y google-chrome-stable

Now we need to build our image and push it to our Docker hub.

docker build -t myImage:0.0.1 .

Once this is published, we’ll pull it and run our tests from there. Then we’ll add these steps to a Jenkins file, as well as the instructions to run our tests from Maven. Have a look at the sample below:

pipeline {
agent {
docker {
image 'myImage:0.0.1'
args '-v /var/run/docker.sock:/var/run/docker.sock --privileged -u root'
}
}

stages {

stage('Checkout') {
steps {
git credentialsId: 'my-git-credentials', url: "git@my.git.url/my-repo.git", branch: "$branch_name"
}
}

stage('Install') {
steps {
sh "./mvnw clean install -Dmaven.test.skip=true"
}
}

stage('Functional Tests') {
steps {
script {
catchError(message: 'Functional Tests Failed', stageResult: 'FAILURE') {
sh "./mvnw -DtestEnvt=${testEnvt} -Dtest=com.my.package.*IT integration-test"
}
surefireReport()
println "The currentBuild result is ${currentBuild.result}"
if (currentBuild.result == 'FAILURE') {
println 'Reporting Failure...'
} else {
println 'Reporting Success...'
}
}
}
}
}
}

void surefireReport() {
sh "./mvnwsurefire-report:report-only"
publishHTML target: [
allowMissing : false,
alwaysLinkToLastBuild: true,
keepAll : true,
reportDir : 'target/site',
reportFiles : '*.html',
reportName : 'Functional Tests Report'
]
}

Finally, this is our configuration on Jenkins.

Jenkins configuration displayed under their UI, highlighting where to type the Github url, ssh key and the path to the Jenkinsfile.
Jenkins configuration

And we’re done! You just need to trigger the pipeline and enjoy it.

Thank you for reading and I hope you have found this article helpful. :)

--

--