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 and Java below. C# code is provided for guidance but is currently unverified.

import google.auth.crypt
import google.auth.jwt
import time

def generate_jwt(sa_keyfile,
                 sa_email,
                 audience,
                 expiry_length,
                 project_id,
                 user_id,
                 display_name,
                 resource_access,
                 acl):

    """Generates a signed JSON Web Token using a Google API Service Account."""
    now = int(time.time())

    # build payload
    payload = {
        'iat': now,
        # expires after 'expiry_length' seconds.
        "exp": now + expiry_length,
        # iss must match 'issuer' in the security configuration in your
        # swagger spec (e.g. service account email). It can be any string.
        'iss': sa_email,
        # aud is the chata.io domain for your company. It will look like
        # <your-company>.chata.io
        'aud':  audience,
        # sub and email should match the service account's email address
        'sub': sa_email,
        'email': sa_email,
        # the id of the project in autoql. 
        # These can be found in the integrator portal or from the API
        'project_id': project_id,
        # The id and display name of the user utiliizing AutoQL
        'user_id': user_id,
        'display_name': display_name,
        # A list of ant-style urls the token will give access to
        'resource_access': resource_access,
    }

    if acl and len(acl) > 0:
        payload['access_control_id'] = acl

    # sign with keyfile
    signer = google.auth.crypt.RSASigner.from_service_account_file(sa_keyfile)
    jwt = google.auth.jwt.encode(signer, payload)
    return jwt
  
jwt = generate_jwt(
  # change to your credential file path
  "/the/path/to/your/file-prod.json",
  "[email protected]t.com",
  "company.chata.io",
  # seconds in 336 hours, two weeks
  1209600,
  # project Id which you want to query data
  "project_id",
  "[email protected]",
  "first last",
  [
  "/autoql/api/v1/**",
  "/autoql/management/api/v1/**"
  ],
  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);
  }
}
// Uses JSON.NET and Sytem.IdentityModel.Tokens.Jwt

public string GenerateToken(string saKeyfile, string saEmail, string audience, double expiryLength,
        string customerId, string userId, List<String> resources) {
    dynamic dynamicSaKeyFile = JObject.Parse(saKeyfile);
    key = dynamicSaKeyFile.private_key;
    var tokenHandler = new JwtSecurityTokenHandler();
    var tokenDescriptor = new SecurityTokenDescriptor{
        IssuedAt = DateTime.UtcNow,
        Expires = DateTime.UtcNow.AddSeconds(expiryLength),
        Issuer = saEmail,
        Audience = audience,
        Subject = saEmail,
        Claims = new ClaimsIdentity(new Claim[]
        {
            new Claim("email", saEmail),
            new Claim("customer_id", customerId),
            new Claim("user_id", userId),
            new Claim("resource_access", resources)
        }),
        SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.RsaSha256)
    };

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

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.