Let's build a bash script that is able to obtain a refresh OAuth token for a 3LO Atlassian app to be able to make offline API requests.
The basic idea
The we create a 3LO application we get a client_id
and a client_secret
. We need to obtain a refresh token, so we can interact with Atlassian. To do so, we need to ask the user to click on a URL, confirm our access and wait for Atlassian to give us the token. We need to configure the app to send the auth code to http://172.0.0.1:8014/token
.
sequenceDiagram
autonumber
participant P as Alice
participant S as Script
participant A as Atlassian
participant API as API
P->>S:./refresh.sh
S->>S:start netcat on port 8014
S->>P:display URL for<br/>token request
P->>A:open URL in browser
P->>A:confirm token
A->>P:redirect http://127.0.0.1:8014/token
S->>S:detects token on port 8014
S->>API:authorization_code
S->>S:save result to token file
As you can see, we'll listen with netcat on port 8014 and save the result. Now that we've saved the details to a token file, we can use the file to refresh the token for any follow up requests.
sequenceDiagram
autonumber
participant P as Alice
participant S as Script
participant A as Atlassian
participant API as API
P->>S:./refresh.sh
S->>S:read refresh token from file
S->>API:refresh_token
API->>S:token
S->>S:save result to token file
We'll store some files next to the script:
.env
a file with the environment variables containing the app secrets and script settings.token.json
a file that contains the refresh token details.
Prerequisites
We need to make sure we install some tools the script uses. We'll use jq
for handling JSON, uuid-runtime
for GUID generation and netcat
to capture the OAuth code from Jira.
apt-get update
apt-get install -y uuid-runtime jq netcat
Environment file
First, we need to store the settings of our scripts somewhere. As it is bad practice to add keys to the script itself, we'll create a .env
file. You might consider using environment settings in a production-like environment.
JIRA_CLIENT_ID=""
JIRA_CLIENT_SECRET=""
JIRA_DOMAIN="https://my-url.atlassian.net"
JIRA_SCOPE="offline_access read:build:jira-software write:build:jira-software read:build-info:jira write:build-info:jira read:issue:jira read:issue:jira-software read:epic:jira-software read:issue-details:jira read:field.default-value:jira read:field.option:jira read:field:jira read:group:jira read:jira-work"
REFRESH_PORT=8014
REFRESH_PATH="/token"
TOKEN_FILE_NAME="token.json"
Note the JIRA_SCOPE
, this defines what we want to do with the token. In order to get a refresh token, we'll need to request offline_access
.
The script
Here's the final script:
#!/bin/bash
set -e
# colors
BLUE='\033[1;34m'
NC='\033[0m'
# Load env file
set -a
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source "$SCRIPT_DIR/.env"
set +a
# Function to print header
print_header() {
printf "
R3FR3SH ${BLUE}T0K3N${NC} G3N3R@T0R 4
__ ${BLUE}__ ${NC}
|__${BLUE}|__|${NC}___________
| | \_ __ \__ \
| | || | \// __ \_
/\__| |__||__| (____ ${BLUE}/${NC}
\______| ${BLUE}\/${NC}
\n"
}
# Function for POST requests
post_request() {
local url=$1
local data=$2
curl --request POST "$url" \
--header "Content-Type: application/json" \
--data "$data" \
--silent
}
# Function to refresh token
refresh_token() {
local refresh_token=$(jq '.refresh_token' --raw-output < "$TOKEN_FILE_PATH")
local data='{
"grant_type": "refresh_token",
"client_id": "'$JIRA_CLIENT_ID'",
"client_secret": "'$JIRA_CLIENT_SECRET'",
"refresh_token": "'$refresh_token'"
}'
printf "Refreshing token..."
local response=$(post_request "https://auth.atlassian.com/oauth/token" "$data")
echo "$response" | jq > "$TOKEN_FILE_PATH"
printf " ${BLUE}OK${NC}\n"
printf "Your refresh token is saved to ${BLUE}$TOKEN_FILE_NAME${NC}.\n\n"
}
# Function to generate new token
generate_token() {
local uuid=$(uuidgen)
local scope=$(echo "$JIRA_SCOPE" | sed 's/ /%20/g' | sed 's/:/%3A/g')
local jira_url="https://auth.atlassian.com/authorize?audience=api.atlassian.com&client_id=$JIRA_CLIENT_ID&scope=$scope&state=$uuid&response_type=code&prompt=consent"
printf "Click link to authorize app:${BLUE}\n"
printf "%s\n\n" "$jira_url"
printf "${NC}Waiting for confirmation..."
local code=""
local msg="OK, you can close this website."
while true; do
local request=$(echo -e "HTTP/1.1 200 OK\r\nContent-Length: $(echo -n "$msg" | wc -c)\r\n\r\n$msg" | nc -N -l -p "$REFRESH_PORT")
code=$(echo "$request" | grep "$uuid" | grep -oP 'code=\K[^& ]*')
if [[ -n "$code" ]]; then
break
fi
done
printf " ${BLUE}OK${NC}\n"
printf "Getting refresh token..."
local data='{
"grant_type": "authorization_code",
"client_id": "'$JIRA_CLIENT_ID'",
"client_secret": "'$JIRA_CLIENT_SECRET'",
"code": "'$code'"
}'
local response=$(post_request "https://auth.atlassian.com/oauth/token" "$data")
echo "$response" | jq > "$TOKEN_FILE_PATH"
printf " ${BLUE}OK${NC}\n"
printf "Your refresh token is saved to ${BLUE}$TOKEN_FILE_NAME${NC}.\n\n"
}
print_header
TOKEN_FILE_PATH="$SCRIPT_DIR/$TOKEN_FILE_NAME"
if [ -f "$TOKEN_FILE_PATH" ]; then
refresh_token
else
generate_token
fi