Create JSON Web Tokens (JWT)

On this page:

Overview of JWT

JSON Web Tokens (JWT) are required to access our REST API methods either through code or through our front end widgets.

JWTs authenticate specific users of the API to allow user-level access and enable resource-specific permissions.

🚧

Mandatory Requirement

JWTs are not optional for any AutoQL API operation; any operation will fail without one.

How JWTs are Implemented

We follow Google Cloud Services's best practices for JWT implementation. Your organization will be provided with a Service Account File which will allow you to create JWTs that permit access to our API.

In conjunction with your AutoQL API key, these JWTs can be used to make authenticated requests to our API. We'll cover requirements for accessing and managing API keys in the next doc.

JWT Fields

Our JWTs contain the following fields:

{
    # Current time
    'iat': now,
     
    # Expires after 'expiry_length' seconds.
    "exp": now + expiry_length,
         
    # All three of these are your Google Cloud Service account email in full
    # You can find this email in the decrypted service account json file
    # It will be keyed as 'client_email'
    'iss': sa_email,
    'sub': sa_email,
    'email': sa_email,
 
    # Same as your domain. Example: companyName.chata.io
    'aud':  audience,
 
    # The project ID of the project that the user requesting access to an endpoint belongs to
    'project_id': project_id,
         
    # The user ID of the user who is requesting access to an endpoint
    'user_id': user_id,
          
    # The resource permissions for this Token
    # A URL list, which contains all of the resources accessible by this Token
    'resource_access': [
         # ** means permit all child resources under /autoql/management/customer
        '/autoql/management/customer/**',
        '/autoql/management/enum/**'
     ]
  
  	# Optional
    # The mame of the user to display in the query logs
    # If you don't provide display_name, user_id will be substituted
    'display_name': display_name,o
  
  	# Optional
    # If your implementation uses an ACL or other row-level ID for security, add them here
    # Examples could be a user ID, role ID, organization ID. We will have communicated how with you in advance how we implemented this to match your database. 
    'acess_control_id': ['your_identifier']
 }

All fields not otherwise marked are mandatory. For resource_access, URLs should be in ant style:

  • ? matches one character
  • * matches zero or more characters
  • ** matches zero or more directories in a path

Managing Json Web Tokens

🚧

JWTs and JWT creation must be managed on your own environment.

We recommend you deploy a web service secured with with your preferred implementation of access management. This service would enclose code that manages your JWTs, utilizing your Service Account File.

For maximum security, we strongly suggest that the Service Account File be kept in a cloud secret. We use a kubernetes secret but you're free to use whatever best suits your environment.

You can review our additional recommendations for security-related best practices here.

Sample code is provided in Python, Java, and C# below.

"""
An example script for generating an AutoQL JWT.

This example is using Python 3 and requires the google-auth package.
"""

import json
import time
import typing

from google.auth import crypt, jwt


def generate_jwt(service_account_filepath: str,
                 audience: str,
                 token_lifespan: int,
                 project_id: typing.Optional[str],
                 user_id: str,
                 display_name: str,
                 resource_access: list[str],
                 access_control_ids: typing.Optional[list[str]]) -> str:
    """Generates a signed JSON Web Token using a Service Account JSON file.

    Args:
        service_account_filepath: The path to your service account file.
            This file can be downloaded in the AutoQL Portal under
            Widgets -> Authentication.
        audience: The JWT audience. This will be equivalent to your custom
            API domain. This can be found in the API Keys section in the
            AutoQL Portal under Widgets -> Authentication. This would not
            include the URL scheme. Example: 'your-company-api.chata.io'
        token_lifespan: The lifespan of your JWT in seconds
        project_id: (optional) The ID of the project you want to access.
            Project ID's can be found in the Projects section of the AutoQL
            Portal. If the project_id is null, it will be replaced with an
            empty string. Most endpoints in the Management API do not require
            a project_id, however all endpoints in the Service API do require
            one.
        user_id: An ID that uniquely identifies the user who is using this JWT.
            User ID's are defined by the consumer of the AutoQL API.
            However you decide to uniquely identify your users is up to you.
        display_name: The display name of the user. Typically, this will be
            their first/last name, email, or nickname.
        resource_access: A list of ant-style URL's that this token will have
            access to in the AutoQL API.
            For example, at the most fundamental level, you can
            generate JWT's that only have access to the Management API, or
            Service API, or both. You can get as granular as you want.
        access_control_ids: (optional )If your database uses row-level control,
            you can specify one more more access control (ACL) ID's here.
            Note that in order to use ACL ID's, more setup is required in the
            AutoQL Portal.

    Returns:
        A signed AutoQL JWT (str)
    """

    with open(service_account_filepath) as sa_file:
        parsed_file = json.loads(sa_file.read())

    now = int(time.time())
    payload = {
        'iat': now,
        "exp": now + token_lifespan,
        'iss': parsed_file['client_email'],
        'aud': audience,
        'sub': parsed_file['client_email'],
        'email': parsed_file['client_email'],
        'project_id': project_id if project_id else '',
        'user_id': user_id,
        'display_name': display_name,
        'resource_access': resource_access,
        'access_control_id': access_control_ids if access_control_ids else []
    }

    signer = crypt.RSASigner.from_service_account_info(parsed_file)
    encoded_jwt = jwt.encode(signer, payload)
    return encoded_jwt.decode()


