AWS Cognito Authentication for API Testing with REST-assured

AWS in Test Automation | Expertise: Chūnin

Uchiha Suryajit
4 min readMay 28, 2022

In this article, we will use AWS library to automatically authenticate a given user secured by AWS Cognito and then run automated API tests.

AWS Cognito Service provides Authentication, Authorization, and User Management (as a) Service for any application built on top of AWS infrastructure.

AWS Cognito + REST-assured

Below is a quick intro to AWS Cognito.

Prerequisites:
• App developed on top of AWS infra which uses AWS Cognito
• API Test Automation Framework

Step 1: Get necessary details of your application hosted on AWS and the User used for authentication in automated API tests.

• Pool id
• App client id
• App client secret
• Region
• Username
• Password
• Access key ID
• Access key security

Note: To create a User Access Key for any given User
→ Click on your username near the top right and select Security Credentials.
→ Create a new Access Key (or use existing) present under the Access keys tab.

Step 2: Add aws-java-sdk dependency in your Maven pom.xml or Gradle build file.

<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk</artifactId>
<version>1.12.228</version>
</dependency>

For Gradle:

implementation 'com.amazonaws:aws-java-sdk:1.12.228'

Step 3: Create an AuthenticationHelper class

This helper class is used to perform SRP based client-side mathematical calculations for any given AWS Application Pool ID.

public class AuthenticationHelper {

private static final BigInteger bigIntegerWithGivenPower = BigInteger.valueOf(2);
private static final BigInteger magnitudeBigInteger;
private static final int EPHEMERAL_KEY_LENGTH = 1024;
private static final SecureRandom SECURE_RANDOM;

private BigInteger randomBigInteger;
private BigInteger bigInteger;
private String poolName;

// Seems to be 3072-bit Group from rfc5054
private static final String HEX_N =
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+ "29024E088A67CC74020BBEA63B139B22514A08798E3404DD"
+ "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
+ "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"
+ "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
+ "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F"
+ "83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+ "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B"
+ "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"
+ "DE2BCBF6955817183995497CEA956AE515D2261898FA0510"
+ "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64"
+ "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7"
+ "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B"
+ "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C"
+ "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31"
+ "43DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF";

private static final BigInteger newBigInteger = new BigInteger(HEX_N, 16);

public AuthenticationHelper(String userPoolName) {
do {
randomBigInteger = new BigInteger(EPHEMERAL_KEY_LENGTH, SECURE_RANDOM).mod(newBigInteger);
bigInteger = bigIntegerWithGivenPower.modPow(randomBigInteger, newBigInteger);
} while (bigInteger.mod(newBigInteger).equals(BigInteger.ZERO));

if (userPoolName.contains("_")) {
poolName = userPoolName.split("_", 2)[1];
} else {
poolName = userPoolName;
}
}


public BigInteger getRandomBigInteger() {
return bigInteger;
}

private static final ThreadLocal<MessageDigest> THREAD_MESSAGE_DIGEST =
ThreadLocal.withInitial(() -> {
try {
return MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
throw new CognitoInternalErrorException("Exception in authentication", e);
}
});

static {
try {
SECURE_RANDOM = SecureRandom.getInstance("SHA1PRNG");

MessageDigest messageDigest = THREAD_MESSAGE_DIGEST.get();
messageDigest.reset();
messageDigest.update(newBigInteger.toByteArray());
byte[] digest = messageDigest.digest(bigIntegerWithGivenPower.toByteArray());
magnitudeBigInteger = new BigInteger(1, digest);
} catch (NoSuchAlgorithmException e) {
throw new CognitoInternalErrorException(e.getMessage(), e);
}
}
}

Step 4: Create AccessTokenManager class

This class will be used to generate user credentials using Java SDK provided by AWS. The

I have highlighted the constants used in the below code snippet for which the values were identified in Step 1 itself.

Note: It is advised to use these constants as environment variables fetchable during runtime. Something like below.
System.getenv(“APP_USERNAME”);

/**
* This class will generate Access Token to access the APIs for any given AWS App.
* It calls the InitiateAuth operation with User Credentials with AppUsername and SRP.
* We're using AmazonCognitoIdentity credentials object {AWSCognitoIdentityCredentials}
* to enable authentication of users through third-party identity providers.
*/
@Slf4j
public class AccessTokenManager {

/**
* This method uses AWS CognitoIdentityCredentials to authenticate given user.
* Note: The given user should already be created within the AWS User Identity Pool with identity providers attached.
*/
public static AWSCognitoIdentityProvider getAmazonCognitoIdentityClient() {
BasicAWSCredentials creds = new BasicAWSCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY);
return AWSCognitoIdentityProviderClientBuilder.standard()
.withCredentials(new AWSStaticCredentialsProvider(creds))
.withRegion(REGION)
.build();
}

/**
*
@return This method will generate unique AccessToken consumable by the Login Service to generate a Bearer Token.
*/
public static String getAccessToken() {

AuthenticationResultType authenticationResult = null;
AWSCognitoIdentityProvider cognitoClient = getAmazonCognitoIdentityClient();

final Map<String, String> authParams = new HashMap<>();

authParams.put("USERNAME", USERNAME);
authParams.put("PASSWORD", PASSWORD);
authParams.put("SRP_A", new AuthenticationHelper(POOL_ID).getRandomBigInteger().toString(16));

final InitiateAuthRequest authRequest = new InitiateAuthRequest();

authRequest.withClientId(CLIENT_ID)
.withAuthParameters(authParams)
.withAuthFlow(USER_PASSWORD_AUTH);

InitiateAuthResult result = cognitoClient.initiateAuth(authRequest);
authenticationResult = result.getAuthenticationResult();
String accessToken = authenticationResult.getAccessToken();

cognitoClient.shutdown();
return accessToken;
}
}

Note: Do checkout the various get methods accessible by authenticationResult and use it as per your need. The returned accessToken is all we need to authenticate our API requests.

Step 5: Create an API test using REST-assured java library

This test will use the accessToken generated in Step 4to authenticate the API requests made by our API tests.

public class APITestDemo {

private RequestSpecification requestSpec;
private ResponseSpecification responseSpec;

//Below constants are just used as an example.
//Replace it with your application endpoints.

private static final String APP_ENDPOINT = "https://service-code.region-code.amazonaws.com";
private static final String LOGIN_ROUTE = "/login";

@BeforeClass
public void beforeClassSetup(){

requestSpec = new RequestSpecBuilder()
.setBaseUri(APP_ENDPOINT)
.setContentType(JSON)
.log(ALL).build();

responseSpec = new ResponseSpecBuilder()
.log(ALL).build();
}

@Test
public void validateAuthFlowForAPITestDemo(){

given(requestSpec)
.auth().oauth2(AccessTokenManager.getAccessToken())
.when()
.get(LOGIN_ROUTE)
.then()
.spec(responseSpec)
.extract().response();
}
}

Step 6: Run the API test created in Step 5

You should get a successful JSON log response (depending on your config) after running your test.

Now you can add more API tests as per your need using the above authentication method.

Bonus: We can further refactor our code to remove boilerplate for AWS variables used in Step 4 within AccessTokenManager class. So the getAmazonCognitoIdentityClient() method can be rewritten as below.

public static AWSCognitoIdentityProvider getAmazonCognitoIdentityClient() {
return AWSCognitoIdentityProviderClientBuilder.standard()
.build();
}

--

--

Uchiha Suryajit

I write about tech solutions which are less talked about but are often needed by Devs in their daily routine.