Securing Custom REST API Endpoints
How to secure custom REST API endpoints in IntegrationManager using Basic Auth (partition credentials) or OAuth2 (JWT / opaque tokens).
Overview
When an external system calls IM's REST API (the Camel routes exposed via integration.rest.*), IM must verify who the caller is. Two authentication modes are supported:
|
Mode |
|
How it works |
|---|---|---|
|
Basic Auth (default) |
|
Caller sends |
|
OAuth2 |
|
Caller sends a Bearer token issued by their own Identity Provider. IM validates the token locally (JWT) or via introspection (opaque). |
Both modes enforce the same authorization rules defined by integration.rest.endpoints.secured, integration.rest.endpoints.roles, and integration.rest.doc.* properties.
Where to Configure
All authentication properties go into your instance's config/application.properties file. REST endpoints are defined as Camel routes in the routes/ directory.
/
├── config/
│ └── application.properties # ← auth properties go here
├── routes/
│ └── my-rest-api.xml # ← your REST endpoint routes
├── connections/
│ └── pricefx.json # ← Pricefx connection (used by Basic Auth)
└── ...
For details on the IM project structure, see Project Structure.
Note — duplicate keys silently override.
application.propertiesfollows last-value-wins for duplicate keys. If a property appears twice (e.g. a Basic Auth block followed by an appended OAuth2 block), the later line wins and silently overrides the earlier one. A strayintegration.rest.endpoints.roles=after an earlierintegration.rest.endpoints.roles=DATAINTEGRATIONclears the role requirement. Keep a single value per property.
Important — restart required after editing properties.
integration.rest.*and OAuth2 properties are loaded once at IM startup and are not reloaded at runtime. After changingauth-mode, OAuth2 configuration, or anyintegration.rest.*property inconfig/application.properties, restart the IntegrationManager service for the new configuration to take effect.
Quick Start
Basic Auth (default):
integration.rest.enabled=true
# auth-mode defaults to "basic"
integration.rest.auth-mode=basic
integration.rest.endpoints.path=/rest
integration.rest.endpoints.secured=true
# endpoints.roles defaults to ADMIN
integration.rest.endpoints.roles=ADMIN
OAuth2 with JWT:
integration.rest.enabled=true
integration.rest.endpoints.path=/rest
integration.rest.endpoints.secured=true
# clear the default (ADMIN) — accept any authenticated user
integration.rest.endpoints.roles=
integration.rest.auth-mode=oauth2
# token-type defaults to "jwt"
integration.rest.oauth2.token-type=jwt
integration.rest.oauth2.jwt.issuer-uri=https://{your-idp-domain}/...
This accepts any valid token from your IdP. For production, restrict access further with audience and a role/scope requirement — see Authorization Rules below. Note that "any valid token from your IdP" can be very broad (e.g. any client in your entire Azure AD tenant).
Note: The default value for
endpoints.rolesisADMIN(used by Basic Auth). When switching to OAuth2, you must either clear it (accept any authenticated user) or set it to a role/scope from your IdP along withroles-claim.
Authentication Modes
1. Basic Auth (Partition Credentials)
This is the default mode. Your external system sends Pricefx partition credentials with every request. IM validates the credentials against the Pricefx partition API using the connection defined in connections/pricefx.json (or via integration.pfx.* properties).
What your external system sends:
POST https://{im-host}/rest/myEndpoint
Authorization: Basic <base64-encoded username:password>
Content-Type: application/json
The username can be in two formats:
-
username(e.g.admin) — authenticates against the default partition from thepricefxconnection -
partition/username(e.g.company/admin) — authenticates against the specified partition (overrides the connection)
Minimal configuration:
integration.rest.enabled=true
integration.rest.endpoints.path=/rest
integration.rest.endpoints.secured=true
# endpoints.roles defaults to ADMIN — caller must have ADMIN role on the partition
With custom role:
integration.rest.enabled=true
integration.rest.endpoints.path=/rest
integration.rest.endpoints.secured=true
integration.rest.endpoints.roles=DATAINTEGRATION
2. OAuth2 (JWT)
Your external system obtains a token from your Identity Provider (Azure AD, Okta, Keycloak, etc.) and sends it to IM as a Bearer token. IM validates the token locally using your IdP's public keys — no per-request calls to the IdP.
What your external system sends:
POST https://{im-host}/rest/myEndpoint
Authorization: Bearer eyJhbGciOiJSUzI1NiJ9...
Content-Type: application/json
What IM needs from you:
-
The IdP's issuer URI or JWKS endpoint URL
Minimal configuration
integration.rest.enabled=true
integration.rest.endpoints.path=/rest
integration.rest.endpoints.secured=true
integration.rest.endpoints.roles=
integration.rest.auth-mode=oauth2
integration.rest.oauth2.token-type=jwt
integration.rest.oauth2.jwt.issuer-uri=https://{your-idp-domain}/...
Recommended: use issuer-uri. It validates both the token signature and the iss (issuer) claim, which binds trust to your specific IdP/tenant.
jwk-set-uri is an advanced alternative that skips OIDC discovery. Used on its own it validates only the signature and expiry — it does NOT check the issuer. If you use jwk-set-uri, always also set issuer-uri (and, for public IdPs, audience):
integration.rest.enabled=true
integration.rest.endpoints.path=/rest
integration.rest.endpoints.secured=true
integration.rest.endpoints.roles=
integration.rest.auth-mode=oauth2
integration.rest.oauth2.token-type=jwt
integration.rest.oauth2.jwt.jwk-set-uri=https://{your-idp-domain}/.well-known/jwks.json
integration.rest.oauth2.jwt.issuer-uri=https://{your-idp-domain}/...
Property details
|
Property |
Required |
Description |
|---|---|---|
|
|
Yes |
Set to |
|
|
Yes (or |
Your IdP's issuer URL. Recommended — validates the |
|
|
Yes (or |
Direct URL to your IdP's JWKS endpoint. Advanced — no issuer validation unless |
Examples by Identity Provider
Azure AD:
integration.rest.auth-mode=oauth2
integration.rest.oauth2.token-type=jwt
integration.rest.oauth2.jwt.issuer-uri=https://login.microsoftonline.com/{tenant-id}/v2.0
Note — Microsoft Entra ID (Azure AD) signing keys may be shared across tenants. A valid signature against the
login.microsoftonline.com/{tenant-id}/discovery/v2.0/keysendpoint therefore does not by itself prove the token was issued by your tenant — the issuer (iss) claim must also be validated. In IM, setissuer-urito your tenant-specific issuer using the tenant GUID (theissclaim contains the GUID, not the*.onmicrosoft.comdomain), and consider also settingaudienceto your IM app registration.
Note — Azure AD access token version (v1.0 vs v2.0). The
issandaudclaim formats depend on the token version configured on the IM app registration (accessTokenAcceptedVersionin the app manifest):
|
|
v2.0 tokens |
v1.0 tokens (default for many app registrations) |
|---|---|---|
|
|
|
|
|
|
|
|
Configure
issuer-uriandaudienceto match the version your tokens actually use — a v2.0issuer-urirejects every v1.0 token, and vice versa. Before rolling out, decode a real access token (e.g. with jwt.ms) and verify the configured values exactly match the token'sissandaudclaims.
Okta:
integration.rest.auth-mode=oauth2
integration.rest.oauth2.token-type=jwt
integration.rest.oauth2.jwt.issuer-uri=https://{your-domain}.okta.com/oauth2/default
Keycloak:
integration.rest.auth-mode=oauth2
integration.rest.oauth2.token-type=jwt
integration.rest.oauth2.jwt.issuer-uri=https://{host}/realms/{realm}
3. OAuth2 (Opaque Token / Introspection)
Instead of validating the token locally, IM forwards it to your IdP's introspection endpoint on every request. The IdP confirms whether the token is valid and returns its scopes.
This mode is useful for testing or when your IdP doesn't expose a JWKS endpoint.
Important — Token format restriction: The Bearer token must conform to the RFC 6750
b64tokenformat. Only the following characters are allowed:A-Z a-z 0-9 - . _ ~ + /and=for padding. Tokens containing other characters (e.g.!,#,@) will be rejected with a 401 Unauthorized error and the messageBearer token is malformed. This affects providers like Salesforce, whose opaque tokens use the formatorgId!sessionId. Salesforce tokens are not compatible with this mode.
Minimal configuration:
integration.rest.enabled=true
integration.rest.endpoints.path=/rest
integration.rest.endpoints.secured=true
integration.rest.endpoints.roles=
integration.rest.auth-mode=oauth2
integration.rest.oauth2.token-type=opaque
integration.rest.oauth2.opaque.introspection-uri=https://{your-idp}/oauth2/introspect
integration.rest.oauth2.opaque.client-id=im-client
integration.rest.oauth2.opaque.client-secret={{oauth2.client.secret}}
|
Property |
Required |
Description |
|---|---|---|
|
|
Yes |
Set to |
|
|
Yes |
Your IdP's token introspection endpoint URL |
|
|
Yes |
Client ID for authenticating to the introspection endpoint |
|
|
Yes |
Client secret. Use an encrypted property placeholder. |
Authorization Rules
By default, REST endpoints require the ADMIN role (configured via integration.rest.endpoints.roles=ADMIN). To change this, configure audience validation and/or role/scope requirements below.
Recommended production minimum (OAuth2 JWT):
issuer-uri+audience.issuer-uribinds trust to your tenant;audienceensures the token was issued for IM.secured=truealone only means "some valid token" — for a public IdP that can be far broader than you intend. To further restrict to specific client applications, add a role requirement (see Restricting by role/scope) or enable "assignment required" on your IdP.
Audience validation (JWT only)
Rejects tokens that were issued for a different application, even if signed by the same IdP. Strongly recommended — especially with Azure AD, where any client in the tenant can otherwise obtain a token your endpoint would accept:
integration.rest.oauth2.jwt.audience=api://my-im-app-id
Restricting by role/scope
For OAuth2 JWT — set the JWT claim containing roles and the required value:
integration.rest.oauth2.jwt.roles-claim=scp
integration.rest.endpoints.roles=im.call
The roles-claim property tells IM where to find roles in the JWT token. It depends on your IdP:
|
IdP |
|
Example role |
|---|---|---|
|
Azure AD |
|
|
|
Okta (scopes) |
|
|
|
Okta (groups) |
|
|
|
Keycloak |
|
|
For OAuth2 opaque tokens — roles come from the scope field in the introspection response automatically. No roles-claim needed:
integration.rest.endpoints.roles=im.call
For Basic Auth — roles come from the Pricefx user's role assignments automatically:
integration.rest.endpoints.roles=DATAINTEGRATION
Authorization logic
# REST endpoints — require authentication and specific role
integration.rest.endpoints.secured=true
integration.rest.endpoints.roles=DATAINTEGRATION
# API documentation — public access
integration.rest.doc.secured=false
|
|
|
Who can access |
|---|---|---|
|
|
(ignored) |
Everyone — no authentication required |
|
|
(empty) |
Any authenticated user — no specific role required |
|
|
|
Only users with at least one of the listed roles |
Role matching is case-insensitive.
endpoints.roles=im.callmatches a token scope ofim.call,IM.CALL, orIm.Call.
Choosing Between OAuth2 and Basic Auth
|
|
Basic Auth |
OAuth2 (JWT) |
|---|---|---|
|
Setup |
Works out of the box |
Requires IdP configuration |
|
Security |
Password in every request (over HTTPS) |
Signed token — no password in transit |
|
Performance |
First call requires Pricefx API calls (then cached) |
Token validated locally — no external calls |
|
Token expiry |
No automatic expiry |
Tokens expire automatically |
|
Access management |
Via Pricefx user accounts |
Via your own Identity Provider |
Recommendation: Use OAuth2 JWT when you have an Identity Provider. Use Basic Auth for simple setups.
Troubleshooting
|
Problem |
Possible cause |
Solution |
|---|---|---|
|
Configuration changes to |
IM was not restarted after editing |
Restart the IntegrationManager service |
|
IM fails to start with |
OAuth2 JWT mode enabled but no JWKS/issuer URI configured |
Set |
|
IM fails to start with |
|
Either clear |
|
IM fails to start with |
Opaque mode enabled but introspection properties missing |
Set |
|
401 Unauthorized — |
Token contains characters outside the RFC 6750 |
Opaque tokens must only contain |
|
401 Unauthorized with OAuth2 |
Token invalid, expired, or (if |
Verify the token is unexpired; if you rely on issuer binding, confirm |
|
Endpoint accepts tokens it shouldn't (e.g. from any tenant) |
|
Add |
|
401 Unauthorized with OAuth2 + audience |
Token's |
Verify |
|
401 Unauthorized for all tokens after setting |
Token version mismatch — the app registration issues v1.0 tokens ( |
Decode a real token and align |
|
401 Unauthorized with Basic Auth |
Wrong username/password or partition unreachable |
Check credentials; verify |
|
403 Forbidden with OAuth2 |
Token is valid but missing the required role/scope |
Check |
|
403 Forbidden with Basic Auth |
User authenticated but doesn't have the required role |
Assign the required role to the user in the Pricefx partition |
|
REST endpoints return 401 but should be public |
|
Set |
|
OAuth2 chain not active (Basic Auth prompt instead) |
|
Set |
Tip: Enable
logging.level.org.springframework.security=DEBUGinapplication.propertiesto see detailed authentication/authorization decisions in the logs.
Complete Property Reference
|
Property |
Default |
Description |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
(empty) |
IdP issuer URI |
|
|
(empty) |
Direct JWKS endpoint URL |
|
|
(empty) |
Expected audience value |
|
|
(empty) |
JWT claim with roles/scopes (required when |
|
|
(empty) |
Introspection endpoint URL |
|
|
(empty) |
Introspection client ID |
|
|
(empty) |
Introspection client secret |
|
|
|
Base path for REST endpoints (e.g. |
|
|
|
Require authentication for REST endpoints |
|
|
|
Required roles (comma-separated). Clear for OAuth2 or set to your IdP's role/scope. |
|
|
|
Require authentication for API docs |
|
|
(empty) |
Required roles for API docs |