# ### Example ###

# This token will have access to all endpoints in both the Service and
# Management API's
access_urls = [
    '/autoql/api/v1/**',
    '/autoql/management/api/v1/**'
]

token = generate_jwt(
    'chata-secret.json',
    'your-company-api.chata.io',
    3600,
    'CT_abcdef',
    'user_123',
    'FirstName LastName',
    access_urls,
    None
)
package com.example.app;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.net.URL;
import java.security.interfaces.RSAPrivateKey;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * JWTClient shows how a client can authenticate with a Cloud Endpoints service
 */
public class GoogleJwtClient {

  /**
   * Generates a signed JSON Web Token using a Google API Service Account
   * utilizes com.auth0.jwt.
   */
  public static String generateJwt(final String saKeyfile, final String saEmail,
      final String audience, final int expiryLength, final String customerId, final String userId,
      final List<String> resource) throws FileNotFoundException, IOException {

    Date now = new Date();
    Date expTime = new Date(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(expiryLength));

    // Build the JWT payload
    JWTCreator.Builder token = JWT.create()
        .withIssuedAt(now)
        // Expires after 'expTime' seconds
        .withExpiresAt(expTime)
        // Must match 'issuer' in the security configuration in your
        // swagger spec (e.g. service account email)
        .withIssuer(saEmail)
        // Must be either your Endpoints service name, or match the value
        // specified as the 'x-google-audience' in the OpenAPI document
        .withAudience(audience)
        // Subject and email should match the service account's email
        .withSubject(saEmail)
        .withClaim("email", saEmail)
        // the customer's id, obtainable from the API
        .withClaim("customer_id", customerId)
        // the user's id, obtainable from the API
        .withClaim("user_id", userId)
        // API resources in Ant-style format
        .withClaim("resource_access", resource);

    // Sign the JWT with a service account
    FileInputStream stream = new FileInputStream(saKeyfile);
    GoogleCredential cred = GoogleCredential.fromStream(stream);
    RSAPrivateKey key = (RSAPrivateKey) cred.getServiceAccountPrivateKey();
    Algorithm algorithm = Algorithm.RSA256(null, key);
    return token.sign(algorithm);
  }
}
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;
using System.Security.Claims;
using Google.Apis.Auth.OAuth2;

