├── LICENSE ├── README.md ├── img ├── google_1.png ├── google_auth_code.png ├── google_token.png └── google_user_infos.png └── oidc-client.sh /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 please-open.it 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # please-open.it bash oauth2/openid connect wrapper 2 | 3 | ## Purpose 4 | [please-open.it](https://please-open.it) specializes in authentication and web security, and provides Keycloak as a service. 5 | 6 | This bash script is an oauth2 wrapper. You can make any authentication request from command line with the right arguments. 7 | 8 | Supported operations are : 9 | 10 | - get oidc server infos 11 | - client credentials 12 | - implicit flow 13 | - authorization code flow 14 | - resource owner password 15 | - auth code exchange 16 | - token exchange 17 | - refresh token 18 | - end session 19 | - token introspection 20 | - user infos 21 | - device code 22 | - poll token 23 | 24 | Use it as a guide for oauth2/openid connect discovery or in any testing/integration process. Script is totally stateless, save the output of a command in variables to reuse tokens. 25 | 26 | ## Install 27 | 28 | You need curl, jq and netcat installed. 29 | 30 | Note : on Debian, install netcat-openbsd version 31 | 32 | ``` 33 | apt-get install netcat-openbsd 34 | ``` 35 | 36 | ## Quick tour Using Google 37 | 38 | ### Discovery document 39 | 40 | Google is an OpenIdConnect provider, you can get the [discovery document](https://developers.google.com/identity/protocols/oauth2/openid-connect#discovery) at https://accounts.google.com/.well-known/openid-configuration 41 | 42 | ``` 43 | ./oidc-client.sh --operation get_oidc_server_infos --openid-endpoint https://accounts.google.com/.well-known/openid-configuration 44 | ``` 45 | 46 |  47 | 48 | ### Get a new token 49 | 50 | *Using authorization_code_grant process, a standard for the web supported by all providers, you can get a new token from an authorization_code. To get a new authorization_code, 51 | 52 | ``` 53 | ./oidc-client.sh --operation authorization_code_grant --client-id 947227895516-68tp60nti613r42u41bch5vesr5iqpbi.apps.googleusercontent.com --openid-endpoint https://accounts.google.com/.well-known/openid-configuration --redirect-uri http://127.0.0.1:8080 --scope email 54 | ``` 55 | 56 | Then the console shows : 57 | 58 | ``` 59 | OPEN THIS URI IN YOUR WEB BROWSER 60 | https://accounts.google.com/o/oauth2/v2/auth?client_id=947227895516-68tp60nti613r42u41bch5vesr5iqpbi.apps.googleusercontent.com&scope=email&response_type=code&response_mode=fragment&redirect_uri=http://127.0.0.1:8080 61 | -- LISTENING ON PORT 8080 FOR A REDIRECT 62 | ``` 63 | 64 | > ✅ Add `--state` (default: "foobar") to the end of the command above to 65 | > provide a state and/or `--nonce` (default: today's date in UNIX time) to add a 66 | > nonce. You can also provide a custom state/nonce by adding a string after 67 | > `--state` or `--nonce`. 68 | 69 | Open your web browser and paste the URI shown in the console. You will have a login screen then a consent screen. Afterthat, redirect process will show a page from 127.0.0.1:8080 with all given data. Get your authorization code. 70 | 71 | Second step is exchanging an authorization_code for a token. The operation "auth_code" is designed for : 72 | 73 | ``` 74 | ./oidc-client.sh --operation auth_code --client-id 947227895516-68tp60nti613r42u41bch5vesr5iqpbi.apps.googleusercontent.com --client-secret ****************** --openid-endpoint https://accounts.google.com/.well-known/openid-configuration --redirect-uri http://127.0.0.1:8080 --authorization-code 4/0AH1T7cXxcNot62nDDUspvbINZ6vP3Qk9oEiy6xhJmuRbIIdJw65LdOc2QPWKO8qaHTQ67g1qg96mjS5R9dWVFs 75 | ``` 76 | 77 | > ✅ If you provided `--state` and `--nonce` earlier, make sure to provide them 78 | > to `auth_code`. Otherwise, the token exchange will fail. 79 | 80 |  81 | 82 | ### User infos 83 | 84 | using the access_token we got previously : 85 | 86 |  87 | 88 | ## Build for Keycloak ? 89 | 90 | Of course ! Keycloak has its own discovery document (for a realm) with the "OpenID Endpoint Configuration" link. Keycloak supports all operations of this bash script, please refer to the documentation links for each operation below. 91 | 92 | Feel free to test it with a realm you can get at [https://realms.please-open.it](https://realms.please-open.it). 93 | 94 | ## Filter 95 | 96 | Each operation output some formatted JSON code. By using --field argument, you can filter the output with JQ [grammar](https://jqplay.org/). 97 | 98 | ## Operations 99 | 100 | ### get_oidc_server_infos 101 | 102 | [https://openid.net/specs/openid-connect-discovery-1_0.html](https://openid.net/specs/openid-connect-discovery-1_0.html) 103 | 104 | Get oauth2 and openid connect endpoint locations using the discovery document. 105 | 106 | --openid-endpoint 107 | 108 | ### client_credentials 109 | 110 | [https://www.mathieupassenaud.fr/oauth-backend/](https://www.mathieupassenaud.fr/oauth-backend/) 111 | 112 | Perform client_credentials authentication request. 113 | 114 | --openid-endpoint OR --token-endpoint 115 | 116 | --client-id 117 | 118 | --client-secret 119 | 120 | ### resource_owner_password_grant 121 | 122 | [https://oauthlib.readthedocs.io/en/latest/oauth2/grants/password.html](https://oauthlib.readthedocs.io/en/latest/oauth2/grants/password.html) 123 | 124 | Make an authentication request with client credentials and user credentials. 125 | 126 | --openid-endpoint OR --token-endpoint 127 | 128 | --client-id 129 | 130 | --client-secret (optionnal in case of public client) 131 | 132 | --username 133 | 134 | --password 135 | 136 | --scope 137 | 138 | ### end_session 139 | 140 | Just log out the user by revoking tokens 141 | 142 | --openid-endpoint OR --end-session-endpoint 143 | 144 | --access-token 145 | 146 | --refresh-token 147 | 148 | --client-id 149 | 150 | --client-secret (optionnal if client is public) 151 | 152 | ### refresh_token 153 | 154 | Renew an access_token using a refresh_token 155 | 156 | --openid-endpoint OR --token-endpoint 157 | 158 | --client-id 159 | 160 | --client-secret (optionnal if client is public) 161 | 162 | --refresh-token 163 | 164 | ### token_exchange 165 | 166 | [https://www.mathieupassenaud.fr/token-exchange-keycloak/](https://www.mathieupassenaud.fr/token-exchange-keycloak/) 167 | 168 | Perform a token_exchange operation 169 | 170 | --openid-endpoint OR --token-endpoint 171 | 172 | --client-id 173 | 174 | --client-secret 175 | 176 | --access_token 177 | 178 | --issuer 179 | 180 | ### implicit_grant 181 | 182 | [https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-implicit-grant-flow](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-implicit-grant-flow) 183 | 184 | FOR LEGACY APPLICATIONS ONLY 185 | 186 | Initiate a connection by showing an URL you have to open using your local web browser. 187 | 188 | Then, a local web server (using netcat) will respond with a single page showing tokens returned by the authentication server in the redirect_uri. 189 | 190 | By default, redirect URI has to be http://127.0.0.1:8080, you can have your own web server for this usage. 191 | 192 | --openid-endpoint OR --authorization-endpoint 193 | 194 | --client-id 195 | 196 | --redirect-uri 197 | 198 | --scope 199 | 200 | ### authorization_code_grant 201 | 202 | [https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow) 203 | 204 | Initiate a connection by showing an URL you have to open using your local web browser. 205 | 206 | Then, a local web server (using netcat) will respond with a single page showing the authorization_code returned by the authentification server in the redirect_uri. 207 | 208 | By default, redirect URI has to be http://127.0.0.1:8080, you can have your own web server for this usage. 209 | 210 | --openid-endpoint OR --authorization-endpoint 211 | 212 | --client-id 213 | 214 | --redirect-uri 215 | 216 | --scope 217 | 218 | --state [STATE] (default: "foobar") 219 | 220 | --nonce [NONCE] (default: `date +%s`) 221 | 222 | ### auth_code 223 | 224 | Change an authorization_code to a token 225 | 226 | --openid-endpoint OR --token-endpoint 227 | 228 | --client-id 229 | 230 | --client-secret (optionnal for public clients) 231 | 232 | --redirect-uri 233 | 234 | --state [STATE] (default: "foobar") 235 | 236 | --nonce [NONCE] (default: `date +%s`) 237 | 238 | ### token_introspect 239 | 240 | [https://www.oauth.com/oauth2-servers/token-introspection-endpoint/](https://www.oauth.com/oauth2-servers/token-introspection-endpoint/) 241 | 242 | Get details about a token 243 | 244 | --openid-endpoint OR --token-introspection-endpoint 245 | 246 | --client-id 247 | 248 | --client-secret 249 | 250 | --access-token 251 | 252 | ### user_info 253 | 254 | Get all user informations 255 | 256 | --openid-endpoint OR --userinfo-endpoint 257 | 258 | --access-token 259 | 260 | ### device_code 261 | 262 | Initiate a device_code flow 263 | 264 | --openid-endpoint OR --device-authorization-endpoint 265 | 266 | --client-id 267 | 268 | --client-secret 269 | 270 | ### poll_token 271 | 272 | Get a token (poll operation, renew if necessary), IE for a device_code flow 273 | 274 | --openid-endpoint OR --token-endpoint 275 | 276 | --client-id 277 | 278 | --client-secret 279 | 280 | --device-code 281 | -------------------------------------------------------------------------------- /img/google_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/please-openit/oidc-bash-client/50df2891680bad00246ef3f5091296e100e5effb/img/google_1.png -------------------------------------------------------------------------------- /img/google_auth_code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/please-openit/oidc-bash-client/50df2891680bad00246ef3f5091296e100e5effb/img/google_auth_code.png -------------------------------------------------------------------------------- /img/google_token.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/please-openit/oidc-bash-client/50df2891680bad00246ef3f5091296e100e5effb/img/google_token.png -------------------------------------------------------------------------------- /img/google_user_infos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/please-openit/oidc-bash-client/50df2891680bad00246ef3f5091296e100e5effb/img/google_user_infos.png -------------------------------------------------------------------------------- /oidc-client.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | HTTP_PORT=8080 4 | 5 | function get_oidc_server_infos { 6 | curl -sS $OPENID_ENDPOINT | jq $FIELD -r 7 | } 8 | 9 | function client_credentials { 10 | curl -sS --request POST --url $TOKEN_ENDPOINT \ 11 | --header 'Accept: */*' \ 12 | --header 'Content-Type: application/x-www-form-urlencoded' \ 13 | --data client_id=$CLIENT_ID \ 14 | --data client_secret=$CLIENT_SECRET \ 15 | --data grant_type=client_credentials \ 16 | | jq $FIELD -r 17 | } 18 | 19 | function token_exchange { 20 | curl -sS --request POST --url $TOKEN_ENDPOINT \ 21 | --header 'Accept: */*' \ 22 | --header 'Content-Type: application/x-www-form-urlencoded' \ 23 | --data client_id=$CLIENT_ID \ 24 | --data client_secret=$CLIENT_SECRET \ 25 | --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \ 26 | --data-urlencode "subject_token_type=urn:ietf:params:oauth:token-type:access_token" \ 27 | --data subject_token=$ACCESS_TOKEN \ 28 | --data subject_issuer=$ISSUER \ 29 | | jq $FIELD -r 30 | 31 | } 32 | 33 | function resource_owner_password_grant { 34 | curl -sS --request POST --url $TOKEN_ENDPOINT \ 35 | --header 'Accept: */*' \ 36 | --header 'Content-Type: application/x-www-form-urlencoded' \ 37 | --data client_id=$CLIENT_ID \ 38 | --data client_secret=$CLIENT_SECRET \ 39 | --data username=$USERNAME \ 40 | --data password=$PASSWORD \ 41 | --data scope=$SCOPE \ 42 | --data grant_type=password \ 43 | | jq $FIELD -r 44 | 45 | } 46 | 47 | function refresh_token { 48 | curl -sS --request POST --url $TOKEN_ENDPOINT \ 49 | --header 'Accept: */*' \ 50 | --header 'Content-Type: application/x-www-form-urlencoded' \ 51 | --data client_id=$CLIENT_ID \ 52 | --data client_secret=$CLIENT_SECRET \ 53 | --data refresh_token=$REFRESH_TOKEN \ 54 | --data grant_type=refresh_token \ 55 | | jq $FIELD -r 56 | 57 | } 58 | 59 | function token_introspect { 60 | curl -sS --request POST --url $TOKEN_INTROSPECTION_ENDPOINT \ 61 | --header 'Accept: */*' \ 62 | --header 'Content-Type: application/x-www-form-urlencoded' \ 63 | --user $CLIENT_ID:$CLIENT_SECRET \ 64 | --data token=$ACCESS_TOKEN \ 65 | | jq $FIELD -r 66 | } 67 | 68 | function user_info { 69 | curl -sS --request GET --url $USERINFO_ENDPOINT \ 70 | --header 'Accept: */*' \ 71 | --header 'Content-Type: application/x-www-form-urlencoded' \ 72 | --header "Authorization: Bearer $ACCESS_TOKEN"\ 73 | | jq $FIELD -r 74 | } 75 | 76 | function device_code { 77 | curl -sS --request POST --url $DEVICE_ENDPOINT \ 78 | --header 'Accept: */*' \ 79 | --header 'Content-Type: application/x-www-form-urlencoded' \ 80 | --data client_id=$CLIENT_ID \ 81 | --data client_secret=$CLIENT_SECRET \ 82 | | jq $FIELD -r 83 | } 84 | 85 | function poll_token { 86 | curl -sS --request POST --url $TOKEN_ENDPOINT \ 87 | --header 'Accept: */*' \ 88 | --header 'Content-Type: application/x-www-form-urlencoded' \ 89 | --data client_id=$CLIENT_ID \ 90 | --data client_secret=$CLIENT_SECRET \ 91 | --data-urlencode device_code=$DEVICE_CODE \ 92 | --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:device_code" \ 93 | | jq $FIELD -r 94 | } 95 | 96 | function implicit_grant { 97 | echo "OPEN THIS URI IN YOUR WEB BROWSER" 98 | echo "$AUTHORIZATION_ENDPOINT?client_id=$CLIENT_ID&scope=$SCOPE&response_type=token&response_mode=query&redirect_uri=$REDIRECT_URI&acr_values=$ACR" 99 | 100 | echo "-- LISTENING ON PORT $HTTP_PORT FOR A REDIRECT" 101 | 102 | # listening for a reponse 103 | # from : https://stackoverflow.com/questions/26455434/create-a-minimum-rest-web-server-with-netcat-nc 104 | rm -f /tmp/out 105 | mkfifo /tmp/out 106 | trap "rm -f /tmp/out" EXIT 107 | cat /tmp/out | nc -l $HTTP_PORT > >( # parse the netcat output, to build the answer redirected to the pipe "/tmp/out". 108 | export REQUEST= 109 | while read line 110 | do 111 | line=$(echo "$line" | tr -d '[\r\n]') 112 | if echo "$line" | grep -qE '^GET /' # if line starts with "GET /" 113 | then 114 | REQUEST=$(echo "$line" | cut -d ' ' -f2) # extract the request 115 | elif [ "x$line" = x ] # empty line / end of request 116 | then 117 | HTTP_200="HTTP/1.1 200 OK" 118 | HTTP_LOCATION="Location:" 119 | HTTP_404="HTTP/1.1 404 Not Found" 120 | # call a script here 121 | # Note: REQUEST is exported, so the script can parse it (to answer 200/403/404 status code + content) 122 | if echo $REQUEST | grep -qE '^/' 123 | then 124 | # https://riptutorial.com/bash/example/29664/request-method--get 125 | ACCESS_TOKEN=$(echo "$REQUEST" | sed -n 's/^.*access_token=\([^&]*\).*$/\1/p') 126 | HTML="
" 139 | printf "%s\n%s %s\n\n%s\n" "$HTTP_200" "$HTTP_LOCATION" $REQUEST $HTML > /tmp/out 140 | exit 0 141 | fi 142 | fi 143 | done 144 | exit 1 145 | ) 146 | } 147 | 148 | function authorization_code_grant { 149 | if [ ! -z "$PKCE" ] ; then 150 | echo "-- Generating PKCE CODES" 151 | gen_pkce_codes 152 | fi 153 | 154 | params="$AUTHORIZATION_ENDPOINT?client_id=$CLIENT_ID&scope=$SCOPE&response_type=code&response_mode=query&redirect_uri=$REDIRECT_URI&acr_values=$ACR" 155 | [[ "$ADD_STATE" == 'true' ]] && params="$params&state=$STATE" 156 | [[ "$ADD_NONCE" == 'true' ]] && params="$params&nonce=$NONCE" 157 | [[ "$PKCE" == 'true' ]] && params="$params&code_challenge_method=S256&code_challenge=$CHALLENGE" 158 | echo "OPEN THIS URI IN YOUR WEB BROWSER" 159 | echo "$params" 160 | 161 | echo "-- LISTENING ON PORT $HTTP_PORT FOR A REDIRECT" 162 | 163 | # listening for a reponse 164 | # from : https://stackoverflow.com/questions/26455434/create-a-minimum-rest-web-server-with-netcat-nc 165 | rm -f /tmp/out 166 | mkfifo /tmp/out 167 | trap "rm -f /tmp/out" EXIT 168 | cat /tmp/out | nc -l $HTTP_PORT > >( # parse the netcat output, to build the answer redirected to the pipe "/tmp/out". 169 | export REQUEST= 170 | while read line 171 | do 172 | line=$(echo "$line" | tr -d '[\r\n]') 173 | if echo "$line" | grep -qE '^GET /' # if line starts with "GET /" 174 | then 175 | REQUEST=$(echo "$line" | cut -d ' ' -f2) # extract the request 176 | elif [ "x$line" = x ] # empty line / end of request 177 | then 178 | HTTP_200="HTTP/1.1 200 OK" 179 | HTTP_LOCATION="Location:" 180 | HTTP_404="HTTP/1.1 404 Not Found" 181 | # call a script here 182 | # Note: REQUEST is exported, so the script can parse it (to answer 200/403/404 status code + content) 183 | if echo $REQUEST | grep -qE '^/' 184 | then 185 | # https://riptutorial.com/bash/example/29664/request-method--get 186 | ACCESS_TOKEN=$(echo "$REQUEST" | sed -n 's/^.*access_token=\([^&]*\).*$/\1/p') 187 | echo AUTHORIZATION_CODE=$(echo "$REQUEST" | sed -n 's/^.*code=\([^&]*\).*$/\1/p') >/tmp/code 188 | HTML="You can close this window." 189 | printf "%s\n%s %s\n\n%s\n" "$HTTP_200" "$HTTP_LOCATION" $REQUEST $HTML > /tmp/out 190 | exit 0 191 | fi 192 | fi 193 | done 194 | exit 1 195 | ) 196 | 197 | . /tmp/code 198 | 199 | # if auth code set, then request 200 | if [ ! -z "$AUTHORIZATION_CODE" ] ; then 201 | auth_code 202 | fi 203 | 204 | } 205 | 206 | function auth_code { 207 | args=(--request POST --url $TOKEN_ENDPOINT \ 208 | --header 'Accept: */*' \ 209 | --header 'Content-Type: application/x-www-form-urlencoded' \ 210 | --data client_id=$CLIENT_ID \ 211 | --data client_secret=$CLIENT_SECRET \ 212 | --data-urlencode code=$AUTHORIZATION_CODE \ 213 | --data redirect_uri=$REDIRECT_URI \ 214 | --data grant_type=authorization_code 215 | ) 216 | [[ "$ADD_STATE" == 'true' ]] && args+=(--data state="$STATE") 217 | [[ "$ADD_NONCE" == 'true' ]] && args+=(--data nonce="$NONCE") 218 | [[ "$PKCE" == 'true' ]] && args+=(--data code_verifier="$VERIFIER") 219 | curl -sS "${args[@]}" | jq $FIELD -r 220 | } 221 | 222 | function end_session { 223 | curl -sS --request POST --url $END_SESSION_ENDPOINT \ 224 | --header 'Accept: */*' \ 225 | --header 'Content-Type: application/x-www-form-urlencoded' \ 226 | --header 'Authorization: Bearer $ACCESS_TOKEN' \ 227 | --data client_id=$CLIENT_ID \ 228 | --data client_secret=$CLIENT_SECRET \ 229 | --data refresh_token=$REFRESH_TOKEN \ 230 | | jq $FIELD -r 231 | } 232 | 233 | function gen_pkce_codes() { 234 | echo "Generating PKCE Verifier" 235 | VERIFIER=$(LC_CTYPE=C && LANG=C && cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 50 | head -n 1) 236 | echo "Verifier is: $VERIFIER" 237 | #Generate PKCE Challenge from Verifier and convert / + = characters" 238 | CHALLENGE=$(/bin/echo -n $VERIFIER | shasum -a 256 | cut -d " " -f 1 | xxd -r -p | base64 | tr / _ | tr + - | tr -d =) 239 | echo "Challenge is: $CHALLENGE" 240 | echo "" 241 | echo "*********************" 242 | } 243 | 244 | 245 | function show_help { 246 | 247 | echo "PLEASE-OPEN.IT BASH CLIENT" 248 | echo "SYNOPSIS" 249 | echo "" 250 | echo "oidc-client.sh --operation OP --openid-endpoint [--authorization-endpoint --token-introspection-endpoint --token-endpoint --end-session-endpoint --device-authorization-endpoint --userinfo-endpoint] --client-id --client-secret --username --password --scope --access-token --refresh-token --issuer --redirect-uri --authorization-code --device-code --acr --field --enable-pkce " 251 | 252 | 253 | 254 | echo "DESCRIPTION" 255 | echo "" 256 | echo "This script is a wrapper over oauth2/openid connect" 257 | 258 | echo "OPTIONS" 259 | echo " --operation in : " 260 | echo " get_oidc_server_infos" 261 | echo " client_credentials" 262 | echo " resource_owner_password_grant" 263 | echo " end_session" 264 | echo " refresh_token" 265 | echo " token_exchange" 266 | echo " implicit_grant" 267 | echo " authorization_code_grant" 268 | echo " auth_code" 269 | echo " token_introspect" 270 | echo " user_info" 271 | echo " device_code" 272 | echo " poll_token" 273 | echo "" 274 | echo " --field : filter for JQ" 275 | echo " --redirect-http-port : open a port and listen for a redirect" 276 | echo " --random-redirect-http-port : open a random port and listen for a redirect" 277 | echo " --enable-pkce: to use PKCE authorization code flow" 278 | 279 | echo "" 280 | echo "More : " 281 | } 282 | 283 | PARAMS="" 284 | while (( "$#" )); do 285 | case "$1" in 286 | --help) 287 | show_help 288 | shift 289 | ;; 290 | --operation) 291 | if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then 292 | OPERATION=$2 293 | shift 2 294 | else 295 | echo "Error: Argument for $1 is missing" >&2 296 | exit 1 297 | fi 298 | ;; 299 | --openid-endpoint) 300 | if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then 301 | OPENID_ENDPOINT=$2 302 | shift 2 303 | fi 304 | ;; 305 | --authorization-endpoint) 306 | if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then 307 | AUTHORIZATION_ENDPOINT=$2 308 | shift 2 309 | fi 310 | ;; 311 | --token-introspection-endpoint) 312 | if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then 313 | TOKEN_INTROSPECTION_ENDPOINT=$2 314 | shift 2 315 | fi 316 | ;; 317 | --device-authorization-endpoint) 318 | if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then 319 | DEVICE_ENDPOINT=$2 320 | shift 2 321 | fi 322 | ;; 323 | --userinfo-endpoint) 324 | if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then 325 | USERINFO_ENDPOINT=$2 326 | shift 2 327 | fi 328 | ;; 329 | --redirect-http-port) 330 | if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then 331 | HTTP_PORT=$2 332 | REDIRECT_URI=http://localhost:$HTTP_PORT 333 | shift 2 334 | fi 335 | ;; 336 | --random-redirect-http-port) 337 | nc -l localhost 0 & 338 | 339 | HTTP_PORT=$(lsof -i \ 340 | | grep $! \ 341 | | awk '{print $9}' \ 342 | | cut -d':' -f2;) 343 | REDIRECT_URI=http://localhost:$HTTP_PORT 344 | 345 | kill $!; 346 | shift 347 | ;; 348 | --enable-pkce) 349 | PKCE=true 350 | shift 351 | ;; 352 | --token-endpoint) 353 | if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then 354 | TOKEN_ENDPOINT=$2 355 | shift 2 356 | fi 357 | ;; 358 | --client-id) 359 | if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then 360 | CLIENT_ID=$2 361 | shift 2 362 | fi 363 | ;; 364 | --client-secret) 365 | if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then 366 | CLIENT_SECRET=$2 367 | shift 2 368 | fi 369 | ;; 370 | --username) 371 | if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then 372 | USERNAME=$2 373 | shift 2 374 | fi 375 | ;; 376 | --password) 377 | if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then 378 | PASSWORD=$2 379 | shift 2 380 | fi 381 | ;; 382 | --scope) 383 | if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then 384 | SCOPE=$2 385 | shift 2 386 | fi 387 | ;; 388 | --access-token) 389 | if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then 390 | ACCESS_TOKEN=$2 391 | shift 2 392 | fi 393 | ;; 394 | --refresh-token) 395 | if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then 396 | REFRESH_TOKEN=$2 397 | shift 2 398 | fi 399 | ;; 400 | --end-session-endpoint) 401 | if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then 402 | END_SESSION_ENDPOINT=$2 403 | shift 2 404 | fi 405 | ;; 406 | --issuer) 407 | if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then 408 | ISSUER=$2 409 | shift 2 410 | fi 411 | ;; 412 | --redirect-uri) 413 | if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then 414 | REDIRECT_URI=$2 415 | shift 2 416 | fi 417 | ;; 418 | --authorization-code) 419 | if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then 420 | AUTHORIZATION_CODE=$2 421 | shift 2 422 | fi 423 | ;; 424 | --device-code) 425 | if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then 426 | DEVICE_CODE=$2 427 | shift 2 428 | fi 429 | ;; 430 | --acr) 431 | if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then 432 | ACR=$2 433 | shift 2 434 | fi 435 | ;; 436 | --field) 437 | if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then 438 | FIELD=$2 439 | shift 2 440 | fi 441 | ;; 442 | --state) 443 | ADD_STATE=true 444 | STATE=foobar 445 | if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then 446 | ADD_STATE=true 447 | STATE="$2" 448 | shift 2 449 | else shift 1 450 | fi 451 | ;; 452 | --nonce) 453 | ADD_NONCE=true 454 | NONCE="$(date +%s)" 455 | if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then 456 | ADD_NONCE=true 457 | NONCE="$2" 458 | shift 2 459 | else shift 1 460 | fi 461 | ;; 462 | -*|--*=) # unsupported flags 463 | echo "Error: Unsupported flag $1" >&2 464 | exit 1 465 | ;; 466 | *) # preserve positional arguments 467 | PARAMS="$PARAMS $1" 468 | shift 469 | ;; 470 | esac 471 | done 472 | 473 | 474 | case "$OPERATION" in 475 | get_oidc_server_infos) 476 | if [ -z "$OPENID_ENDPOINT" ]; then 477 | echo "Error: --openid-endpoint is missing" >&2 478 | exit 1 479 | fi 480 | get_oidc_server_infos 481 | ;; 482 | 483 | client_credentials) 484 | if [ -z "$OPENID_ENDPOINT" ] && [ -z "$TOKEN_ENDPOINT" ]; then 485 | echo "Error: --token-endpoint is missing, you can also use --openid-endpoint" >&2 486 | exit 1 487 | fi 488 | if [ -z "$CLIENT_ID" ] && [ -z "$CLIENT_SECRET" ]; then 489 | echo "Error: --client-id or --client-secret is missing" >&2 490 | exit 1 491 | fi 492 | if [ -z "$TOKEN_ENDPOINT" ]; then 493 | TOKEN_ENDPOINT=$(curl -sS $OPENID_ENDPOINT | jq .token_endpoint -r) 494 | fi 495 | client_credentials 496 | ;; 497 | resource_owner_password_grant) 498 | if [ -z "$OPENID_ENDPOINT" ] && [ -z "$TOKEN_ENDPOINT" ]; then 499 | echo "Error: --token-endpoint is missing, you can also use --openid-endpoint" >&2 500 | exit 1 501 | fi 502 | if [ -z "$CLIENT_ID" ] && [ -z "$CLIENT_SECRET" ]; then 503 | echo "Error: --client-id or --client-secret is missing" >&2 504 | exit 1 505 | fi 506 | if [ -z "$USERNAME" ] && [ -z "$PASSWORD" ]; then 507 | echo "Error: --username or --password is missing" >&2 508 | exit 1 509 | fi 510 | if [ -z "$TOKEN_ENDPOINT" ]; then 511 | TOKEN_ENDPOINT=$(curl -sS $OPENID_ENDPOINT | jq .token_endpoint -r) 512 | fi 513 | resource_owner_password_grant 514 | ;; 515 | end_session) 516 | if [ -z "$OPENID_ENDPOINT" ] && [ -z "$END_SESSION_ENDPOINT" ]; then 517 | echo "Error: --end-session-endpoint is missing, you can also use --openid-endpoint" >&2 518 | exit 1 519 | fi 520 | if [ -z "$CLIENT_ID" ] && [ -z "$CLIENT_SECRET" ]; then 521 | echo "Error: --client-id or --client-secret is missing" >&2 522 | exit 1 523 | fi 524 | if [ -z "$ACCESS_TOKEN" ]; then 525 | echo "Error: --access-token is missing" >&2 526 | exit 1 527 | fi 528 | if [ -z "$REFRESH_TOKEN" ]; then 529 | echo "Error: --refresh-token is missing" >&2 530 | exit 1 531 | fi 532 | if [ -z "$END_SESSION_ENDPOINT" ]; then 533 | END_SESSION_ENDPOINT=$(curl -sS $OPENID_ENDPOINT | jq .end_session_endpoint -r) 534 | fi 535 | end_session 536 | ;; 537 | refresh_token) 538 | if [ -z "$OPENID_ENDPOINT" ] && [ -z "$TOKEN_ENDPOINT" ]; then 539 | echo "Error: --token-endpoint is missing, you can also use --openid-endpoint" >&2 540 | exit 1 541 | fi 542 | if [ -z "$CLIENT_ID" ] && [ -z "$CLIENT_SECRET" ]; then 543 | echo "Error: --client-id or --client-secret is missing" >&2 544 | exit 1 545 | fi 546 | if [ -z "$REFRESH_TOKEN" ]; then 547 | echo "Error: --refresh-token is missing" >&2 548 | exit 1 549 | fi 550 | if [ -z "$TOKEN_ENDPOINT" ]; then 551 | TOKEN_ENDPOINT=$(curl -sS $OPENID_ENDPOINT | jq .token_endpoint -r) 552 | fi 553 | refresh_token 554 | ;; 555 | token_exchange) 556 | if [ -z "$OPENID_ENDPOINT" ] && [ -z "$TOKEN_ENDPOINT" ]; then 557 | echo "Error: --token-endpoint is missing, you can also use --openid-endpoint" >&2 558 | exit 1 559 | fi 560 | if [ -z "$CLIENT_ID" ] && [ -z "$CLIENT_SECRET" ]; then 561 | echo "Error: --client-id or --client-secret is missing" >&2 562 | exit 1 563 | fi 564 | if [ -z "$ACCESS_TOKEN" ]; then 565 | echo "Error: --access-token is missing" >&2 566 | exit 1 567 | fi 568 | if [ -z "$ISSUER" ]; then 569 | echo "Error: --issuer is missing" >&2 570 | exit 1 571 | fi 572 | if [ -z "$TOKEN_ENDPOINT" ]; then 573 | TOKEN_ENDPOINT=$(curl -sS $OPENID_ENDPOINT | jq .token_endpoint -r) 574 | fi 575 | token_exchange 576 | ;; 577 | implicit_grant) 578 | if [ -z "$OPENID_ENDPOINT" ] && [ -z "$AUTHORIZATION_ENDPOINT" ]; then 579 | echo "Error: --authorization-endpoint is missing, you can also use --openid-endpoint" >&2 580 | exit 1 581 | fi 582 | if [ -z "$CLIENT_ID" ]; then 583 | echo "Error: --client-id is missing" >&2 584 | exit 1 585 | fi 586 | if [ -z "$REDIRECT_URI" ]; then 587 | echo "Error: --redirect-uri is missing" >&2 588 | exit 1 589 | fi 590 | if [ -z "$AUTHORIZATION_ENDPOINT" ]; then 591 | AUTHORIZATION_ENDPOINT=$(curl -sS $OPENID_ENDPOINT | jq .authorization_endpoint -r) 592 | fi 593 | implicit_grant 594 | ;; 595 | authorization_code_grant) 596 | if [ -z "$OPENID_ENDPOINT" ] && [ -z "$AUTHORIZATION_ENDPOINT" ]; then 597 | echo "Error: --authorization-endpoint is missing, you can also use --openid-endpoint" >&2 598 | exit 1 599 | fi 600 | if [ -z "$CLIENT_ID" ]; then 601 | echo "Error: --client-id is missing" >&2 602 | exit 1 603 | fi 604 | if [ -z "$REDIRECT_URI" ]; then 605 | echo "Error: --redirect-uri is missing" >&2 606 | exit 1 607 | fi 608 | if [ -z "$AUTHORIZATION_ENDPOINT" ]; then 609 | AUTHORIZATION_ENDPOINT=$(curl -sS $OPENID_ENDPOINT | jq .authorization_endpoint -r) 610 | fi 611 | if [ -z "$TOKEN_ENDPOINT" ]; then 612 | TOKEN_ENDPOINT=$(curl -sS $OPENID_ENDPOINT | jq .token_endpoint -r) 613 | fi 614 | authorization_code_grant 615 | ;; 616 | auth_code) 617 | if [ -z "$OPENID_ENDPOINT" ] && [ -z "$TOKEN_ENDPOINT" ]; then 618 | echo "Error: --token-endpoint is missing, you can also use --openid-endpoint" >&2 619 | exit 1 620 | fi 621 | if [ -z "$CLIENT_ID" ]; then 622 | echo "Error: --client-id is missing" >&2 623 | exit 1 624 | fi 625 | if [ -z "$REDIRECT_URI" ]; then 626 | echo "Error: --redirect-uri is missing" >&2 627 | exit 1 628 | fi 629 | if [ -z "$AUTHORIZATION_CODE" ]; then 630 | echo "Error: --authorization-code is missing" >&2 631 | exit 1 632 | fi 633 | if [ -z "$TOKEN_ENDPOINT" ]; then 634 | TOKEN_ENDPOINT=$(curl -sS $OPENID_ENDPOINT | jq .token_endpoint -r) 635 | fi 636 | auth_code 637 | ;; 638 | token_introspect) 639 | if [ -z "$OPENID_ENDPOINT" ] && [ -z "$TOKEN_INTROSPECTION_ENDPOINT" ]; then 640 | echo "Error: --token-introspection-endpoint is missing, you can also use --openid-endpoint" >&2 641 | exit 1 642 | fi 643 | if [ -z "$CLIENT_ID" ] && [ -z "$CLIENT_SECRET" ]; then 644 | echo "Error: --client-id or --client-secret is missing" >&2 645 | exit 1 646 | fi 647 | if [ -z "$ACCESS_TOKEN" ]; then 648 | echo "Error: --access-token is missing" >&2 649 | exit 1 650 | fi 651 | if [ -z "$TOKEN_INTROSPECTION_ENDPOINT" ]; then 652 | TOKEN_INTROSPECTION_ENDPOINT=$(curl -sS $OPENID_ENDPOINT | jq .introspection_endpoint -r) 653 | fi 654 | token_introspect 655 | ;; 656 | 657 | user_info) 658 | if [ -z "$OPENID_ENDPOINT" ] && [ -z "$USERINFO_ENDPOINT" ]; then 659 | echo "Error: --userinfo-endpoint is missing, you can also use --openid-endpoint" >&2 660 | exit 1 661 | fi 662 | if [ -z "$ACCESS_TOKEN" ]; then 663 | echo "Error: --access-token is missing" >&2 664 | exit 1 665 | fi 666 | if [ -z "$USERINFO_ENDPOINT" ]; then 667 | USERINFO_ENDPOINT=$(curl -sS $OPENID_ENDPOINT | jq .userinfo_endpoint -r) 668 | fi 669 | user_info 670 | ;; 671 | device_code) 672 | if [ -z "$OPENID_ENDPOINT" ] && [ -z "$DEVICE_ENDPOINT" ]; then 673 | echo "Error: --device-endpoint is missing, you can also use --openid-endpoint" >&2 674 | exit 1 675 | fi 676 | if [ -z "$CLIENT_ID" ]; then 677 | echo "Error: --client-id is missing" >&2 678 | exit 1 679 | fi 680 | if [ -z "$CLIENT_SECRET" ]; then 681 | echo "Error: --client-secret is missing" >&2 682 | exit 1 683 | fi 684 | if [ -z "$DEVICE_ENDPOINT" ]; then 685 | DEVICE_ENDPOINT=$(curl -sS $OPENID_ENDPOINT | jq .device_authorization_endpoint -r) 686 | fi 687 | device_code 688 | ;; 689 | poll_token) 690 | if [ -z "$OPENID_ENDPOINT" ] && [ -z "$TOKEN_ENDPOINT" ]; then 691 | echo "Error: --token-endpoint is missing, you can also use --openid-endpoint" >&2 692 | exit 1 693 | fi 694 | if [ -z "$CLIENT_ID" ]; then 695 | echo "Error: --client-id is missing" >&2 696 | exit 1 697 | fi 698 | if [ -z "$CLIENT_SECRET" ]; then 699 | echo "Error: --client-secret is missing" >&2 700 | exit 1 701 | fi 702 | if [ -z "$DEVICE_CODE" ]; then 703 | echo "Error: --device-code is missing" >&2 704 | exit 1 705 | fi 706 | if [ -z "$TOKEN_ENDPOINT" ]; then 707 | TOKEN_ENDPOINT=$(curl -sS $OPENID_ENDPOINT | jq .token_endpoint -r) 708 | fi 709 | poll_token 710 | ;; 711 | *) 712 | echo "unsupported operation" 713 | exit 1 714 | ;; 715 | 716 | esac 717 | --------------------------------------------------------------------------------