Some API authentication system cannot be accessed using ordinary browser interactions and so cannot be scripted in pure GoScript. In these cases a solution can be to submit bespoke HTTP requests using JavaScript, which can be embedded in GoScript. This guide will explain how to do so.
Note: This article was originally written as an internal document for use within AppCheck to help us create custom API authentication solutions for clients. Some clients have expressed a desire to create these solutions themselves, therefore we are making this document available to clients. Since this article was originally written for an internal audience the tone may be less formal that other articles on our support site, and it may assume a higher than normal level of technical understanding.
Note: This guide assumes existing familiarity with GoScript. If you have not used GoScript before then it is recommended to start with our basic GoSCript Guide: A Guide to GoScript.
Note: Your authentication system, and therefore the steps required to authenticate, may differ from that described in the examples in this guide. A degree of familiarity with your API's authentication system will be required on your part.
If you have any feedback, problems or questions relating to this article please get in touch with our Support team.
Contents
- Overview
- Creating the JavaScript
- Additional Javascript Tips and Tricks
- Wrapping the JavaScript in GoScript for Inclusion in A Scan
Overview
The basic idea is to show the scanner an example of a request being made to your API with a valid session token in the headers (so that the scanner can replicate it), and then a valid response coming back so that the scanner knows what response headers indicate the session is active (so when it stops seeing them it know to re-authenticate.
The basic format will usually be:
- GoScript: Direct the browser to the API's origin (to avoid CORS problems)
- Javascript:
- Make HTTP request to get token
- In the onReadyStatechange callback for that request, make a second request using the received token the request header
- In the onReadyStateChange callback for the second request, print a success message to the document body (any message will do)
- GoScript: wait for the success message you printed.
The scanner will go through this process at the start of the scan, it will see the request headers and remember them (including the token), and it will see the response header that indicates an active session. When it stops seeing that header, it will re-run the script, and "learn" the new headers (including the new token).
Creating the JavaScript
We're going to start by using purely Javascript to get a working solution.
First of all, browse to the API in your preferred browser, that way when we are testing Javascript it's done on the website that it's going to, if you do JS in the console not on the website it won't work because of CORS protection (or at least it shouldn't anyway).
You will need to create two HTTP request in most circumstances, you want to create one that gets the tokens or values required for the authenticated session such as a session token, once you have that you want to fire off another request using the session token to make an API call. This is to make sure you are authenticated and properly forming API calls.
Rather than write your JavaScript directly within the GoScript editor it is advisable to use an IDE or text editor with JavaScript syntax highlighting.
We will start by creating the Request objects:
var xhr = new XMLHttpRequest();
var xhr2 = new XMLHttpRequest();
This is creating 2 new XMLHttpRequest objects as mentioned earlier we will have to do two HTTP Requests.
XHR will be used for the first request which is authenticating and getting any variables we need, and the second one will be used for confirming we are doing everything properly.
The next line of code is the following:
xhr.open('GET', 'http://example.com/v2/auth/authenticate', true);
This line of code is initalizing our first HTTP request, we can use POST here instead of get, then we put in the target URL and don't worry about true it just means that this is going to be done asynchronously.
xhr.setRequestHeader("userId", "appcheckapi@login.com");
This next line is basically setting a request header called userId, in some instances you might need to set request headers like api key or username and password etc so that's what we're using this line for.
You will also be best off setting the content type using these headers like below:
xhr.setRequestHeader("Content-Type", "application/json");
Once that's done you should have the following code:
var xhr = new XMLHttpRequest();
var xhr2 = new XMLHttpRequest();
xhr.open('GET', 'http://example.com/v2/auth/authenticate', true);
xhr.setRequestHeader("userId", "appcheckapi@login.com");
xhr.setRequestHeader("Content-Type", "application/json");
We're then going to add callback functions so our JS knows what to do when the first request is completed, and then what to do once the second request is completed.
var xhr = new XMLHttpRequest();
var xhr2 = new XMLHttpRequest();
xhr.open('GET', 'http://example.com/v2/auth/authenticate', true);
xhr.setRequestHeader("userId", "appcheckapi@login.com");
xhr.setRequestHeader("Content-Type", "application/json");
// new stuff below
xhr.onreadystatechange = function() {
if(xhr.readyState == XMLHttpRequest.DONE && xhr.status === 200) {
xhr2.onreadystatechange = function() {
if(xhr2.readyState === XMLHttpRequest.DONE && xhr2.status === 200) {
}
}
}
}
You can see here we've created both callback functions and the second requests callback function is nested inside the first request, this is because we only want to prepare this callback function once the first request has been fulfilled as we require some details from the first request.
var xhr = new XMLHttpRequest();
var xhr2 = new XMLHttpRequest();
xhr.open('GET', 'http://example.com/v2/auth/authenticate', true);
xhr.setRequestHeader("userId", "appcheckapi@login.com");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = function() {
if(xhr.readyState == XMLHttpRequest.DONE && xhr.status === 200) {
xhr2.open("GET", "https://website.com/someapicall/43", true);
xhr2.setRequestHeader("sessiontoken", xhr.responseText);
xhr2.setRequestHeader("Content-Type", "application/json");
xhr2.onreadystatechange = function() {
if(xhr2.readyState === XMLHttpRequest.DONE && xhr2.status === 200) {
}
}
}
}
So as you can see we've added some headers to the second request and also initialised the second request. This is basically what we did with the first request.
Now the next few lines are the following:
var xhr = new XMLHttpRequest();
var xhr2 = new XMLHttpRequest();
xhr.open('GET', 'http://example.com/v2/auth/authenticate', true);
xhr.setRequestHeader("userId", "appcheckapi@login.com");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = function() {
xhr2.open("GET", "https://website.com/someapicall/43", true);
xhr2.setRequestHeader("sessiontoken", xhr.responseText);
xhr2.setRequestHeader("Content-Type", "application/json");
if(xhr.readyState == XMLHttpRequest.DONE && xhr.status === 200) {
xhr2.onreadystatechange = function() {
if(xhr2.readyState === XMLHttpRequest.DONE && xhr2.status === 200) {
document.write(xhr2.responseText);
}
}
xhr2.send();
}
}
xhr.send();
So we can see here, once the second request is completed we are putting the response on the screen, this is so that if the API call is valid, it's displayed on screen. You can do an API call to get the users details for instance and print them on screen, this gives you something to 'Wait for:' in your GoScript so you know the authentication has been successful.
XHR2.send is then written after the callback function and conditional for the 2nd request is closed and then we are sending the first request once we are outside of both requests conditionals and callback functions.
You can then add further document.writes so that it can help you diagnose any issues and see the process running.
For instance:
var xhr = new XMLHttpRequest();
var xhr2 = new XMLHttpRequest();
xhr.open('GET', 'http://example.com/v2/auth/authenticate', true);
xhr.setRequestHeader("userId", "appcheckapi@login.com");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = function() {
if(xhr.readyState == XMLHttpRequest.DONE && xhr.status === 200) {
document.write('First request success HTTP status 200 </br>');
xhr2.open("GET", "https://website.com/someapicall/43", true);
xhr2.setRequestHeader("sessiontoken", xhr.responseText);
xhr2.setRequestHeader("Content-Type", "application/json");
xhr2.onreadystatechange = function() {
if(xhr2.readyState === XMLHttpRequest.DONE && xhr2.status === 200) {
document.write(xhr2.responseText);
}
}
xhr2.send();
}
}
document.write('Sending first request </br>');
xhr.send();
Once you've got this, paste it into console when currently on the API in the browser and look at the console and also network to make sure everything is going well, if it's not you can diagnose what you're doing wrong there by looking at the requests and responses in the network tab and any errors in the console.
Additional Javascript Tips and Tricks
I need to send JSON in the body of my request, how do I do that ?
Here is a JavaScript example sending a JSON payload in the first request (this example was written for an application using AWS for authentication):var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://example.com', true);
xhr.setRequestHeader("Content-Type", "application/x-amz-json-1.1");
xhr.setRequestHeader("X-Amz-Target", "AWSCognitoIdentityProviderService.InitiateAuth");
xhr.onreadystatechange = function() {
if(xhr.readyState == XMLHttpRequest.DONE && xhr.status === 200) {
var accesstoken = JSON.parse(xhr.responseText)["AuthenticationResult"]["AccessToken"];
//TODO: Do second request here using the accessToken
}
};
var authJSON = {
"AuthParameters": {
"USERNAME": "username",
"PASSWORD": "password"
},
"AuthFlow": "USER_PASSWORD_AUTH" ,
"ClientId": "someToken"
};
xhr.send(JSON.stringify(authJSON));
As you can see, the main difference is we create a dictionary, turn it into a JSON formatted string using JSON.stringify, and submit it as the request body in a POST request (in the previous examples we had created xhr as a GET request). We also receive JSON in the response body so we use JSON.parse to extract the token.
Wrapping the JavaScript in GoScript for Inclusion in A Scan
Building the GoScript from here is pretty easy, it's something like this:
def auth.login
go: https://example.com/api/swagger/index
wait for: swagger js: [JAVASCRIPT HERE] wait for: App Check
First of all we need to go to the API because any JS executed not on the same site will likely fail.
Important: You might notice I put 'Javascript here' and only one JS line, this is required due to the asynchronous nature of the JavaScript. There are several ways to do this, but the way I like to do it is minify the JS so there are no line breaks and essentially it's one big very long line, this means we can just plop it all onto one JS: line.
The wait for command at the end is looking for part of the strings printed by your document.write commands in the page body. If we see these once the script has completed then we know it was successful.
Comments
0 comments
Article is closed for comments.