Understanding Jamf Pro API Roles and Clients

Jamf Pro 10.49.0 introduced a more secure method of accessing the API: Client Credentials-based authentication. Here's how it works.

July 23 2024 by

Graham Pugh

How to . . .

Introduction to the Jamf Pro APIs

Jamf Pro has two APIs, the original API now known as the Classic API, and the newer Jamf Pro API that was introduced in 2016 with Casper Suite 9.93 and became a production API with Jamf Pro 10.26.0.

The Classic API used Basic Authentication for all endpoints. Basic Authentication supplies the username and password of a user account directly with every request. From a security perspective, sending credentials repeatedly over the internet could be considered a credible risk.

The Jamf Pro API supports Basic Authentication, but only for a single endpoint: /api/v1/auth/token. This endpoint is used to obtain a "Bearer Token:" a unique, time-limited access token used to make requests to all other endpoints. It can also be revoked after use. This reduces the exposure of the original credentials, since they need only be used once per task.

This method of obtaining a bearer token was also added to the Classic API with Jamf Pro 10.35.0, released April 2022. Since then, it has been possible to disable Basic Authentication for the Classic API (apart from the /api/v1/auth/token endpoint used to obtain the Bearer Token) by navigating to Settings > Jamf Pro User Accounts & Groups > Password Policy and deselecting the Allow Basic Authentication in addition to Bearer Token authentication checkbox.

New Jamf Pro instances created with version 10.42.0 or newer had Basic Authentication disabled by default, though it could be enabled using the checkbox above.

When a Jamf Pro instance is upgraded to Jamf Pro 11.5.0 or greater from a version older than this, the Basic Authentication checkbox is forcibly disabled. For the time being it is still possible to re-enable it, but Basic Authentication of Classic API endpoints is no longer supported, so any tools and scripts should be converted to use Bearer Token Authentication as soon as possible.

For more information, see the Jamf Developer Article Classic API Authentication Changes, William Smith's blog post How to convert Classic API scripts to use bearer token authentication, and my personal blog post Changes to Classic API authentication in Jamf Pro - what you need to know.

Client credentials-based authentication

In addition to the method of obtaining a bearer token using Basic Authentication described above, a new, more secure way of obtaining an access token was introduced with the release of Jamf Pro 10.49.0, namely Client Credentials-based authentication. Unlike the credentials required to obtain a bearer token using Basic Authentication, API Clients provide a dedicated interface for controlling access to the Jamf Pro API and the Classic API, and, importantly, have no access to the Jamf Pro user interface.

*

Note that it is not currently possible to disable API access to user accounts via Bearer Token Authentication.

*

What is an API role?

API Roles are a custom collection of privileges, defining which API endpoints an API Client can access.

You define API roles within Jamf Pro, by navigating to Settings > System > API Roles and Clients and clicking on the API Roles tab.

*

Note that these privilege sets are completely unrelated to any user or group privileges set in Settings > System > User accounts and groups.

*

What is an API Client?

An API Client is a kind of account to which API Roles can be assigned. One or more API Roles can be assigned to an API Client, granting their cumulative privileges. At least one API Role must have been created before an API Client can be created, as it is not possible to assign no API Roles to an API Client.

For detailed instructions on how to set up and use API Roles and Clients, see the excellent Jamf Pro documentation API Roles and Clients. I just want to point out below the basic differences between the use of API Clients and a normal account username/password combination.

Using an API Client to generate an access token

Once an API client has been created, a Client Secret can be generated. The API Client and Secret are used together to obtain an access token using the /api/oauth/token endpoint. In the following example, we are outputting the JSON to a file so that it can be used across multiple commands and scripts. The fictitious client_id is visible in the Jamf Pro console, and the client_secret is the one generated, which is shown only at the point of generation so needs to be stored in a password manager of your choice.

*

Note: you may see short or long parameter names in curl statements. I've used the long names here, so here's a short glossary of equivalents that are relevant to this post:

  • -X is the same as --request (POST, GET, PUT or DELETE)
  • -H is the same as --header
  • -u is the same as --user
  • -d is the same as --data (but for URL-encoding, --data-urlencode should be specified)
  • -o is the same as --output (i.e. write the output to a file)
  • --url is optional; a URL can be supplied with no parameter name

*

The access token file generated by this method is different from that generated when requesting a Bearer Token using Basic Authentication, in that the key names are different. The token key is named access_token, but is used in exactly the same way as the token obtained using Basic Authentication. And rather than storing the expiry as a timestamp, we get a value in seconds for how long it will be until it expires. This is the expires_in key.

Let's look at how we extract the token, and how to calculate if the token is still valid.

Since we exported the token to a file, we should invalidate the token after use to minimize the risk of unwanted capture and malicious re-use. This is done using the token itself:

*

Note that unlike with a Bearer Token obtained using Basic Authentication, it is not possible to keep an access token that was generated using /api/oauth/token alive using the existing token, so we always need to use the Client ID and Client Secret to create a new token once the old one has expired.

*

Best practices for API Role and API Client use

As pointed out by Bryson Tyrell in his article Notes on Jamf Pro API Roles and Clients, if we add or remove an API Role from an API Client, this is defined as a "scope change" which requires the generation of a new Client Secret for the changes to take effect. On the other hand, any changes made to the privileges set in an existing API Roles take immediate effect in terms of what the API Client can access. It is not necessary to generate a new client secret for these changes to take effect.

