Connect with OAuth (IDCS device flow)
This guide walks an Oracle Identity Domain Administrator through registering EPM Workbench for per-user OAuth sign-in, so that Pull / Push / Deploy / Execute run as the individual developer — and Oracle’s own job console and audit log attribute the work to the human, instead of to a shared Service Account.
It was written from a live end-to-end setup against a real Pod, so it calls out where the Oracle console deviates from the official docs and where the setup produces opaque errors. If you are debugging a failing setup, read the Troubleshooting table first — every error we hit is mapped to its cause and fix there.
Key concept: setup is two-sided
Section titled “Key concept: setup is two-sided”There are two separate things to configure, and both are required. Missing
the second is the single most common failure — it produces an opaque 401 with
no useful message.
| What it is | Where it lives | |
|---|---|---|
| Step 1 — the client app | The application EPM Workbench signs in as (a public “Mobile” app) | Domains → your domain → Integrated applications |
| Step 2 — the resource server | Telling the Pod to accept those OAuth tokens | Domains → your domain → Oracle cloud services → your Pod |
Step 1 is “who is asking.” Step 2 is “the Pod agreeing to listen.” Do both.
Prerequisites
Section titled “Prerequisites”- You have the Identity Domain Administrator role in the identity domain backing the Pod (OCI Console → Identity & Security → Domains → your domain → the role is on your user).
- The Pod is Gen 2 (OCI). OAuth 2.0 is Gen-2 only; Classic / Gen-1 Pods stay on Basic auth with a Service Account.
- You know which identity domain governs EPM. A tenancy can have several domains
(ERP / OIC / etc. may use different ones); pick the one over EPM. In a
single-domain tenancy this is just
Default.
Step 1 — Register the client application
Section titled “Step 1 — Register the client application”- OCI Console → ☰ → Identity & Security → Domains → open the domain that governs EPM.
- Note the Domain URL from the Details tab — e.g.
https://idcs-<hash>.identity.oraclecloud.com. You need this later (it’s the OAuth host). It is not derivable from your Pod URL, so copy it now. - Go to Integrated applications → Add application.
- Choose Mobile Application.
Why Mobile and not Confidential: a Mobile app is a public client (no client secret). EPM Workbench is a distributed desktop extension and cannot keep a secret. Oracle’s own description of Mobile Application — “hosted on the resource owner’s machine … cannot maintain the confidentiality of their client secret” — is exactly our case. The “reduced integration options” note on that card does not exclude Device Code.
- Name it (e.g.
epm-workbench). Leave all URL fields (sign-in/out/error/callback) blank — device flow has no redirect. Leave the display toggles off. Click Submit.OAuth configuration is not on this creation form. A “Configure OAuth on the Application details page” note appears — that is the remaining substeps, below.
- On the app’s OAuth configuration tab, click Edit OAuth configuration:
- Allowed grant types: enable Device code and Refresh token. Turn off Implicit.
- Redirect / Post-logout / Logout URLs: leave blank.
- Client IP address: choose Anywhere. (“Restrict by network perimeter” needs a configured perimeter policy and can silently block token requests.)
- Bypass consent: off (optional — on skips a one-time consent screen).
- Token issuance policy → Add resources: ON. Click Add scope, find
your Pod in the list (see the naming note below), expand it,
and check its
urn:opc:serviceInstanceID=…urn:opc:resource:consumer::allscope. Click Add.Copy the scope from Oracle, don’t retype it. It embeds a long service-instance OCID; a single wrong character gives
invalid_scope. The row is truncated on screen — copy the full value. One resource per token is an IDCS hard limit; add more Pods later as additional scopes. - Add app roles: ON. Click Add roles, choose Identity Domain
Administrator, Add.
This is mandatory per Oracle’s EPM REST OAuth doc. Without it, authorization is denied (“Access to the application is denied”). It is a required trust grant for the client app.
- Submit.
- Back on the app page, Actions → Activate the application. Copy the Client ID (not a secret — it goes into EPM Workbench’s committed config).
Pod naming note
Section titled “Pod naming note”Pods appear in the scope picker under two naming generations:
- Older:
Planning_<name>(type “Oracle Enterprise Performance Management”). - Newer: bare
<name>likeplanning-test(type “Planning Service”).
Pick the one matching your target Pod. If unsure which is which, the Pod URL’s
leading segment (e.g. planning-test-…) matches the newer bare name.
Step 2 — Configure the Pod as a resource server
Section titled “Step 2 — Configure the Pod as a resource server”Do not skip this. Without it, tokens are issued but every EPM REST call
returns 401.
- In the same domain, go to Oracle cloud services (a tab alongside Integrated applications).
- Click your Pod in the list (e.g.
planning-test). This is the Pod as a resource server, separate from the client app you just made. - On its OAuth configuration tab, click Edit OAuth configuration:
- Enable Allow token refresh.
Without this, requesting
offline_accessfails withunauthorized_client: "The resource does not support offline access."and no refresh token is ever issued. - Enable Add secondary audience, click Add, and enter the EPM Pod
URL exactly, e.g.
https://<pod>.epm.<region>.ocs.oraclecloud.com.This is the fix for the
invalid_session401. The token’s primary audience is theserviceInstanceID, but EPM’s Access Manager validates the token against the host you are calling (the Pod URL). Adding the Pod URL as a secondary audience puts it in the token’saud, so EPM accepts the session. - Submit.
- Enable Allow token refresh.
A per-user token issued for this Pod will now be accepted by its REST APIs.
What you hand to the developer
Section titled “What you hand to the developer”Three non-secret values (they go into committed config — a domain-level
domain.json, or pod.json for a single Pod):
| Value | Example | Notes |
|---|---|---|
| Identity-domain host | https://idcs-<hash>.identity.oraclecloud.com | From the domain Details tab. Not derivable from the Pod URL. |
| Client ID | c95fa57c0792414982680ae1e99c40f0 | From the client app. Public, not a secret. |
| Scope | urn:opc:serviceInstanceID=<env-OCID>urn:opc:resource:consumer::all | Per-Pod. Copy verbatim from the scope picker. |
The developer never supplies these by hand. They are committed once by whoever sets up the Pod or Domain. Each developer then just runs Connect with your EPM account and signs in through their normal SSO / MFA. Their refresh token stays in VS Code SecretStorage on their machine and is never shared.
Reference: the flow EPM Workbench performs
Section titled “Reference: the flow EPM Workbench performs”Provided so you can reproduce it with curl when debugging.
- Device request —
POST {host}/oauth2/v1/device, bodyresponse_type=device_code&client_id=<id>&scope=<scope> offline_access. Returnsuser_code,verification_uri({host}/ui/v1/device),device_code,expires_in.response_type=device_codeis required (IDCS-specific; not in the RFC).client_idgoes in the body, never HTTP Basic.
- The developer opens the verification URL, signs in (SSO / MFA), enters the code, and approves.
- Poll —
POST {host}/oauth2/v1/token, bodygrant_type=urn:ietf:params:oauth:grant-type:device_code&device_code=<code>&client_id=<id>. Returnsaccess_token(~3600s) andrefresh_token(~7d). - Call EPM —
Authorization: Bearer <access_token>against{pod}/HyperionPlanning/rest/...or{pod}/interop/rest/.... - On expiry, refresh —
POST {host}/oauth2/v1/token,grant_type=refresh_token&refresh_token=…→ new access + rotated refresh token.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Cause | Fix |
|---|---|---|
400 invalid_request on the device request | Missing response_type=device_code (IDCS requires it) | Add response_type=device_code to the device request body |
invalid_client on token/refresh | Sent client credentials as HTTP Basic on a public client | Send client_id in the request body, no Basic auth |
invalid_scope on the device request | Scope string mistyped (the embedded env OCID is off by a character) | Copy the scope verbatim from the OCI scope picker; don’t retype |
unauthorized_client: "The resource does not support offline access." | Resource server has Allow token refresh off | Step 2 → enable Allow token refresh on the Pod’s OAuth config |
| Browser: “Access to the application is denied” | Client app missing the Identity Domain Administrator app role | Step 1 → Add app roles → Identity Domain Administrator |
401 WWW-Authenticate: Bearer error="invalid_session" on every EPM REST call, even though the token looks valid | Pod URL not in the token’s aud (EPM’s OAM validates audience against the host called) | Step 2 → Add secondary audience = the EPM Pod URL |
Refresh token → EPM scope exchange fails 400 | Trying to elevate a domain-only (openid) refresh token to the EPM scope | Request the EPM scope directly in the device flow (with offline_access); don’t exchange a domain refresh token |
Quick sanity check that the Pod itself is fine (isolates OAuth from everything
else): the same REST call with Basic auth (a Service Account username and
password) should return 200. If Basic works and OAuth returns
401 invalid_session, the problem is Step 2 (secondary audience), not your Pod
or your user’s roles.