November 18, 2020

How to Run Performance Tests on OAuth Secured Apps with JMeter

Open Source Automation
Performance Testing

In this blog we will cover JMeter OAuth testing including why it's important, and three options on how to run performance tests on OAuth secured apps with JMeter.

Table of Contents:

What is OAuth & Why Do You Need it?

OAuth (the Open Standard for Authorization) is an open protocol which provides token-based authentication and authorization - as opposed to the standard username and password requirements.  It allows third party services to use the end-user information without revealing their personal credentials.  

Here’s an example of how it works:

  1. The user goes to website A.

  2. On website A, he’s given the option to sign in by using website B (Google, Facebook, LinkedIn, GitHub, etc.).

  3. The user confirms that information obtained from website B (i.e. first name and last name) can be used by website A.

  4. The user is known on website A by the credentials (i.e. first name and last name) that he set up on website B.

This entire process is known as the flow. As you can see, the end user doesn’t need to enter his credentials at all on website A, which makes it far more secure.

Why and How Developers Started Using OAuth

OAuth version 1.0 was first released in 2007. Twitter was the first platform to start using OAuth to access its API.  By 2010, the platform had made the use of OAuth compulsory for all 3rd-party applications working with its API. In the  same year, IETF launched OAuth 2.0. Like its predecessors (Oauth 1.0 and OAuth 1.0.a),  OAuth 2.0 gives users the ability to grant third-party access to web resources without sharing their passwords.

New features in OAuth 2.0 include new flows, simplified signatures and short-lived tokens with long-lived authorizations.

OAuth has a huge number of flows, different protocol versions - with opposing viewpoints on them (the creator of OAuth 1.0 withdrew his name and support from OAuth 2.0 due to suspected security flaws) - and various server/client sides implementations (OAuth 2.0 leaves a lot of things up to the implementor).

3 Ways to Run JMeter Performance Tests on OAuth Secured Apps

Let's explore three options of running performance tests on JMeter OAuth secured apps.

Option 1: The End User

This first option is the closest one to a real-life user experience (logging into website A with your credentials from website B). 

For this test scenario, I’m going to use blazemeter.com which provides the possibility to use your existing Google account instead of signing up. 

When you click the button, it takes you to your Google Account sign-in page where you provide your email and password and returns you to the BlazeMeter application authenticated and authorized. 

I’m going to use:

  1. setUp Thread Group
  2. WebDriver Sampler plugin to open BlazeMeter and Google and store the cookies into JMeter Properties 
  3.  Normal “Thread Group” with
    • HTTP Cookie Manager - to manage cookies.
    • JSR223 PreProcessor - to convert WebDriver cookies into JMeter Cookies.
    • HTTP Request sampler - to send the request to BlazeMeter application - it should bypass any login pages and open the dashboard directly.

The WebDriver Sampler Groovy code, which opens BlazeMeter via a Google account looks like:

import org.openqa.selenium.By
import org.openqa.selenium.support.ui.ExpectedConditions
import org.openqa.selenium.support.ui.WebDriverWait

WDS.sampleResult.sampleStart()

WebDriverWait wait = new WebDriverWait(WDS.browser, 30L)

WDS.browser.get('https://blazemeter.com')

def startTestingNow = wait.until(ExpectedConditions.elementToBeClickable(By.cssSelector('a[class="try-for-free"]')))

startTestingNow.click()

def signIn = wait.until(ExpectedConditions.elementToBeClickable(By.xpath('//a[text()="Sign In"]')))

signIn.click()

def signInWithGoogle = wait.until(ExpectedConditions.elementToBeClickable(By.xpath('//span[text()="Sign In with Google"]')))

signInWithGoogle.click()

def usernameInput = wait.until(ExpectedConditions.elementToBeClickable(By.id('identifierId')))

usernameInput.sendKeys(WDS.vars.get('username'))

def nextButton = wait.until(ExpectedConditions.elementToBeClickable(By.id('identifierNext')))

nextButton.click()

def passwordInput = wait.until(ExpectedConditions.elementToBeClickable(By.cssSelector('input[type="password"]')))

passwordInput.sendKeys(WDS.vars.get('password'))

nextButton = wait.until(ExpectedConditions.elementToBeClickable(By.id('passwordNext')))

nextButton.click()

wait.until(ExpectedConditions.urlContains('a.blazemeter.com/app'))

def cookies = WDS.browser.manage().getCookies()

WDS.props.put('cookies', cookies)
WDS.props.put('URL', WDS.browser.getCurrentUrl())

WDS.sampleResult.sampleEnd()


And the code for the JSR223 PreProcessor is:

import org.apache.jmeter.protocol.http.control.Cookie

def cookies = props.get('cookies')
sampler.setPath(props.get('URL'))