For this reason, it is recommended to create a specific API Role for each API Client, rather than trying to manage cumulative API Roles that are reused with different API Clients.

Bonus section: using the Jamf Pro API to manage API Roles and Clients

The Jamf Pro documentation describes how to create API Roles and API Clients in the Jamf Pro Console. However, it is possible to manage API Roles and Clients completely from the Jamf Pro API. It may seem counterintuitive to use a Jamf Pro user to manage API Roles and Clients, but for those of us interested in automating the setup of Jamf Pro instances, this is a valuable resource for avoiding manual work within the admin console of new instances.

Let's take a look at how to use the API to manage API Roles and Clients. To do this, we have to obtain a Bearer Token in the traditional way using the credentials of a user account. In this example, we will assume an admin account with the username jamfsw and the password jamf1234.

We will use the token stored in /tmp/bearer-token.txt for the following API requests, using the following command before each use:

List existing API Roles.

In order to manage API roles, we need to obtain a list of any existing API Roles. The following command will output a list of current API roles in JSON format, sorted by ID, to /tmp/curl-output.txt.

The output shows the IDs, display names and sets of privileges associated with each Role:

To obtain details of a specific role, add the ID of that Role to the URL, e.g. https://yourserver.jamfcloud.com/api/v1/api-roles/1. No additional information is stored in the individual records in comparison to the complete list.

Get a list of all possible privileges that can be assigned to an API Role.

To get a complete set of possible API Role privileges that we could add to a Role, use the /api/v1/api-role-privileges endpoint, as in the following request.

The output lists all roles within a privileges list.

Alternatively, we can search for privileges using keyword search. Here we search for all privileges associated with static computer groups:

The output lists all roles within a privileges list.

Create a new API Role.

The following command will create a new role called "Amend Static Groups" which has create, read, and update privileges for Static Computer Groups. Note that the data key contains the privileges key in exactly the form it was obtained from the search above.

The response reflects the request and returns also the ID of the new API Role:

Amend or delete an existing API Role.

To amend a specific API Role, add the ID of that Role to the URL, e.g. https://yourserver.jamfcloud.com/api/v1/api-roles/2, and use a PUT request. The contents of the data are exactly as with creating a new API Role (do not include the ID) and the response is also the same.

To delete a specific API Role, add the ID of that Role to the URL, and use a DELETE request, as below.

List existing API Clients.

If we want to create API clients using the API, we need to know the existing clients. API clients are managed via the api-integrations endpoint. The following command will output a list of current API roles in JSON format, sorted by ID, to /tmp/curl-output.txt.

The output shows the IDs, display names and set of privileges associated with each Role:

This gives us an idea of the possible settings we can set and amend in an API Client, namely that we can set it to enabled or disabled, and we can specify the lifetime of an access token in seconds.

To obtain details of a specific role, add the ID of that Role to the URL, e.g. https://yourserver.jamfcloud.com/api/v1/api-roles/1. No additional information is stored in the individual records in comparison to the complete list.

You can also filter the list to a specific API Client name by adding a filter to the URL. For example, to filter to the API Client named "Static Group Amendment Client," use the following URL: https://yourserver.jamfcloud.com/api/v1/api-roles/?page=0&page-size=100&sort=id%3Aasc&filter=displayName%3D%3D%22Static%20Group%20Amendment%20Client%22 (note that we have to escape any quotation marks (") with %22 and any spaces with %20).

Create a new API Client.

The following command will create a new API Client called "Static Group Amendment Client" which is assigned the API Role Amend Static Groups. Note that the data key contains the authorizationScopes key in exactly the form it was obtained from the search above.

The response reflects the request and returns also the ID of the new API Client, as well as the clientId which is required for generating the access token:

Amend or delete an existing API Client.

To amend a specific API Client, add the ID of that Client to the URL, e.g. https://yourserver.jamfcloud.com/api/v1/api-integrations/1, and use a PUT request. The contents of the data are exactly as with creating a new API Client (do not include the ID) and the response is also the same.

To delete a specific API Role, add the ID of that Role to the URL, and use a DELETE request, as below.

Create client credentials for an API Client.

As explained earlier, to use an API client to obtain an access token, we need the Client ID and Client Secret. To generate a Client Secret using the API, use the following request:

The response contains the clientID and clientSecret keys.

Remember that there is no way to retrieve an existing Client Secret, neither via the GUI nor the API. So our workflow should store the Client Secret for future use.

Conclusion

For security reasons, moving forward it is recommended to set up API Roles and Clients instead of using actual account credentials. This is especially pertinent when setting up accounts for use by third-party integrations, external teams, and so on. There are currently no plans to deprecate the use of account credentials to obtain Bearer Token, but using API Roles and Clients is more secure.

For Jamf Pro admins who currently automate the setting up of users and groups in new instances using the Jamf Pro API, I have demonstrated how we can instead manage the setting up of API Roles and Clients. This could be done as part of a setup workflow at the end of which the management account with the rights to create API Roles and Clients is disabled or removed, drastically reducing the exposure surface of the Jamf Pro console.

Further reading

Here's a summary of the links used in this article:

Discover how Jamf Pro helps you securely and effectively manage your Apple devices at scale.