Providing authentication for your web-based APIs is both a challenging problem but also a largely solved problem. The hardest part is really just working your way through the various options, and from there it’s usually some variation on a drop-in component.

Done properly, it’s also client-agnostic. I can access the service from my browser, I can access it from a thick client, I can access it from cURL. Done incorrectly, well, you get what happened to Amira.

She was trying to pull some statistics from the backend, and couldn’t figure out how to authenticate. So she checked the front-end code to see how it authenticated:

crypt = new JSEncrypt();
crypt.setPublicKey('<removed>');
challenge = "<removed>";
function doChallengeResponse()
{
document.loginForm.password.value.replace(/&/g, '%26');
document.loginForm.password.value.replace(/\\+/g, '%2B');
document.loginForm.password.value = crypt.encrypt(document.loginForm.password.value);
document.loginForm.response.value = document.loginForm.password.value;
document.loginForm.password.value = '';
document.loginForm.submit();
}

On one hand, I want to guess that this code is very old, just based on the document.loginForm approach to interacting with DOM elements. On the other, JSEncrypt was first released in 2013, setting an upper limit on the age.

We send those credentials to the backend using a from submission, which our original developer decided needed some sanitization- all the & and + in the password are escaped, which… shouldn’t be necessary because the form should be doing a POST request, but also, we’ve encrypted the data.

Here’s my suspicion. This code is actually quite old. The original developer copied it from a circa 2005 StackOverflow post, and it didn’t include things like encryption or sending the form as a POST. Over the years, little things got added to it, one after the other, but the core mechanics never really changed.

Amira did check the history, and found that the previous version didn’t use encryption, and instead concatenated the password with the challenge variable, MD5 hashed the two, and sent that over the wire, which… well, almost kinda works, if you don't think about it too hard.

The good news is that once Amira figured out how to get the cookie she needed, the server never expires that token, so she’s free to keep it, send her requests via cURL, and never look at the web form again. She doesn’t have to worry about the cookie being compromised in transit, because the application uses and has always used SSL/TLS.