A rather strange use of OAuth 2.0

Recently I was asked to look into changing the authentication method to OAuth 2.0 for an integration service. It’s something I’d done enough times in Azure to know what’s involved: The service would need an app registration, which should provide a client ID and secret, an OAuth token request stage, and modifications to include the token in the Authorization header field of whatever was originally being sent.

After a little digging, it turned out the integration was a simple batch script, running on a Windows server, and it used curl to send data to a third-party service. The main part of it looked something like this:

curl -i -X POST -u "Username:Password123" --data-binary "@\\DataDirectory\data-to-upload.json" -H "Content-Type:text/plain" "https://service-provider-upload.net"

I quite like this method of systems integration. Of course, depending on an organisation’s attitude to security, an Azure Logic App might (or might not!) be a safer way of going about it, but something this basic just has to be more reliable than Logic Apps and SSIS packages. If anything fails, it will be a data issue.

Obviously, the biggest problem here is the actual password was relatively short, and, depending on how well the third-party service was secured, might have allowed a brute-force attack. OAuth 2.0 would definitely be an improvement there, if only because the values that replace the username and password are both much longer.

That the password (and now the Client Secret) is hard-coded isn’t so much of an issue - it’s not difficult for anyone with access to the server to read a configuration file or modify the script a little to read from a key vault (they generally rely on IP restrictions for access control). If an adversary does gain access to a production server, we’d have much bigger problems to worry about.

But anyway, how would I go about modifying the script to use OAuth 2.0, I wondered? Unlike a Web browser, curl is stateless/sessionless. We can use it to get an OAuth token, but that token isn’t cached or carried over to a subsequent curl command.

Maybe there is a more elegant way of solving this problem (I’m definitely not an expert here), but I decided to add another curl command to get the token, store it as a variable and pass that variable into the Authorization header field of the next request, which replaces the username:password thing.

The command to get the token and declare it as variable ‘GetToken’ will look roughly like this:

Set "GetToken=curl -v -X POST https://oauth-token-provider.net/oauth2/token -d "client_id=client_id_here" -d "client_secret=client_secret_here" -d "grant_type=client_credentials" -d "scope=service" -H "Content-Type: application/x-www-form-urlencoded""

The Client ID, Client Secret and Scope values are supplied when an application is registered to use OAuth 2.0 with a third-party service. These values ideally wouldn’t be hard-coded in the script here.

The response, obviously, should have a payload that contains the OAuth token. I’ve decided to write the payload to a file (which doesn’t really need a ‘.txt’ extension) and have the script read it back out into a variable:

%GetToken% > response.txt

for /f "delims=" %%a in (response.txt) do (
    set "response_body=%%a"
)

The response payload will look something like:

{"access_token":"long_oauth_token_string_here","scope":"service","token_type":"Bearer","expires_in":9999}

Which brings us to the next problem: We want only the token value, so it must be extracted from the payload. Luckily, batch scripting is pretty good for extracting substrings:

Set "String=%response_body%"
Set "FirstPart=%String:*"access_token":"=%"
Set "SubStr=%FirstPart:","scope"="&:"%"
echo "Access token is:"
Echo %SubStr%

It should then be possible to use the SubStr variable as an Authorization header value for the main curl command. e.g.

curl -i -X POST \ -H "Authorization: Bearer %SubStr%" \ --data-binary '@\\DataDirectory\data-to-upload.json' \ -H 'Content-Type:text/plain' \ https://service-provider-upload.net/oauth/username