Pricefx Client OAuth Connection

Overview

Since PFIMCORE-3074, IntegrationManager can authenticate to a Pricefx partition with OAuth credentials instead of partition basic auth. The connection type is pricefx-client-oauth and it works as follows:

  • The client secret stored in the connection is a long-lived refresh token issued by the partition.

  • IM exchanges the refresh token for a short-lived access token at https://<instance>/pricefx/<partition>/oauth/token (grant_type=refresh_token).

  • Every IM call to Core then carries an Authorization: Bearer <access_token> header. The token is cached and refreshed automatically before it expires.

Attribute

Details

Connection type

pricefx-client-oauth (PriceFxOAuthConnection)

Core requirement

Partition release 15.3 or later (OAuth support is experimental)

Credential

clientId + clientSecret (refresh token), encrypted at rest by IM

Default name behavior

When named pricefx, IM uses it as the default partition connection

Fallback

Deploy a basic-auth pricefx connection instead; connections hot-reload without restart

Prerequisites

  • The partition runs Core release 15.3 or later. On older releases the /oauth/* endpoints return HTTP 404.

  • You can edit Advanced Configuration Options on the partition (administrator access).

  • You can sign in to the partition in a browser as the user the IM connection should act as.

Step 1 — Register the OAuth client on the partition

OAuth clients are registered in the oauthConfiguration advanced configuration option. In the partition UI, go to Administration → Configuration → Advanced Configuration Options and create (or extend) oauthConfiguration:

JSON
{
  "knownClients": {
    "im-client": {
      "redirect_uri": "http://localhost:8000/callback",
      "token_expiry": 7200,
      "client_description": "IntegrationManager OAuth client"
    }
  }
}
  • The JSON key (im-client) is the client_id the client sends — choose your own, but use it consistently in all following steps.

  • redirect_uri is mandatory and matched as an exact string (protocol, host, port, path, trailing slash). For a non-interactive client like IM, a placeholder such as http://localhost:8000/callback is fine — nothing needs to listen there.

  • token_expiry is the access-token lifetime in seconds (default 7200).

The option can also be set through the API: POST /pricefx/<partition>/configurationmanager.set/oauthConfiguration.

Step 2 — Obtain the authorization code

Core currently implements only the Authorization Code grant, so the initial credential issuance is a one-time manual browser flow.

  1. Sign in to the partition in a browser as the user the IM connection should act as. Use a private browser window to avoid a stale session on a different partition.

  2. Open the authorization URL (note the URL-encoded redirect_uri):

https://<instance>/pricefx/<partition>/oauth/authorize?response_type=code&client_id=im-client&redirect_uri=http%3A%2F%2Flocalhost%3A8000%2Fcallback
  1. Confirm the consent screen. The browser is redirected to http://localhost:8000/callback?code=<CODE> — the page does not load (nothing listens there), but the code is in the address bar. Copy it.

The code is valid for 10 minutes and can be used only once. If it expires or the exchange fails, repeat this step.

Step 3 — Exchange the code for tokens

Bash
curl -s -X POST "https://<instance>/pricefx/<partition>/oauth/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code&code=<CODE>&redirect_uri=http%3A%2F%2Flocalhost%3A8000%2Fcallback&client_id=im-client"

The response contains both tokens:

JSON
{
  "access_token": "eyJhbGciOiJSUzI1NiJ9...",
  "token_type": "bearer",
  "expires_in": 7200,
  "refresh_token": "eyJhbGciOiJSUzI1NiJ9..."
}

Store the refresh_token — that is the value for the IM connection's clientSecret. The refresh token currently has an unlimited lifetime (subject to review on the Core side).

Step 4 — Verify the tokens without IM

Verify the refresh flow (this is exactly the request IM sends at runtime):

Bash
curl -s -X POST "https://<instance>/pricefx/<partition>/oauth/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token&refresh_token=<REFRESH_TOKEN>&client_id=im-client"

Verify the access token on a regular API call:

Bash
curl -s -H "Authorization: Bearer <ACCESS_TOKEN>" "https://<instance>/pricefx/<partition>/ping"

Step 5 — Configure the IM connection

Create connections/pricefx.json in the instance repository:

JSON
{
  "id": "pricefx",
  "discriminator": "net.pricefx.integration.component.rest.domain.connection.PriceFxOAuthConnection",
  "uri": "https://<instance>/pricefx",
  "partition": "<partition>",
  "clientId": "im-client",
  "clientSecret": "<REFRESH_TOKEN>"
}
  • clientSecret is encrypted at rest by IM the same way as the basic-auth password (stored as {ENC}... when auto-encrypt is enabled).

  • For local development, keep the secret out of git with local-secret.properties: connections.pricefx.clientSecret=<REFRESH_TOKEN>.

  • The partition value must match the partition the refresh token was issued on — the token carries the partition as a JWT claim and Core validates it.

Step 6 — Verify in IM

  • Run the connection test (POST /api/connections/test, or the test button in PlatformManager). The OAuth connection test acquires a real access token, so a success proves the whole flow.

  • Run any route using the connection; with "debug": true the client log shows Authorization: Bearer headers instead of Basic.

Switching between basic auth and OAuth

The authentication mode is determined by which connection type is deployed. To switch a partition to OAuth, replace the basic-auth pricefx connection JSON with the OAuth one (same id); to fall back, deploy the basic-auth JSON again. Connections hot-reload, so no restart is needed and cached tokens are discarded on redeploy.

Troubleshooting

Error

Cause

Fix

redirect_uri did NOT match configuration

The redirect_uri sent in the authorize or token request differs from oauthConfiguration (exact string match, including encoding, port, and trailing slash).

Send the identical, correctly URL-encoded value in both requests.

invalid_grant: Invalid partition

The refresh token was issued on a different partition than the token endpoint being called — the partition claim inside the JWT does not match.

Decode the JWT payload (second dot-separated segment, base64) and check the partition claim; re-issue the token while signed in to the correct partition.

invalid_grant on code exchange

The authorization code expired (10 minutes) or was already used (single use).

Repeat Step 2 to obtain a fresh code.

HTTP 404 on /oauth/*

The partition release is older than 15.3.

Upgrade the partition or keep basic auth.

IM fails with NonRecoverableException: OAuth token request ... failed

The refresh token is invalid, revoked, or the client was removed from oauthConfiguration. IM does not retry a rejected grant.

Re-issue credentials (Steps 2–3) and update the connection.

Security notes

  • The refresh token is equivalent to a password — treat it accordingly. IM encrypts it at rest, masks it in logs and toString, and blocks access to it from the Groovy sandbox.

  • Access to the access_token and clientSecret getters is restricted in sandboxed Groovy expressions.

  • Removing the client from oauthConfiguration on the partition invalidates the credentials; IM surfaces a non-recoverable error and does not retry.