I’ve recently begun looking into uses for the Jamf Pro API, the API which Jamf makes available for Jamf Pro in addition to the Classic API. The two APIs handle authentication differently and for folks coming over to using the Jamf Pro API from the Classic API, the extra steps involved may be a surprise.
For the Classic API, here’s what’s required for authentication:
- One step process.
- Only username and password needed to authenticate an API call.
- Username and password can be in plaintext.
- No tokens are used.
Example Classic API call process:
This file contains 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
# Provide username and password as part of the API call: | |
/usr/bin/curl -su username_here:password_here" -H "Accept: application/xml" https://server.name.here/JSSResource/packages/id/2 |
For the Jamf Pro API, here’s what’s required for authentication:
- Multi-step process involving multiple API calls.
- Username and password only used to get authentication token, known as a Bearer Token. The Bearer Token is subsequently used for authentication.
- Username and password must be base64-encoded.
- Bearer Tokens are valid for 30 minutes maximum.
- Introduces need to validate authentication Bearer Tokens before making an API call.
Example Jamf Pro API call process:
This file contains 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
# Get username and password encoded in base64 format and stored as a variable in a script: | |
encodedCredentials=$(printf username_here:password_here | /usr/bin/iconv -t ISO-8859-1 | /usr/bin/base64 -i -) | |
# Use encoded username and password to request a token with an API call and store the output as a variable in a script: | |
authToken=$(/usr/bin/curl https://server.name.here/api/v1/auth/token –silent –request POST –header "Authorization: Basic ${encodedCredentials}”) | |
# Read the output, extract the token information and store the token information as a variable in a script: | |
api_token=$(/usr/bin/plutil -extract token raw -o – – <<< “$authToken”) | |
# Verify that the token is valid and unexpired by making a separate API call, checking the HTTP status code and storing status code information as a variable in a script: | |
api_authentication_check=$(/usr/bin/curl –write-out %{http_code} –silent –output /dev/null https://server.name.here/api/v1/auth/keep-alive –request POST –header "Authorization: Bearer ${api_token}") | |
#Assuming token is verified to be valid, use the token information to make an API call: | |
/usr/bin/curl –silent -H "Accept: application/xml" –header "Authorization: Bearer ${api_token}" https://server.name.here/JSSResource/packages/id/2 |
The differences in authentication are significant enough that I decided to write functions for shell scripting to handle the following tasks for the Jamf Pro API:
- Obtaining Bearer Token.
- Verifying that current Bearer Token is valid and unexpired.
- Renewing Bearer Tokens by using current Bearer Token to authenticate the issuing of a new Bearer Token. (This renewal process creates a new Bearer Token and invalidates the old Bearer Token.)
- Invalidating current Bearer Token.
For more details, please see below the jump.
I’ve written a script which includes the four functions listed below:
- GetJamfProAPIToken: Obtains the Bearer Token using the username and password of a Jamf Pro account.
- APITokenValidCheck: Runs an API call using the current Bearer Token which displays the authorization details associated with the current API user. The API call will only return the HTTP status code.
- CheckAndRenewAPIToken: Uses APITokenValidCheck to verify if the current Bearer Token is valid by checking the HTTP status code. If it is, the current Bearer Token will be used to get a new Bearer Token. If not, GetJamfProAPIToken is run to get a new Bearer Token using the username and password of a Jamf Pro account.
- InvalidateToken: Uses APITokenValidCheck to verify if the current Bearer Token is valid by checking the HTTP status code. If it is, the current Bearer Token will be used to invalidate itself.
The script is available below:
This file contains 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 | |
# This script uses the Jamf Pro API to get an authentication token | |
# Set default exit code | |
exitCode=0 | |
# Explicitly set initial value for the api_token variable to null: | |
api_token="" | |
# Explicitly set initial value for the token_expiration variable to null: | |
token_expiration="" | |
# If you choose to hardcode API information into the script, set one or more of the following values: | |
# | |
# The username for an account on the Jamf Pro server with sufficient API privileges | |
# The password for the account | |
# The Jamf Pro URL | |
# Set the Jamf Pro URL here if you want it hardcoded. | |
jamfpro_url="" | |
# Set the username here if you want it hardcoded. | |
jamfpro_user="" | |
# Set the password here if you want it hardcoded. | |
jamfpro_password="" | |
# Read the appropriate values from ~/Library/Preferences/com.github.jamfpro-info.plist | |
# if the file is available. To create the file, run the following commands: | |
# | |
# defaults write $HOME/Library/Preferences/com.github.jamfpro-info jamfpro_url https://jamf.pro.server.here | |
# defaults write $HOME/Library/Preferences/com.github.jamfpro-info jamfpro_user API_account_username_goes_here | |
# defaults write $HOME/Library/Preferences/com.github.jamfpro-info jamfpro_password API_account_password_goes_here | |
# | |
if [[ -f "$HOME/Library/Preferences/com.github.jamfpro-info.plist" ]]; then | |
if [[ -z "$jamfpro_url" ]]; then | |
jamfpro_url=$(defaults read $HOME/Library/Preferences/com.github.jamfpro-info jamfpro_url) | |
fi | |
if [[ -z "$jamfpro_user" ]]; then | |
jamfpro_user=$(defaults read $HOME/Library/Preferences/com.github.jamfpro-info jamfpro_user) | |
fi | |
if [[ -z "$jamfpro_password" ]]; then | |
jamfpro_password=$(defaults read $HOME/Library/Preferences/com.github.jamfpro-info jamfpro_password) | |
fi | |
fi | |
# If the Jamf Pro URL, the account username or the account password aren't available | |
# otherwise, you will be prompted to enter the requested URL or account credentials. | |
if [[ -z "$jamfpro_url" ]]; then | |
read -p "Please enter your Jamf Pro server URL : " jamfpro_url | |
fi | |
if [[ -z "$jamfpro_user" ]]; then | |
read -p "Please enter your Jamf Pro user account : " jamfpro_user | |
fi | |
if [[ -z "$jamfpro_password" ]]; then | |
read -p "Please enter the password for the $jamfpro_user account: " -s jamfpro_password | |
fi | |
echo | |
# Remove the trailing slash from the Jamf Pro URL if needed. | |
jamfpro_url=${jamfpro_url%%/} | |
GetJamfProAPIToken() { | |
# This function uses Basic Authentication to get a new bearer token for API authentication. | |
# Create base64-encoded credentials from user account's username and password. | |
encodedCredentials=$(printf "${jamfpro_user}:${jamfpro_password}" | /usr/bin/iconv -t ISO-8859-1 | /usr/bin/base64 -i –) | |
# Use the encoded credentials with Basic Authorization to request a bearer token | |
authToken=$(/usr/bin/curl "$jamfpro_url/api/v1/auth/token" –silent –request POST –header "Authorization: Basic ${encodedCredentials}") | |
# Parse the returned output for the bearer token and store the bearer token as a variable. | |
if [[ $(/usr/bin/sw_vers -productVersion | awk -F . '{print $1}') -lt 12 ]]; then | |
api_token=$(/usr/bin/awk -F \" 'NR==2{print $4}' <<< "$authToken" | /usr/bin/xargs) | |
else | |
api_token=$(/usr/bin/plutil -extract token raw -o – – <<< "$authToken") | |
fi | |
} | |
APITokenValidCheck() { | |
# Verify that API authentication is using a valid token by running an API command | |
# which displays the authorization details associated with the current API user. | |
# The API call will only return the HTTP status code. | |
api_authentication_check=$(/usr/bin/curl –write-out %{http_code} –silent –output /dev/null "$jamfpro_url/api/v1/auth/keep-alive" –request POST –header "Authorization: Bearer ${api_token}") | |
} | |
CheckAndRenewAPIToken() { | |
# Verify that API authentication is using a valid token by running an API command | |
# which displays the authorization details associated with the current API user. | |
# The API call will only return the HTTP status code. | |
APITokenValidCheck | |
# If the api_authentication_check has a value of 200, that means that the current | |
# bearer token is valid and can be used to authenticate an API call. | |
if [[ ${api_authentication_check} == 200 ]]; then | |
# If the current bearer token is valid, it is used to connect to the keep-alive endpoint. This will | |
# trigger the issuing of a new bearer token and the invalidation of the previous one. | |
# | |
# The output is parsed for the bearer token and the bearer token is stored as a variable. | |
authToken=$(/usr/bin/curl "$jamfpro_url/api/v1/auth/keep-alive" –silent –request POST –header "Authorization: Bearer ${api_token}") | |
if [[ $(/usr/bin/sw_vers -productVersion | awk -F . '{print $1}') -lt 12 ]]; then | |
api_token=$(/usr/bin/awk -F \" 'NR==2{print $4}' <<< "$authToken" | /usr/bin/xargs) | |
else | |
api_token=$(/usr/bin/plutil -extract token raw -o – – <<< "$authToken") | |
fi | |
else | |
# If the current bearer token is not valid, this will trigger the issuing of a new bearer token | |
# using Basic Authentication. | |
GetJamfProAPIToken | |
fi | |
} | |
InvalidateToken() { | |
# Verify that API authentication is using a valid token by running an API command | |
# which displays the authorization details associated with the current API user. | |
# The API call will only return the HTTP status code. | |
APITokenValidCheck | |
# If the api_authentication_check has a value of 200, that means that the current | |
# bearer token is valid and can be used to authenticate an API call. | |
if [[ ${api_authentication_check} == 200 ]]; then | |
# If the current bearer token is valid, an API call is sent to invalidate the token. | |
authToken=$(/usr/bin/curl "$jamfpro_url/api/v1/auth/invalidate-token" –silent –header "Authorization: Bearer ${api_token}" -X POST) | |
# Explicitly set value for the api_token variable to null. | |
api_token="" | |
fi | |
} | |
GetJamfProAPIToken | |
APITokenValidCheck | |
echo "$api_authentication_check" | |
echo "$api_token" | |
CheckAndRenewAPIToken | |
APITokenValidCheck | |
echo "$api_authentication_check" | |
echo "$api_token" | |
InvalidateToken | |
APITokenValidCheck | |
echo "$api_authentication_check" | |
echo "$api_token" |