If you have ever had to integrate with a legacy API (like Jira Server, Tumblr, NetSuite, or Flickr) or a strict financial service, you’ve likely encountered OAuth 1.0a.
While the industry has largely moved to OAuth 2.0 (Bearer tokens), OAuth 1.0a remains strictly enforced in many enterprise environments. The problem? It is notoriously difficult to debug. Implementing the cryptographic signature (HMAC-SHA1) correctly requires byte-perfect precision. If your timestamp is off by a second, or if you encode a space as + instead of %20, the server simply returns 401 Unauthorized with zero explanation.
To make experimenting with OAuth1.0a bearable, I built a Dockerized OAuth 1.0a Sandbox. It’s a standalone provider designed specifically for debugging signature mismatches.
The Problem with Testing Today
Most modern libraries abstract away the handshake, but when things break, they break hard. Public APIs that support OAuth 1.0a are disappearing, making it difficult to find a reliable target to test your client code against. You often end up debugging against a production system that has rate limits and poor error logging.
Useful Logging
I created this sandbox with one main feature in mind: Transparency
Instead of a generic error message, the server prints the exact decrypted headers and the precise Signature Base String it calculated. This allows you to compare your client’s base string against the server’s byte-for-byte to spot the mismatch.
You can find the project here: github.com/galois17/oauth1-sandbox
Key Features
- 🐳 Full Docker Support: Runs with a single command.
- 🔐 Auto-Generated SSL: Automatically creates self-signed certificates for
127.0.0.1.nip.io. - 🛡️ Replay Protection: Implements sophisticated nonce and timestamp validation to mimic strict enterprise servers.
- ** Deep Logging:** See exactly what the server sees.
Quick Start
The server is available as a Docker container. It maps port 9090 and handles the SSL certificate generation for you automatically.
1
2
3
4
5
# Build the image
docker build -t oauth1-server .
# Run it
docker run --rm -p 9090:9090 oauth1-server
Once running, the server is accessible at:
1
https://127.0.0.1.nip.io:9090
Note: I use nip.io to force DNS resolution to localhost while appearing as a “real” domain. This tricks strict OAuth libraries that often reject localhost or IP addresses.
Seeing it in Action The repository includes sample clients in Ruby and Python to demonstrate the full 3-legged flow (Request Token -> User Authorization -> Access Token).
Here is what it looks like when you run the Ruby client against the sandbox.
The Client View:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
--- RUBY CLIENT (FULL FLOW) ---
Target: [https://127.0.0.1.nip.io:9090](https://127.0.0.1.nip.io:9090)
[STEP 1] Requesting Temporary Token...
> Token: BxmQenbHjYK7Mm8PREES8bqyMd3YXA
> Secret: 3YMxYoeaYoSq7mC1qlQOeZ2D6HJ9Tr
[STEP 2] User Authorization
> OPEN THIS URL IN YOUR BROWSER:
> [https://127.0.0.1.nip.io:9090/oauth/authorize?oauth_token=](https://127.0.0.1.nip.io:9090/oauth/authorize?oauth_token=)...
(The server will show you a 6-digit PIN code. Enter it below.)
> PIN Code: 804209
[STEP 3] Exchanging for Access Token...
[SUCCESS] 🚀 OAuth Flow Complete!
> FINAL Access Token: CVmmJ9f3hUq7zGhvC3ORTltvMKCvvH
The Server Logs (The Magic Part): Simultaneously, the Docker container logs the internal validation logic:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
==================================================
INCOMING POST REQUEST
==================================================
URL: [https://127.0.0.1.nip.io:9090/oauth/access_token](https://127.0.0.1.nip.io:9090/oauth/access_token)
Headers: {'Authorization': 'OAuth oauth_consumer_key="ClientKeyMustBeLongEnough00001", oauth_nonce="...", oauth_signature="..." ...}
==================================================
[DEBUG] check_nonce: '891MkTg6Yigf2ruZzZ0LlYYPzv1NtOtOfGzODq4PRw' -> ALLOWED
[DEBUG] check_verifier: '804209' -> ALLOWED
[DEBUG] Validating Verifier: Client says '804209' vs Stored '804209'
[STORE] Issuing Access Token: {'oauth_token': 'CVmmJ9f3hUq7zGhvC3ORTltvMKCvvH'...}
[STORE] Invalidating (Burning) Request Token: BxmQenbHjYK7Mm8PREES8bqyMd3YXA
INFO: 192.168.65.1:26189 - "POST /oauth/access_token HTTP/1.1" 200 OK
Try it out
If you are stuck maintaining a legacy integration, or just want to understand how OAuth signing actually works under the hood, give it a spin.
Check out the code on GitHub: https://github.com/galois17/oauth1-sandbox
