jwt "github.com/golang-jwt/jwt/v4" // go get github.com/golang-jwt/jwt/[email protected] // Used to exchange a JWT for an access token
tokenEndpoint = "https://identity.disruptive-technologies.com/oauth2/token"
// The base URL for the Disruptive REST API
apiBaseUrl = "https://api.disruptive-technologies.com/v2"
type AuthResponse struct {
// The access token used to access the Disruptive REST API
AccessToken string `json:"access_token"`
// The type of token this is. Will typically be "Bearer"
TokenType string `json:"token_type"`
// How many seconds until the token expires. Will typically be 3600
ExpiresIn int `json:"expires_in"`
func getAccessToken(keyID string, secret string, email string) (*AuthResponse, error) {
// Construct the JWT header
jwtHeader := map[string]interface{}{
// Construct the JWT payload
jwtPayload := &jwt.RegisteredClaims{
Audience: jwt.ClaimStrings{tokenEndpoint},
IssuedAt: jwt.NewNumericDate(now),
ExpiresAt: jwt.NewNumericDate(now.Add(time.Hour)),
// Sign and encode JWT with the secret
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwtPayload)
encodedJwt, _ := token.SignedString([]byte(secret))
// Prepare HTTP POST request data.
// NOTE: The body must be Form URL-Encoded
"assertion": {encodedJwt},
"grant_type": {"urn:ietf:params:oauth:grant-type:jwt-bearer"},
// Create the request to exchange the JWT for an access token
req, err := http.NewRequest(
strings.NewReader(reqData),
// Set Content-Type header to specify that our body
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
// Exchange the JWT for an access token. Set a 3 second
// timeout in case the server can't be reached.
client := &http.Client{Timeout: time.Second * 3}
res, err := client.Do(req)
// Decode the response body to an AuthResponse
var authResponse AuthResponse
if err := json.NewDecoder(res.Body).Decode(&authResponse); err != nil {
// Return the AuthResponse, which contains the access token
return &authResponse, nil
func listProjects(auth *AuthResponse) error {
// Create the request to get a list of projects from the
req, err := http.NewRequest("GET", apiBaseUrl+"/projects", nil)
// Set the Authorization header by specifying both the token type
// (which will typically be "Bearer") as well as the access token
req.Header.Set("Authorization", fmt.Sprintf("%s %s", auth.TokenType, auth.AccessToken))
// Create an http Client with a timeout
// Send the GET request to list all projects.
// Set a 3 second timeout in case the server can't be reached.
client := &http.Client{Timeout: time.Second * 3}
resp, err := client.Do(req)
// Define a struct with the format we expect the response
// to be in. See the REST API Reference for more details.
type ProjectsResponse struct {
Name string `json:"name"`
DisplayName string `json:"displayName"`
Inventory bool `json:"inventory"`
Organization string `json:"organization"`
OrganizationDisplayName string `json:"organizationDisplayName"`
SensorCount int `json:"sensorCount"`
CloudConnectorCount int `json:"cloudConnectorCount"`
NextPageToken string `json:"nextPageToken"`
// Decode the response into a ProjectsResponse
var projectsResponse ProjectsResponse
if err = json.NewDecoder(resp.Body).Decode(&projectsResponse); err != nil {
// Print the name of each project, and how many device each contain
for _, project := range projectsResponse.Projects {
fmt.Println(project.DisplayName)
fmt.Printf(" %d Sensors\n", project.SensorCount)
fmt.Printf(" %d Cloud Connectors\n", project.CloudConnectorCount)
// OAuth2 authentication flow
auth, err := getAccessToken(
os.Getenv("DT_SERVICE_ACCOUNT_KEY_ID"),
os.Getenv("DT_SERVICE_ACCOUNT_SECRET"),
os.Getenv("DT_SERVICE_ACCOUNT_EMAIL"),
// Test the access token by listing all the projects our
// Service Account has access to
if err := listProjects(auth); err != nil {