Starbucks should really make their APIs public
From Slack integrations to coffee buttons, there are loads of potential integrations that could be built if they opened their API to third-party developers.
Motivation
To give credit where it’s due, the Starbucks app is great. I use it (at a minimum) once per day. It has everything I look for in a great mobile experience — coffee, fire 80’s Spotify playlists, and a lack of interaction with other people. I’m clearly not alone in these desires, as 20% of Starbucks transactions in the US are now made using mobile phones.
From Slack integrations to coffee buttons, there are loads of potential integrations that could be built if they opened their API to third-party developers. They’re clearly moving in that direction, as they have both a twitter account and a (password protected) website for developers.
I couldn’t wait, however, so I decided to take matters into my own hands.
They Didn’t Make It Easy
As it turns out, the Starbucks app is a tough nut to crack. Despite the URL being “ open api.starbucks.com,” there are quite a few obscure hoops to jump through before one can even begin analyzing the calls the app makes. As should be the case with any app that processes payments, Starbucks has taken many security measures to secure the APIs their app uses from unauthorized use. Here are some of them:
- SSL certificate pinning
- Fingerprinting attributes of your mobile phone to see if it’s “phonelike”
- Encrypting that fingerprint using AES, with a 256-bit key and a random initialization vector
- Signing requests with the current timestamp
Observing Network Requests
First things first, I needed a way to observe the requests and responses between the Starbucks app and their servers. Normally, I’d just point my iPhone at Charles (or mitmproxy) and I’d be on my way.
Not this time! Since the app uses certificate pinning, I wasn’t able to intercept any of the requests as I normally would. Instead, I had to dig up an old Android phone, root it, install a framework called Xposed, and finally install an extension that injects itself in running applications and disables SSL pinning.
Muahahahahahahah
Once I had that running, I was able to start watching the requests using Charles proxy. This wound up being a bit of a false dawn, as not all of the requests can be easily replayed. That said, I was able to query for basic information like nearby stores, menu items, and card balances.
The result of a nearby store request. Now you know where I work. Eek.
“Easy,” I thought, “I’ll just place an order while connected to the proxy server and replay the requests later!”
Nope.
As it turns out, Starbucks expires their access tokens after an hour, so you can’t simply grab the one your phone is using and hang on to it. That created some challenges…
Logging In
The OAuth endpoint used by the app checks for three parameters before granting a token:
- The ‘sig’ query string parameter. Security researcher Ryan Pickren identified that this is the client key, client secret, and current UNIX timestamp concatenated together and run through an MD5 hashing function.
- The ‘deviceFingerprint’ form parameter. This is a base64-encoded, AES-256 encrypted list of various device attributes. It also changes regularly, as the current time as well as device uptime are included in the fingerprint.
- The ‘X-Cbt’ HTTP header. Another base64-encoded secret string.
I started trying to construct some of these myself. I was able to grab the encryption key used to produce the deviceFingerprint by using a jailbroken iPhone to decrypt the frameworks inside the Starbucks app. After looking at the framework in Hopper for a while, I was eventually able to track down a call to Apple’s CCCrypt function.
https://www.youtube.com/watch?v=o8ZnCT14nRc
I then linked against their SBXData framework (the framework I decrypted earlier on the jailbroken phone) from a quick dummy app, and used Fishhook to rebind that function to my own implementation. This allowed me to dump the key and related parameters to the console:
Figuring out how to generate the ‘X-Cbt’ header was a similar process. For the sake of brevity, I’ll leave that one up to you
Wrapping Up
Now that I was able to sign and fingerprint my login requests, I combined everything into a small Node.js module that allows some basic Starbucks API functions. The good news is that it’s (mostly) hosted here on GitHub!
Voilà! Programmatic coffee.
About Tendigi
We are a mobile design and development studio located in Brooklyn, NY. If you’d like to learn how we can apply our award-winning process of strategy, design, and engineering to your business, drop us a line at hello@tendigi.com. You may also contact me directly at nick@tendigi.com.
Hi Nick, the agency that recently re-did the Starbucks app is relatively small, and perhaps your suggestion would be heard if you reached out to them: Formidable.com
Hi Nick, like your post.
Isn’t the Starbucks API or better say an interface to it published already? https://www.npmjs.com/package/starbucks-api
Looks like you’re the main collaborator there, is it based on the work you’ve described here in this article?
I wrote that library.