Created
January 13, 2026 22:15
-
-
Save linux4life798/f7e72ea433053d75bdb7447d122f8a48 to your computer and use it in GitHub Desktop.
Messy educational example to understand OIDC flow
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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