Secure Service-to-Service Authentication for Forge Web Trigger Endpoint (EazyBI Data Source)

We are currently developing a Forge app (headless, no UI) to facilitate data synchronization between Jira Cloud and EazyBI.

We use a Forge web trigger to store the necessary data, which is then exposed via a GET endpoint to be used by EazyBI as a REST API data source. This endpoint serves sensitive data.

The Core Security Problem: How do we secure this stateless web trigger endpoint to guarantee that only the authorized EazyBI service (and not arbitrary third parties) can access it?

  1. Standard Forge security mechanisms like asUser() are not applicable here, as there is no Jira user initiating the call.

  2. The web trigger URL is technically public. While we can use the sharedSecret from the Forge installation to validate the call (checking for the Authorization: Bearer token in the header), how do we securely provision this secret into EazyBI?

  3. EazyBI offers authentication options including OAuth 2.0 (client credentials flow), Basic Auth, and Token Auth.

Question: What is the recommended best practice for implementing service-to-service authentication in this specific Forge scenario?

  • Should we use the Forge sharedSecret and instruct the user to manually configure it in EazyBI as a Bearer Token?

  • Is there a native way to implement an OAuth 2.0 Client Credentials flow, where the Forge app acts as the Resource Server and EazyBI as the Client?

2 Likes

So, for anyone that is interested , I received the following from EazyBi:

The most practical approach for your Forge web trigger scenario is to use HTTP header authentication in eazyBI with your Forge sharedSecret:

  1. In your Forge app, validate incoming requests by checking for the Authorization: Bearer <token> header

  2. In the eazyBI REST API source configuration, select HTTP header authentication

  3. Set Header name to Authorization

  4. Set Header value to Bearer <your-forge-sharedSecret>

This will allow you to validate that requests come from an authorized source (eazyBI with the correct token), keep the authentication stateless, and avoid exposing the endpoint to arbitrary third parties. Additionally, when headers are stored in eazyBI source application settings, they are encrypted and not exposed to subsequent user views.

Unfortunately, eazyBI’s current OAuth 2.0 implementation is designed for user authorization flows (requiring Authorize URL, Token URL, and a redirect callback). It doesn’t natively support a pure service-to-service OAuth 2.0 Client Credentials flow where your Forge app would act as the Resource Server.

The OAuth 2.0 option in eazyBI expects an authorization step involving user consent, a redirect callback to eazyBI, and token exchange after user authorization, which doesn’t align with your headless, service-to-service authentication requirement.

The sharedSecret would need to be manually configured by the user in eazyBI - there’s no automatic provisioning mechanism. If you need more sophisticated authentication, you can implement a custom token generation mechanism in your Forge app and provide users with time-limited tokens; however, this would still require manual configuration in eazyBI.

1 Like

You can implement asymmetric service-to-service authentication between Forge and EazyBI using a two-layer key system.

Step 1: Global key pairs

Generate two asymmetric key pairs:

  • Forge App: ForgeAppPrivateKey, ForgeAppPublicKey
  • EazyBI: EazyBIPrivateKey, EazyBIPublicKey

During deployment:

  • Store ForgeAppPrivateKey and EazyBIPublicKey as Forge variables (forge variables set).
  • Store EazyBIPrivateKey and ForgeAppPublicKey securely inside EazyBI’s environment.

Now both systems can issue and verify JWT tokens signed with their respective private keys and validated with the other party’s public key.

Each JWT payload may include:

{
  "iat": 1734970400,
  "exp": 1734971000,
  "scope": "sync:data",
  "tenant": "installation-id-or-cloudid"
}

Step 2: Per-installation trust

However, this alone doesn’t prevent EazyBI from calling a trigger for the wrong Jira site.

To isolate tenants, you can subscribe to the avi:forge:installed:app event and generate a per-installation key pair:

  • InstallationPrivateKey
  • InstallationPublicKey

Store InstallationPrivateKey as a Forge secret.

Then encrypt InstallationPublicKey using EazyBIPublicKey(Optional), sign it with ForgeAppPrivateKey, and send it to EazyBI.

EazyBI verifies the signature with ForgeAppPublicKey, decrypts with its EazyBIPrivateKey(Optional), and stores the valid InstallationPublicKey.

When EazyBI calls the Forge web trigger:

  • Include an encrypted cloudId(or another tenantId/installationId) in the JWT payload, encrypted using InstallationPublicKey.
  • On the Forge side, decrypt with InstallationPrivateKey and validate that the cloudId matches context.cloudId (or another tenantId/installationId).

This ensures that:

  1. The call truly comes from EazyBI (as signature is valid).
  2. The data belongs to the correct Jira site.
  3. No shared static secrets are exposed in code or configuration.

Key rotation

To keep this secure over time, rotate the key pairs periodically.

You can trigger rotation:

  • On the avi:forge:upgraded:app event, when the app is redeployed or version changes, or
  • Using a scheduleTrigger to perform automatic key rotation (e.g. once per month).

This approach combines JWT-based asymmetric authentication with tenant-level key isolation and regular rotation, providing strong service-to-service security without any manual secret handling.

2 Likes