Debugging OAuth authorization tokens from your terminal
Here’s a quick post about something that I don’t do often but is typically a pain when I do: debugging OAuth tokens super quickly.
An Example Scenario
You’ve just provisioned a shiny new Keycloak instance and want to make it the upstream identity provider (IdP) to your company’s Okta tenant because you were volun-told to do so.
You’re also using an app that is configured to do single sign-on through
Keycloak and will only log in users if they have admin
in their access token’s
groups
claim.
You’ve created the Okta Identity Provider in Keycloak, set up everything
correctly in Okta, and are able to log into Keycloak through Okta just fine.
However, you’re not able to log into the app despite being in the admin
group in Okta.
What gives?
Helpful tools
We’re not going to solve this problem in this post, especially since the internet solved it already.
What we will do is talk about some CLI tools that can help simplify getting to the bottom of this.
oidc-bash-client.sh
oidc-bash-client.sh
is a
mostly-compliant OAuth2 client written entirely in Bash. It’s entire purpose is
to print tokens received after successfully completing token exchange.
browsh
browsh
is a modern text-based web
browser. It uses Firefox Marionette (like Chromedriver) to render webpages (and
JavaScript!) entirely in your terminal.
Here’s a TL;DR of how to use it for a two-legged authorization code OAuth login:
✅ Make sure that your OAuth client has
http://127.0.0.1:8080
as a valid redirect URL before doing this.
- First, clone the script somewhere:
git clone https://github.com/please-openit/oidc-bash-client /tmp
-
Run the command below to get an auth code. This will use
netcat
to spin up a local webserver that will capture redirects./tmp/oidc-bash-client/oidc-client.sh \ --operation authorization_code_grant \ --client-id $CLIENT_ID \ --openid-endpoint https://$KEYCLOAK_INSTANCE/.well-known/openid-configuration \ --redirect-uri http://127.0.0.1:8080 \ --scope "$SCOPES"
This will print something like the below:
OPEN THIS URI IN YOUR WEB BROWSER https://$KEYCLOAK_INSTANCE/realms/tanzu-products/protocol/openid-connect/auth?client_id=$CLIENT_ID&scope=$SCOPES&response_type=code&response_mode=fragment&redirect_uri=http://127.0.0.1:8080&acr_values= -- LISTENING ON PORT 8080 FOR A REDIRECT
Here, we could copy this URL and paste it in a web browser. But that’s lame. You’re probably reading this inside of
w3m
entirely in your terminal. Can’t be bothered.Instead, open another terminal tab and open it in
browsh
:browsh --startup-url '$URL_YOU_COPIED'
This will give you a really cool retro 8-bit version of what you’d normally see in your browser.
After you log in, you’ll get an auth code back, like this:
{"state":"foobar","session_state":"e4dc98f7-bed4-49da-b81a-672c2011d30c","code":"f905d57d-e6aa-4109-9ffc-789752e146df.e4dc98f7-bed4-49da-b81a-672c2011d30c.1e75bca2-981c-4ddb-a12a-edb6e7896431"}
oidc-bash-client
will also exit.Copy the code, then go to the next step.
-
Run the command below to exchange the code you received for access, ID and refresh tokens:
/tmp/oidc-bash-client/oidc-client.sh \ --operation auth_code \ --client-id "$CLIENT_ID" \ --client-secret "$CLIENT_SECRET" \ --openid-endpoint https://$KEYCLOAK_INSTANCE/realms/$KEYCLOAK_REALM/.well-known/openid-configuration \ --redirect-uri http://127.0.0.1:8080 \ --authorization-code 'f905d57d-e6aa-4109-9ffc-789752e146df.e4dc98f7-bed4-49da-b81a-672c2011d30c.1e75bca2-981c-4ddb-a12a-edb6e7896431'
This will give you a JSON payload with all three in it!
{ "access_token": "$ACCESS_TOKEN", "expires_in": 300, "refresh_expires_in": 1800, "refresh_token": "$REFRESH_TOKEN", "token_type": "Bearer", "not-before-policy": 1698706734, "session_state": "e4dc98f7-bed4-49da-b81a-672c2011d30c", "scope": "profile tenant_id groups full_name email" }
At this point, you would take the Base64-encoded access_token
JWT to
jwt.io and decode it to get the contents of the token.
We’re better than that.
We’ll use jq
to decode the token right here, right now.
(Big thanks to Vithal Reddy for this one.)
-
Copy the JSON blob (you won’t be able to get it again without starting from step [2]) and save it as a variable:
json=$(pbpaste) # if you're on a Mac!
-
Use
jq
to split up the JWT by period and Base64-decode each fragent:echo "$json" | jq -r '.access_token | split(".") | .[1] | @base64 -d | fromjson'
And wa la! Your decrypted JWT appears!
{
"exp": 1698769211,
"iat": 1698768911,
"auth_time": 1698768896,
"jti": "96aad20d-62af-4a8c-89c3-569d92619ad6",
...rest of the token
}