cookies.each { cookie ->
   sampler.getCookieManager().add(new Cookie(cookie.getName(),
   cookie.getValue(),
   cookie.getDomain(),
   cookie.getPath(),
   cookie.isSecure(),
   cookie.getExpiry()?.getTime() ?: System.currentTimeMillis() + 86400000L))
}


Test plan overview and outcome in the View Results Tree listener:

Test plan overview and outcome in the View Results Tree listener.

The JMeter .jmx file can be found in the Bitbucket repository, plus: 

  • The script uses WebDriver Sampler plugin so make sure to install it using JMeter Plugins Manager.
  • Download chromedriver for your Chrome version.
  • Change email and password in the User Defined Variables to your very own ones.
  • Run the test.

You should be able to get the same results. 

Option 2: On OAuth 1.0

OAuth 1.0 has a 3-step authorization flow to accessing protected resources. These are:

Obtaining the Request Token and Token Secret using the Consumer Key and Consumer Secret 
Obtaining the Authorization Token using the Request Token and Secret 
Exchanging the Authorization Token to the Access Token -  which can be used to access protected resources. 

Any of the above requests (and, in fact, any request for protected resources) must be signed using one of the following methods: 

PLAINTEXT
HMAC-SHA1
RSA-SHA1

Put simply, the request parameters will look something like this:

 

oauth_consumer_key

OAuth 1.0 Consumer Key

oauth_token

Access token from above step 3

oauth_signature_method

One of above request signing methods

oauth_timestamp

