Skip to content

Instantly share code, notes, and snippets.

@linux4life798
Created January 13, 2026 22:15
Show Gist options
  • Select an option

  • Save linux4life798/f7e72ea433053d75bdb7447d122f8a48 to your computer and use it in GitHub Desktop.

Select an option

Save linux4life798/f7e72ea433053d75bdb7447d122f8a48 to your computer and use it in GitHub Desktop.
Messy educational example to understand OIDC flow
#!/bin/bash
# Test and learn about OpenID Connect.
ISSUER="https://auth.blah.com"
REDIRECT_URI="http://127.0.0.1:7777/callback"
CLIENT_ID="BLAHHHHHHH"
CLIENT_SECRET="BLAHHHHHHH"
main() {
echo "Getting openid configuration:"
msg-run curl -s "$ISSUER/.well-known/openid-configuration" | jq .
echo
OPENID_CONFIG="$(curl -s "$ISSUER/.well-known/openid-configuration" | jq .)"
export AUTHZ_EP=$(jq -r .authorization_endpoint <<<"${OPENID_CONFIG}")
export TOKEN_EP=$(jq -r .token_endpoint <<<"${OPENID_CONFIG}")
export USERINFO_EP=$(jq -r .userinfo_endpoint <<<"${OPENID_CONFIG}")
export JWKS_URI=$(jq -r .jwks_uri <<<"${OPENID_CONFIG}")
export DEV_EP=$(jq -r .device_authorization_endpoint <<<"${OPENID_CONFIG}")
export REG_EP=$(jq -r .registration_endpoint <<<"${OPENID_CONFIG}")
# Extract the supported scopes as a space-separated list in SCOPES
#export SCOPES=$(jq -r '.scopes_supported | join(" ")' <<<"${OPENID_CONFIG}")
# export SCOPES="openid profile email"
export SCOPES="openid"
echo "AUTHZ=$AUTHZ_EP"; echo "TOKEN=$TOKEN_EP"; echo "USERINFO=$USERINFO_EP"; echo "JWKS=$JWKS_URI"; echo "DEVICE=$DEV_EP"; echo "REG=$REG_EP"; echo "SCOPES=$SCOPES"
echo
# echo
# echo "Registering client..."
# curl -s -X POST "$REG_EP" \
# -H "Content-Type: application/json" \
# -d @- <<'JSON' | jq .
# {
# "application_type": "web",
# "redirect_uris": ["http://127.0.0.1:7777/callback"],
# "client_name": "Curl PKCE Demo",
# "token_endpoint_auth_method": "none"
# }
# JSON
# Make a base64url-safe random verifier (43-128 chars)
export CODE_VERIFIER="$(openssl rand -base64 96 | tr -d '\n' | tr '+/' '-_' | tr -d '=\n')"
# Compute S256 code_challenge
export CODE_CHALLENGE=$(printf '%s' "$CODE_VERIFIER" \
| openssl dgst -binary -sha256 \
| openssl base64 -A | tr '+/' '-_' | tr -d '=')
# Anti-CSRF and replay protection
export STATE=$(openssl rand -hex 16)
export NONCE=$(openssl rand -hex 16)
echo "verifier=$CODE_VERIFIER"
echo "challenge=$CODE_CHALLENGE"
echo "state=$STATE"
echo "nonce=$NONCE"
echo
# export SCOPES="openid profile email offline_access"
AUTH_URL="$AUTHZ_EP?response_type=code"
AUTH_URL+="&client_id=$(urlencode "$CLIENT_ID")"
AUTH_URL+="&redirect_uri=$(urlencode "$REDIRECT_URI")"
AUTH_URL+="&scope=$(urlencode "$SCOPES")"
AUTH_URL+="&code_challenge=$(urlencode "$CODE_CHALLENGE")"
AUTH_URL+="&code_challenge_method=S256"
AUTH_URL+="&state=$STATE"
AUTH_URL+="&nonce=$NONCE"
echo "Go to this URL:"
echo "$AUTH_URL"
echo
# crude listener to display the incoming request line
CALLBACK_PAGE='<!doctype html><html><body><script>(function(){try{window.open("","_self");window.close();}catch(e){}setTimeout(function(){document.body.innerHTML="You may close this tab.";},200);})();</script></body></html>'
CALLBACK_HTTP_RESPONSE=$'HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n'
CALLBACK_HTTP_RESPONSE+="$CALLBACK_PAGE"
CALLBACK_RESP="$(nc -N -l 127.0.0.1 7777 <<<"$CALLBACK_HTTP_RESPONSE")"
CALLBACK_RESP="$(head -n 1 <<<"$CALLBACK_RESP")"
echo "CALLBACK_RESP=$CALLBACK_RESP"
echo
# Parse the CODE and STATE query params from CALLBACK_RESP
RESP_CODE=$(awk -F '[?& ]' '/GET \/callback/ {for(i=2;i<=NF;i++){if($i ~ /^code=/){split($i,a,"="); print a[2]}}}' <<<"$CALLBACK_RESP")
RESP_STATE=$(awk -F '[?& ]' '/GET \/callback/ {for(i=2;i<=NF;i++){if($i ~ /^state=/){split($i,a,"="); print a[2]}}}' <<<"$CALLBACK_RESP")
DECODED_CODE=$(urldecode "$RESP_CODE")
DECODED_STATE=$(urldecode "$RESP_STATE")
echo "Parsed RESP_CODE=$RESP_CODE"
echo "Parsed STATE=$RESP_STATE"
echo "Decoded CODE=$DECODED_CODE"
echo "Decoded STATE=$DECODED_STATE"
echo
if [ "$DECODED_STATE" == "$STATE" ]; then
echo "** State matches **"
else
echo "** State MISMATCH **"
echo "Continuing anyway..."
fi
echo
echo "Token code exchange request:"
msg-run curl -s -X POST "$TOKEN_EP" \
-H "Content-Type: application/x-www-form-urlencoded" \
--user "${CLIENT_ID}:${CLIENT_SECRET}" \
-d "grant_type=authorization_code" \
-d "code=$DECODED_CODE" \
-d "redirect_uri=$REDIRECT_URI" \
-d "code_verifier=$CODE_VERIFIER" \
| tee token_response.json \
| jq .
echo
ID_TOKEN=$(jq -r .id_token token_response.json)
ACCESS_TOKEN=$(jq -r .access_token token_response.json)
REFRESH_TOKEN=$(jq -r .refresh_token token_response.json) # may be null
# Apparently this id_token is kinda only an openid connect thing,
# not just oauth2.
echo "Decoding ID_TOKEN"
mapfile -t ID_TOKEN_PARTS < <(echo "$ID_TOKEN" | tr '.' '\n')
echo "ID_TOKEN.HEADER=$(base64 -d <<<"${ID_TOKEN_PARTS[0]}")"
echo "ID_TOKEN.PAYLOAD:"
jq . <<<"$(base64 -d <<<"${ID_TOKEN_PARTS[1]}")"
ID_TOKEN_SIGNATURE_LENGTH="$(base64urldecode "${ID_TOKEN_PARTS[2]}" | wc -c)"
echo "ID_TOKEN.SIGNATURE=${ID_TOKEN_PARTS[2]} (len=$ID_TOKEN_SIGNATURE_LENGTH)"
echo
echo "Getting userinfo with ACCESS_TOKEN"
msg-run curl -s "$USERINFO_EP" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
| jq .
echo
echo "Getting userinfo with REFRESH_TOKEN"
msg-run curl -s "$USERINFO_EP" \
-H "Authorization: Bearer $REFRESH_TOKEN" \
| jq .
echo
echo "Getting JWKS for theoretical JWT verification:"
msg-run curl -s "$JWKS_URI" | jq .
}
# Print command in Blue and then run it.
msg-run() {
local f="" redir=""
for f in " <$(readlink /proc/$$/fd/0)" " >$(readlink /proc/$$/fd/1)" " 2>$(readlink /proc/$$/fd/2)"; do
redir+="${f##*/dev/pts/*}"
done
printf "\E[34m%s\E[m\n" "> $*${redir}" >&2
"$@"
local r=$?; printf '\E[%smReturned %d\e[m\n' $((r?31:33)) $r >&2; return $r
}
urldecode() {
python3 -c 'import sys,urllib.parse;print(urllib.parse.unquote_plus(sys.argv[1]))' "$1"
}
urlencode() {
printf %s "$1" | jq -sRr @uri
# python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1]))' "$1"
}
# Decode url encoded base64.
base64urldecode() {
local data="$1"
echo "$data" | tr '_-' '/+' | base64 -d
# Padding to multiple of 4 characters should be done, too.
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment