Authentication

In this page, we provide information to answer the following questions:

  1. What information does Glean provide to authenticate requests coming to the tools server?
  2. How can the tools server verify the requests are coming from Glean?

Authentication information provided by Glean

Broadly, currently Glean supports the OAuth 2.0 standard to enable servers to authenticate requests. When developing a tool, under the authentication section - you will see 3 options:

tools_auth_section

1. None Authentication Type

No tokens are provided in the request. Choose this method if you require no tokens from Glean specifically to handle the request. Tool developers may choose this method if they already have access in their server to either retrieve relevant data (or) take actions initiated by the user.

Important

If this authentication type is set - it is highly recommend to follow the steps outlined below to verify that requests are coming from Glean. If there are no further protections on the server endpoint (eg. VPN, Firewall) - then this endpoint would be publicly accessible via the internet.

2. OAuth Admin Authentication Type

Here, the tool developer (or admin for the app) needs to authorize once at setup time. Once this is successful, glean will send the token for all requests issued by all users for whom the tool is deployed to.

3. Oauth User Authentication Type

Here, the tool developer (or admin for the app) will provide a few initial details re: the oauth connection itself (see below). Once this is done, the glean user will need to authorize the tool when the tool is invoked for the first time. All subsequent calls from that user will be authenticated by sending the user's token in the request to the tools server.

Oauth setup parameters

There are a few parameters to be set when configuring OAuth (admin or user) in the tools setup step. All of these follow the OAuth authorization_code grant type:

  • Client ID : The client ID for your OAuth application.
  • Client Secret : The client secret for your OAuth application.
  • Client URL : The URL that will be used to redirect the user to authenticate.
  • Authorization URL : Glean will use this to complete the OAuth step by issuing a POST request with the authorization code.
  • Scopes : An optional parameter to include relevant scopes for your application. Note: Depending on the OAuth app, it is recommended to add an offline_access scope if the app supports it to ensure refresh tokens are sent ( reference ).
Important

The OAuth App must be configured to allow redirects (aka Callback URL) to the following URL to be able to exchange the code for a token:

https://{your-glean-instance}-be.glean.com/tools/oauth/verify_code/{toolName}

Otherwise the OAuth integration might not work, or you might see an error indicating an invalid redirect URI.

How to verify requests coming from Glean

Note

This is an optional, but highly recommended step that could be performed in the tools server when it receives a request from Glean to improve the security posture of the tools server endpoints.

TL;DR Glean always provides a token signature in the header under Glean-Tools-Signature. This token signature can be verified using the public key available for your glean instance. Follow the code snippets below to perform the verification.

Details

Glean provides a JWT based signature in a header Glean-Tools-Signature which is signed using a private key based on RSA-SHA256 algorithm. The public key is available via the following URL: https://{your-glean-instance}-be.glean.com/api/v1/tools/verification_key. We package the following claims in the JWT header (note that we use standard claims):

  1. iat : Issued at time
  2. exp : Expiration time
  3. iss : Issuer - Which is always set to 'glean'

Code Snippets

Below are code snippets to perform the verification.

pythonjava
Copy
Copied
import jwt
import json
import requests

# Fill your glean instance here.
YOUR_GLEAN_INSTANCE=''

# Use this function as isin your code (once you have filled out YOUR_GLEAN_INSTANCE). 
# Pass the header value for Glean-Tools-Signature as the 'token' in this function.
def verify_jwt(token):
  try:
    # First, we fetch the public key JSON response.
    response = requests.get(f"https://{YOUR_GLEAN_INSTANCE}-be.glean.com/api/tools/v1/verification_key")
    response.raise_for_status()  # Raises an exception for 4XX/5XX responses
    public_key_str = response.json()['publicKey']

    # Second, we convert this into the PEM format.
    pem_key = f"-----BEGIN PUBLIC KEY-----\n{public_key_str}\n-----END PUBLIC KEY-----"

    # Finally, we attempt to decode the token using the public key.
    decoded = jwt.decode(token, pem_key, algorithms=['RS256'],issuer='glean')
    return True
  except jwt.PyJWTError as e:
    # Handle error (e.g., token expired, token tampered, etc.)
    print(f"JWT verification failed: {e}")
    return False
Copy
Copied
import java.util.Base64;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import org.jose4j.jwa.AlgorithmConstraints;
import org.jose4j.jwt.consumer.InvalidJwtException;
import org.jose4j.jwt.consumer.JwtConsumer;
import org.jose4j.jwt.consumer.JwtConsumerBuilder;

public static void verifySignature(String publicKey, String jwtFromHeader) throws IOException {
    byte[] derKey = Base64.getDecoder().decode(publicKey);
    final X509EncodedKeySpec keySpec = new X509EncodedKeySpec(derKey);
    PublicKey rsaOrEcKey;
    try {
      rsaOrEcKey = KeyFactory.getInstance(JWT_ALG).generatePublic(keySpec);
    } catch (NoSuchAlgorithmException e) {
      // This should never happen, since we're using a standard algorithm.
      throw new RuntimeException(e.getMessage());
    } catch (InvalidKeySpecException e) {
      throw new RuntimeException(
          "Unhandled exception during public key setup: " + e.getMessage());
    }

    AlgorithmConstraints constraints =
        new AlgorithmConstraints(AlgorithmConstraints.ConstraintType.WHITELIST, "RS256");

    JwtConsumer jwtConsumer =
        new JwtConsumerBuilder()
            .setRequireExpirationTime()
            .setRequireIssuedAt()
            .setAllowedClockSkewInSeconds(30)
            .setExpectedIssuer("glean")
            .setVerificationKey(rsaOrEcKey)
            .setJwsAlgorithmConstraints(constraints)
            .build();

    try {
      jwtConsumer.processToClaims(jwtFromHeader);
    } catch (InvalidJwtException e) {
      throw new IOException("Failed to verify tools signature: " + e.getMessage());
    }
  }