Current timestamp (in seconds from (01/01/1970)

oauth_nonce

Any random string (usually current timestamp in milliseconds from 01/01/1970)

any other request parameters

 

oauth_signature

calculated depending on the signature type

The most tricky part is the oauth_signature. Here’s how you do it for each method. 

  • PLAINTEXT: URL-encoded Consumer Secret + & + Token Secret
  • HMAC-SHA1: base64 of the sha1 of the Signature Base String - the HTTP Method followed by "&", then the URL of the resource (http or https), and then followed by the parameters which are sent to the endpoint and sorted alphabetically - hashed by the Consumer Secret
  • RSA-SHA1: PKCS#1 of Signature Base String hashed by Consumer’s private RSA key - and again Base64 and URL-encoded. 

Given the complexity of building this request, it’s best to use one of the OAuth Java Client Libraries. In this demo, I’ll be using the:


The right Test Element to be used when developing the OAuth 1.0 client-side code is the JSR223 Sampler with Groovy.

Here is the test flow:

  1. Download Groovy, find the groovy-all.jar and drop it into JMeter’s /lib folder
  2. Do the same for the latest version of the oauth-signpost jar for the /lib folder of your JMeter installation. As for signpost-core-1.2 and JMeter 2.11, the signpost-core.jar should be enough as other dependencies are already part of JMeter.  
  3.  Restart JMeter (if it’s running)
  4. Add a Thread Group to the Test Plan
  5. Add User Defined Variables to the Thread Group or Test Plan. Provide the following variables:
    • consumerKey - key
    • consumerSecret - secret
    • requestUrl - http://oauthbin.com/v1/request-token
    • accessUrl - http://oauthbin.com/v1/access-token
    • protectedUrl - http://oauthbin.com/v1/echo
  6. Add the JSR223 Sampler to the Thread Group
  7. Type “groovy” into the “Language” input
  8. Provide the following script:
import oauth.signpost.OAuth;
import oauth.signpost.OAuthConsumer;
import oauth.signpost.OAuthProvider;
import oauth.signpost.basic.DefaultOAuthConsumer;
import oauth.signpost.basic.DefaultOAuthProvider;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

OAuthConsumer consumer = new DefaultOAuthConsumer(vars.get("consumerKey"),
      vars.get("consumerSecret"));
URL url = new URL(vars.get("requestUrl"));
HttpURLConnection request = (HttpURLConnection) url.openConnection();
consumer.sign(request);
request.connect();
BufferedReader reader = new BufferedReader(new InputStreamReader(
    request.getInputStream()));
String inputLine;
log.info("========== OAuth token and token secret ==========");
while ((inputLine = reader.readLine()) != null)
    log.info(inputLine);
reader.close();
request.disconnect();

OAuthProvider provider = new DefaultOAuthProvider(
    vars.get("requestUrl"), vars.get("accessUrl"),
    vars.get("protectedUrl"));

log.info("========== OAuth Request Token ==========");

log.info(provider.retrieveRequestToken(consumer, OAuth.OUT_OF_BAND));

provider.retrieveAccessToken(consumer, OAuth.OUT_OF_BAND);

url = new URL(vars.get("protectedUrl") + "?jmeter=great");
request = (HttpURLConnection) url.openConnection();
consumer.sign(request);
request.connect();
reader = new BufferedReader(new InputStreamReader(
    request.getInputStream()));
log.info("========== Authenticated Request Response ==========");

while ((inputLine = reader.readLine()) != null)
    log.info(inputLine);    
reader.close();
request.disconnect();

 

  1. Enable the Log Viewer from the “Options” menu.
  2. Run the test and look into the log window. The output should look like the image below:
JMeter OAuth 1.0 test output

Option 3: OAuth 2.0 

OAuth 2.0 is considered simpler and easier than OAuth 1.0/1.0a. It doesn’t require any crazy signatures, timestamps or secrets - and for the simulation, all you have to do is add an HTTP Header Manager as a child of a single request, or at the same level as all requests (depending on the desired scope). 

Only one header is enough to access protected resources:

  • Name: Authorization
  • Value: Bearer ${OAuth2Token}

Without OAuth2Token, you’ll need to either implement the end-user scenario (i.e. Option 1) or take a harder route.

Just like for OAuth 1.0, you’ll need the following libraries to be in the  /lib folder of your JMeter installation:

  • groovy-all-x.jar (downloadable from http://groovy.codehaus.org/Download
  • google-api-client-1.19.0.jar
  • google-api-services-plus-v1-rev137-1.19.0.jar
  • google-http-client-1.19.0.jar
  • google-http-client-jackson2-1.19.0.jar
  • google-oauth-client-1.19.0.jar
  • guava-14.0.jar
  • guava-jdk5-13.0.jar
  • jackson-core-2.1.3.jar
  • jsr305-1.3.9.jar

The Google Plus OAuth2 authentication script looks like this (add the code below to the JSR223 Sampler):

import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.plus.Plus;
import com.google.api.services.plus.PlusScopes;
import com.google.api.services.plus.model.Activity;

String APPLICATION_NAME = vars.get("applicationName");
String SERVICE_ACCOUNT_EMAIL = vars.get("serviceAccountEmail");
JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();

HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
GoogleCredential credential = new GoogleCredential.Builder().setTransport(httpTransport)
    .setJsonFactory(JSON_FACTORY)
    .setServiceAccountId(SERVICE_ACCOUNT_EMAIL)
    .setServiceAccountScopes(Collections.singleton(PlusScopes.PLUS_ME))
    .setServiceAccountPrivateKeyFromP12File(new File(vars.get("p12FilePath")))
    .build();

Plus plus = new Plus.Builder(httpTransport, JSON_FACTORY, credential)
    .setApplicationName(APPLICATION_NAME).build();

String activityId = vars.get("activityId");
Activity activity = plus.activities().get(activityId).execute();
log.info("id: " + activity.getId());
log.info("url: " + activity.getUrl());
log.info("content: " + activity.getObject().getContent());

 

Now, let’s take a look at the User Defined Variables that need to be set and see where to get the appropriate values. 

We’ll need the following:

  1. applicationName - the name of the product which will perform the OAuth authentication and authorization.
  2. serviceAccountEmail - Google Service Account Email Address.
  3. p12FilePath - location of .p12 file containing Google Service Account credentials.
  4. activityId - Google+ activity ID  - protected resource, can be left default - z12gtjhq3qn2xxl2o224exwiqruvtda0i.

You need to have a Google Account to get the relevant values for points 1-3: 

  1. Go to the Google Developer Console at: https://console.developers.google.com/project.
  2. Click the “Create Project” button and provide a Name and ID.
  3. Expand “API and Auth” and go to “Consent Screen”.
  4. Fill out the “Email Address” and “Product Name” fields.
  5. Go to “APIs”.
  6. Turn on “Google+ API”.
  7. Go to “Credentials” and click “Create new Client ID”.
  8. Choose “Service Account” (otherwise you’ll need to provide a redirect URL and use the browser for Google Authentication). See the first section for more information on this.
  9. After clicking the “Create” button, you’ll receive a .p12 key file and password. Save it somewhere so JMeter can access it (i.e. in the /bin folder of your JMeter installation)
  10. You’ll be able to see your Service Email Address and Client ID in the “Credentials” screen.
Credentials screen for JMeter OAuth 2.0 test execution

Populate the User Defined Variables with the values from your Google Developer Console and run your test. Now look at the Log Viewer window: 

Log viewer window

And last but not least, it is possible to open an OAuth-protected application without using any third-party libraries or browsers. You can just correlate dynamic values using JMeter’s built-in test elements. 

Bottom Line

In this blog, we covered the key ways you can access JMeter OAuth-protected resources. If you’re already using BlazeMeter (or considering using it), it is important to note that it supports all of the approaches mentioned above.

Experience BlazeMeter for yourself. Start testing today with your free trial. 

START TESTING NOW


Related Resources: