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. You can find your apps here.
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://localhost: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://localhost:8014/token
S->>S:detects token on port 8014
S->>API:authorization_code
S->>S:save result to token fileAs 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 fileWe'll store some files next to the script:
.enva file with the environment variables containing the app secrets and script settings.token.jsona 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 netcatEnvironment 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 offline_access read:jira-work read:jira-user write:jira-work"
REFRESH_PORT=8014
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
# settings
ENV_FILE="jira.env"
# colors
BLUE='\033[1;34m'
NC='\033[0m'
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)
pushd "$SCRIPT_DIR" &>/dev/null
# validate dependencies
for cmd in jq nc uuidgen; do
if ! type "$cmd" &>/dev/null; then
echo "Error: '$cmd' is not installed. Please install it to continue."
exit 1
fi
done
if [ ! -f "$ENV_FILE" ]; then
echo "Error: '$ENV_FILE' is missing, please create it."
exit 1
elif grep -q $'\r' "$ENV_FILE"; then
echo "Error: $ENV_FILE file contains Windows-style line endings (\r)."
exit 1
else
set -a
source "$ENV_FILE"
set +a
fi
# Function to print header
print_header() {
echo -e "
R3FR3SH ${BLUE}T0K3N${NC} G3N3R@T0R 4
__ ${BLUE}__ ${NC}
|__${BLUE}|__|${NC}___________
| | \\_ __ \\__ \\
| | || | \\// __ \\_
/\\__| |__||__| (____ ${BLUE}/${NC}
\\______| ${BLUE}\\/${NC}
"
}
# 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 data response
refresh_token=$(jq '.refresh_token' --raw-output <"$TOKEN_FILE_PATH")
data='{
"grant_type": "refresh_token",
"client_id": "'$JIRA_CLIENT_ID'",
"client_secret": "'$JIRA_CLIENT_SECRET'",
"refresh_token": "'$refresh_token'"
}'
printf "Refreshing token..."
response=$(post_request "https://auth.atlassian.com/oauth/token" "$data")
echo "$response" | jq >"$TOKEN_FILE_PATH"
echo -e " ${BLUE}OK${NC}"
echo -e "Your refresh token is saved to ${BLUE}${TOKEN_FILE_NAME}${NC}."
echo
}
# Function to generate new token
generate_token() {
local uuid redirect_url redirect_url_2 jira_scope jira_url request response code msg
uuid="$(uuidgen)"
redirect_url="http%3A%2F%2Flocalhost%3A$REFRESH_PORT"
redirect_url_2="http://localhost:$REFRESH_PORT"
jira_scope=$(echo "$JIRA_SCOPE" | sed 's/ /%20/g' | sed 's/:/%3A/g')
jira_url="https://auth.atlassian.com/authorize?audience=api.atlassian.com&client_id=$JIRA_CLIENT_ID&scope=$jira_scope&state=$uuid&response_type=code&prompt=consent&redirect_uri=$redirect_url"
echo -e -n "Click link to authorize app:$BLUE
$jira_url
$NC
Waiting for confirmation..."
code=""
msg="OK, you can close this website."
while true; do
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
echo -e " ${BLUE}OK${NC}"
echo -n "Getting refresh token..."
local data='{
"grant_type": "authorization_code",
"client_id": "'$JIRA_CLIENT_ID'",
"client_secret": "'$JIRA_CLIENT_SECRET'",
"redirect_uri": "'$redirect_url_2'",
"code": "'$code'"
}'
response=$(post_request "https://auth.atlassian.com/oauth/token" "$data")
echo "$response" | jq >"$TOKEN_FILE_PATH"
echo -e " ${BLUE}OK${NC}"
echo -e "Your refresh token is saved to ${BLUE}${TOKEN_FILE_NAME}${NC}."
echo
}
print_header
TOKEN_FILE_PATH="$SCRIPT_DIR/$TOKEN_FILE_NAME"
if [ -f "$TOKEN_FILE_PATH" ]; then
refresh_token
else
generate_token
fiChangelog
- Added some extra checks to the scripts to check if the dependencies are installed and if the
.envhas\rcharacters (thanks Windows!). We've swappedprintfout forecho. - Initial article.