class AutoqlJWT 
{
    // Summary:
    //     Creates an AutoQL JWT for authentication in the AutoQL API
    //
    // Parameters:
    //  serviceAccountFilePath:
    //      (string) The path to your Service Account JSON file.
    //      Your file can be found in the AutoQL Portal under Settings -> Service Account
    //
    //  audience:
    //      (string) Your custom domain without the URL scheme
    //      Example: "your-company.chata.io"
    //      Your domain can be found in the AutoQL Portal under Settings -> API Keys
    //
    //  projectId:
    //      (string) The ID of the project [optional].
    //      Project IDs can be found in the AutoQL Portal under Projects
    //      The Management API do not require project IDs and this can instead
    //      be replaced with an empty string "" in the JWT claims.
    //
    //  userId:
    //      (string) An ID that uniquely identifies the user who is using this JWT.
    //      User IDs are defined by the consumer of the AutoQL API.
    //      However you decide to uniquely identify your users is up to you.
    //
    public static string GenerateToken(string credentialFilePath, string audience, string projectId, string userId)
    {
        var credential = GoogleCredential.FromFile(credentialFilePath)
                                        .UnderlyingCredential as ServiceAccountCredential;
                                                    
        var now = DateTime.UtcNow;
        var expiresInSeconds = 3600;

        // These resource access routes will give users full access to both the management and service APIs.
        // Adjust these as required
        string[] resource_access = new string[2] 
        { 
            "/autoql/api/v1/**", 
            "/autoql/management/api/v1/**" 
        };
        
        var additionalClaims = new Dictionary<string, object>
        {
            { "email", credential.Id },
            { "project_id", projectId },
            { "user_id", userId },
            { "display_name", userId },  // The user's display name. If you don't have a display name you can use the userId.
            { "resource_access", resource_access },
            // If your database uses row-level control, you can specify one or more access control (ACL) IDs here.
            { "access_control_id", Array.Empty<string>() },
        };

        var signingCredentials = new SigningCredentials(new RsaSecurityKey(credential.Key), SecurityAlgorithms.RsaSha256);
        var tokenDescriptor = new SecurityTokenDescriptor 
        {
            IssuedAt = now,
            Expires = now.AddSeconds(expiresInSeconds),   
            Issuer = credential.Id,
            Audience = audience,
            Subject = new ClaimsIdentity(new Claim[] { new Claim("sub", credential.Id) }),
            Claims = additionalClaims,
            SigningCredentials = signingCredentials
        };

        var tokenHandler = new JwtSecurityTokenHandler();
        var token = tokenHandler.CreateToken(tokenDescriptor);
        return tokenHandler.WriteToken(token);
    }
}

While you can use this locally to send REST requests to the API, we recommend you also make this available through an internal web service or cloud function to provide the JWT to the rest of your deployment and the widgets, if you are opting to implement them.

Using Json Web Tokens

Once acquired, send your signed JWT in the Authorization: Bearer header when making requests to our API in the back end:

def make_jwt_request(signed_jwt, url='https://your-endpoint.com'):
    """Makes an authorized request to the endpoint"""
    headers = {
        'Authorization': 'Bearer {}'.format(signed_jwt.decode('utf-8')),
        'content-type': 'application/json'
    }
    response = requests.get(url, headers=headers)

    response.raise_for_status()
    return response.text
/**
 * Makes an authorized request to the endpoint.
 */
public static String makeJwtRequest(final String signedJwt, final URL url)
    throws IOException, ProtocolException {

  HttpURLConnection con = (HttpURLConnection) url.openConnection();
  con.setRequestMethod("GET");
  con.setRequestProperty("Content-Type", "application/json");
  con.setRequestProperty("Authorization", "Bearer " + signedJwt);

  InputStreamReader reader = new InputStreamReader(con.getInputStream());
  BufferedReader buffReader = new BufferedReader(reader);

  String line;
  StringBuilder result = new StringBuilder();
  while ((line = buffReader.readLine()) != null) {
    result.append(line);
  }
  buffReader.close();
  return result.toString();
}
public static IRestResponse makeJwtRequest(string url, string signedJwt)
{
	var client = new RestClient(url);
	var request = new RestRequest(Method.GET);
	request.AddHeader("content-type", "application/json");
	request.AddHeader("authorization", "Bearer " + signedJwt);
	return client.Execute(request);
}

If you are using any of the widgets we provide to access the API, you will need to get the JWT from your service and provide it as a property to the widget.

Below is some React-flavored pseudo-code to give you an idea of how this might work:

import React from 'react'
import axios from 'axios'

export default class App extends React.Component {
  componentDidMount = () => {
    this.getJWT()
  }
  
  getJWT = async () => {
    try {
      // You will need to create this endpoint to return the JWT
        let url = `https://[email protected]/jwt?display_name=${this.state.displayName}&project_id=${this.state.projectId}&other_authentication_info=${this.state.authenticationInfo}`
        
        const jwtResponse = await axios.get(url)
        const token = jwtResponse.data
    		localStorage.setItem('jwt', token)

    		this.setState({
          isAuthenticated: true,
          isAuthenticating: false,
        })
    } catch (error) {
      this.setState({
        isAuthenticated: false,
        isAuthenticating: false,
      })
      localStorage.removeItem('jwt')
    }
  }

  render = () => {
    if (this.state.isAuthenticated) {
      ...
    } else {
      ...
    }
  }
}

What’s Next

Now that you've accessed your Service Account File and created JWTs, the only thing you still need to successfully begin making API calls is an API key! Don't worry, this step is a lot simpler than creating JWTs and is covered in detail in the next doc.