# Okta (Okta Identity Engine)

![Okta_Logo_BrightBlue_Medium.png](https://assets.relyanceuat.xyz/images/docs/34594768882317/34594768868237.png)
Okta is a platform-as-a-service used for identity management (IDaaS). It is a customizable, secure, drop-in solution to add authentication and authorization services to your applications, providing scalable authentication built right into your application without the development overhead, security risks, and maintenance.

The Relyance Okta integration helps with a) understanding personal data types stored in Okta and b) providing visibility into third-party vendors configured in Okta. This guide is for customers using the newer Okta Identity Engine (OIE) platform. If you're unsure whether you're using Okta Classic or Okta OIE, see this support article from Okta.

#### **Choose an authentication method**

Relyance supports two ways to connect to Okta. Both run the same scans and read the same data - they differ only in how the connection authenticates.

- **Machine to Machine (Recommended)** - Uses cryptographic key signing instead of a client secret. No interactive browser sign-in at connection time and no refresh token to expire. Relyance generates and manages the key for you; you only paste a JWKS URL into Okta. Start with this method.
- **OAuth2 (OIDC Web App)**- The original interactive flow. Requires a Super Admin to click **Authorize** in a browser and relies on a refresh token. Use this only if you cannot create an API Services app. See [Part 2](#oauth2-method).

### **Part 1 - Machine to Machine Authentication (Recommended)**

Configure a service app in Okta so Relyance can connect without an interactive browser sign-in at connection time. Setup takes about 5–10 minutes and only needs to be done once.

ℹ️ Why Machine to Machine?

The standard OAuth2 flow requires a Super Admin to click

Authorize interactively in a browser. Machine to Machine uses cryptographic key signing instead - no browser step, no refresh token to expire. Relyance generates and manages the key for you; you only need to paste a JWKS URL into Okta. Okta also requires the service app to have both OAuth scopes and an admin role before it can call Okta Management APIs.

#### **🔑 Which admin role do you need?**

The integration runs **Vendor Discovery** by default. Vendor Discovery calls Okta's API Service Integrations endpoint (`/integrations/api/v1/api-services`), which Okta authorizes **only** for the **Super Administrator** role - no narrower or custom role can cover it. So the service app you create must be assigned Super Administrator. This is the standard setup.

**🔒 The Super Administrator role grants Relyance no write access - Okta's authorization model prevents it.**

- **Two independent checks.** Okta authorizes every Management API call against both the access token's scopes *and* the app's admin role. A call succeeds only if both permit it. Relyance's token carries read scopes only, so Okta refuses every create, update, or delete with `insufficient_scope` - the role cannot override a scope the token doesn't have.
- **Capability comes from scopes, not the role.** The role decides which resources are in reach; the granted scopes decide which operations are allowed. With read-only scopes, the allowed operations are read-only - on every resource, Super Administrator or not.
- **You control the scopes.** The app holds exactly the scopes you grant on the Okta API Scopes tab. For discovery that is `okta.*.read` only, and Okta issues a token containing only those.
- **The app cannot expand its own access.** Granting a scope is a Super Administrator action in the Okta console; doing it via the API requires a management scope the read-only app does not hold. There is no path for the app to escalate itself.
- **Why you may see `okta.users.manage` in the token request:** the connector lists it on every request so a single app can also serve DSR, but Okta issues only the scopes you have granted and silently omits the rest (per Okta's documented downscoping behavior). Grant `okta.users.manage` only when you enable DSR; otherwise it is never issued and the app stays strictly read-only.
- **Fully auditable.** Every call the app makes appears in your System Log, and you can revoke the app or any scope at any time.

⚠️ Exception - DSR / right-to-erasure.

The read-only guarantee above holds only while you have not granted a write scope. If you separately enable DSR, you grant okta.users.manage

, which lets the app deprovision and delete Okta users (DSR issues

DELETE /api/v1/users/{id}

) - this is intentional and required for erasure. Combined with Super Administrator, that delete capability spans every user in the org. So grant okta.users.manage only if you actually run DSR, and treat the DSR trust decision separately from Vendor Discovery. Discovery-only connections should never have this scope granted.

**Not running Vendor Discovery?** Then the app only needs **Read-Only Administrator**. Everything else in this guide is the same.

#### **Before you start**

1. An Okta account with **Super Administrator** access. An Application Administrator may be able to create the API Services app, but Super Administrator access is needed to grant Okta API scopes and assign an admin role to the app.
2. Access to the Relyance configuration portal for your organization ( **Settings → Integrations → Okta** ).

#### **Step 1 - Create an API Services app in Okta**

*Okta Admin Console → Applications*

1. In the Okta Admin Console, navigate to **Applications › Applications** and click **Create App Integration**.
2. Select **API Services** and click **Next**.
3. Name the app - e.g. **Relyance Integration**- and click **Save**.
4. On the app's **General** tab, scroll to **Client Credentials** and change **Client authentication** to **Public key / Private key**.
5. Under **PUBLIC KEYS**, select **Use a URL to fetch public keys / JWKS URL**. Leave the URL field blank for now - you'll fill it in after Step 4.
6. Click **Save**. Then copy the **Client ID** shown at the top of the General tab - you'll need it in Step 4.

#### **Step 2 - Grant OAuth scopes to the app**

*Okta Admin Console → your app → Okta API Scopes*

Navigate to the **Okta API Scopes** tab on the app and click **Grant** for each scope below.

| Scope | Purpose | Required for |
| --- | --- | --- |
| `okta.users.read` | Read user profiles | All |
| `okta.groups.read` | Read group memberships | All |
| `okta.logs.read` | Read system logs | All |
| `okta.apps.read` | Read application catalog | All |
| `okta.idps.read` | Read identity providers | All |
| `okta.authenticators.read` | Read authenticator configs | All |
| `okta.oauthIntegrations.read` | Read OAuth app integrations | All |
| `okta.orgs.read` | Read org settings | All |
| `okta.users.manage` | Fulfill data subject requests (DSAR / right to erasure). Requires user-management authority in the app's admin role. | **DSR only** |

✅ Scopes are necessary but not sufficient.

Okta also checks the service app's admin role and resource permissions on each Management API call. Complete the next step before testing the connection.

#### **Step 3 - Assign an admin role to the app**

*Okta Admin Console → your app → Admin roles*

Grant the service app an admin role so Okta authorizes the resources Relyance reads. OAuth scopes let the app request API access; the admin role determines which resources it can reach. Assign **Super Administrator** - the integration's default Vendor Discovery requires it, and it stays read-only because the app holds only read scopes (see "Which admin role do you need?" above).

1. In Okta, open the app you created for Relyance.
2. Go to the **Admin roles** tab and click **Edit assignments** or **Add assignment**.
3. Assign **Super Administrator** and save.

| Role | When to use | Notes |
| --- | --- | --- |
| **Super Administrator** | Default - required for Vendor Discovery | The standard role for this integration, and the only one that authorizes the API Service Integrations endpoint. It grants no write access on its own - the app can only do what its granted scopes allow, which for discovery are read-only. Verify granted scopes on the Okta API Scopes tab. |
| **Read-Only Administrator** | Only if Vendor Discovery is disabled | Covers users, groups, system logs, applications, identity providers, authenticators, and org settings. Does not cover Vendor Discovery - the API Service Integrations endpoint returns 403 / E0000006 under this role. |
| **+ User-management** | Add on only for DSR / right-to-erasure | DSR also needs user-management authority for the affected users, plus the  `okta.users.manage`  OAuth scope. Not needed for discovery. |

⚠️ If this step is skipped, the access token may still contain scopes like okta.logs.read or okta.groups.read

, but Okta can return

E0000006

/

You do not have permission to perform the requested action

.

✅ After saving role assignments, retry

Test Connection

. You do not need to rotate keys or recreate the app; Relyance can request a fresh access token on the next test if needed.

#### **Step 4 - Configure the connection in Relyance**

*Settings → Integrations → Okta → Machine to Machine*

1. In Relyance, go to **Settings › Integrations › Okta** and click **Add connection**.
2. Select **Machine to Machine** (marked *Recommended* ) as the authentication method.
3. Fill in the two fields:
    - **Client ID** - from the app's General tab in Okta (Step 1).
    - **Subdomain** - your Okta subdomain, i.e. the prefix before `.okta.com` (e.g. `acme` for `acme.okta.com` ).

✅ Click Save.

Relyance will generate a cryptographic key pair on its end. The first save attempt may fail authentication - that's expected, because Okta has not been given the JWKS URL yet. The connection page will now show a Public Key URL panel, which you'll use in the next step.

#### **Step 5 - Paste the Public Key URL into Okta**

*Back to Okta Admin Console → your app → General*

1. In Relyance, copy the URL from the **Public Key URL** panel on the connection page. It will look like: `https://api.relyance.ai/v1/integrations/okta/<your-tenant>/<connection-id>/jwks`
2. In Okta, go back to your app's **General** tab → **PUBLIC KEYS** section.
3. In the **JWKS URL** field (visible because you selected "Use a URL to fetch public keys" in Step 1), paste the URL you just copied.
4. Click **Save**. Okta will fetch the JWKS from Relyance.

✅ Okta caches the JWKS for around an hour.

Subsequent key rotations on Relyance's side are picked up automatically - you won't need to revisit this step.

#### **Step 6 - Test the connection**

*Back to Relyance*

1. In Relyance, return to the Okta connection page and click **Save** (or **Test Connection** ) again.
2. Relyance signs a token request with its private key, Okta verifies it against the JWKS it fetched from your URL, and the connection succeeds if scopes and admin-role permissions are correctly assigned. ✅

#### **How the token request is signed**

Worth knowing before you debug one, because it explains most failures:

- The grant is **client credentials with `private_key_jwt`** — there is no client
  secret for this method, which is why the app's client authentication must be
  **Public key / Private key**.
- Every request is also **DPoP-bound** (RFC 9449). Relyance always sends a DPoP
  proof and answers Okta's `use_dpop_nonce` challenge automatically; there is no
  non-DPoP fallback, so an app that cannot accept a DPoP-bound token request will
  never connect.
- **One EC P-256 key** signs both the client assertion and the DPoP proof.
  Relyance generates and holds it, and publishes it at the **Public Key URL** —
  which is why that URL is read-only in Relyance and why Okta must fetch the JWKS
  from it rather than being given a static key.
- Proofs carry an `iat` timestamp and are short-lived, so **significant clock
  skew** on either side shows up as a rejected proof rather than as a scope or
  role error.

#### **After it connects: confirm you got the whole picture**

Okta grants only the scopes the app is authorised for and **silently omits the
rest** — a connection can succeed with a partial grant, so verify rather than
assume:

1. Open the connection and check **Scopes and Permissions** lists all nine scopes
   from Step 2.
2. In **Last Scan Status**, confirm the resource groups you expect (users, groups,
   system logs, applications) completed. A group that scanned zero
   resources usually means its scope was requested but not granted.
3. If DSR deletion is in scope, confirm `okta.users.manage` is present. Granting
   only the read scopes keeps the app read-only regardless of its admin role.

#### **🔧 Troubleshooting**

| Error | Likely cause | Fix |
| --- | --- | --- |
| `invalid_client`  / The client_assertion JWT kid is invalid | Okta hasn't (re)fetched the JWKS yet, or the URL was pasted into the wrong app | Confirm the JWKS URL in the right app, save Okta config, wait ~30 seconds, retry |
| `unauthorized_client` | App's client authentication is not set to Public key / Private key | In Okta General tab → Client Credentials, switch to Public key / Private key |
| `E0000006`  / You do not have permission | The service app has scopes, but no admin role or an admin role/resource set that does not cover the requested resource | Assign Super Administrator (the default - Vendor Discovery requires it). Read-Only Administrator is sufficient only if Vendor Discovery is disabled. |
| `E0000006`  on  `/integrations/api/v1/api-services`  only | Vendor Discovery endpoint reached without Super Administrator (no lesser role can authorize it) | Assign Super Administrator to the service app, then retry - no key rotation needed. As long as you grant only read scopes, the app stays read-only despite the role (verify on the Okta API Scopes tab). If you don't want Super Administrator, disable Vendor Discovery; all other areas keep working. |
| `insufficient_scope` | One or more required OAuth scopes were not granted | Okta API Scopes tab → grant any missing scopes from the table in Step 2 |
| Okta can't reach the JWKS URL | URL typo, or Okta blocked the domain | Open the URL in a browser - it should return JSON with a  `keys`  array |
| Works now, stops working later | Okta app deactivated, or the integration removed in Relyance | Check the app's status in Okta Admin Console; verify the Relyance connection still exists |
| `invalid_dpop_proof` | The DPoP proof was rejected — most often clock skew between Relyance and Okta, or an app that cannot accept a DPoP-bound token request | Check the Okta app accepts DPoP for token requests; there is no non-DPoP fallback |
| Connected, but a resource group scanned zero items | The scope for that area was requested but not granted — Okta omits ungranted scopes silently rather than failing | Compare **Scopes and Permissions** on the connection against the nine scopes in Step 2, then grant the missing one |
| Sandbox tenant will not connect | The **Sandbox Oauth2** method takes **Okta domain** (the full domain), not the **Okta Subdomain** the production methods use | Enter the full domain for the sandbox method |

### **Part 2 - OAuth2 Authentication (OIDC Web App)**

Use this method only if you cannot create an API Services app. It requires an **Okta Subdomain**, **Client ID**, **Client Secret**, and an interactive **OAuth2** sign-in.

#### **In Okta**

You'll need to first create an OAuth 2.0 application.

1. Login to your Okta account.
2. Click on the **Applications** menu, and navigate to and click on the **Applications** menu item.
3. Click on **Create App Integration**.
4. Select Sign-In Method = **OIDC** and Application Type = **Web Application**, and click **Next**.
5. Enter the following: **App Integration Name**: Relyance (or whatever you would like it to be) **Logo** (optional): Attached to this guide. **Sign-in Redirect URIs**: [https://root.relyance.ai/api/oauth2](https://root.relyance.ai/api/oauth2) **Assignments - Controlled Access**: Skip group assignment for now.
6. Click on **Save**.
7. Use the copy icon buttons to copy and save the **Client ID** and **Client Secret** to be used in Relyance AI later.
8. Click on the **Edit** button in the following screen.
9. Enable the **Refresh Token** checkbox. The refresh token allows

![mceclip1.png](https://assets.relyanceuat.xyz/images/docs/34594768882317/34594814553229.png)

![mceclip2.png](https://assets.relyanceuat.xyz/images/docs/34594768882317/34594814554637.png)

![mceclip3.png](https://assets.relyanceuat.xyz/images/docs/34594768882317/34594768872717.png)

![mceclip0.png](https://assets.relyanceuat.xyz/images/docs/34594768882317/34594814551053.png) offline_access

. It is a long-lived token that can be used to obtain a new access token when the original one expires.
10. Scroll down, and click on **Save**.
11. Click on the **Assignments** tab.
12. Click on the **Assign** dropdown and select **Assign to People**.
13. Assign a user account to be used in the OAuth authentication for the integration (you may wish to create a new service account for this instead of attaching it to a named user) by clicking **Assign** beside their name, then **Save and Go Back**. If authentication is being done using a service account, ensure the service account is assigned to the Okta application and has Read-only Administrator access.
14. Click on **Done**.
15. Click on the **Okta API Scopes** tab.
16. Grant the following scopes:
    - `okta.logs.read`
    - `okta.users.read`
    - `okta.apps.read`
    - `okta.idps.read`
    - `okta.authenticators.read`
    - `okta.oauthIntegrations.read`
    - `okta.groups.read`
    - `okta.orgs.read`
17. If using a service account, you will need to grant the account Read-only Administrator access. Navigate to **Security › Administrators**, select the service account, and provide it with the **Read-only Administrator** role.

#### **In the Relyance AI application**

![Screenshot](https://assets.relyanceuat.xyz/images/docs/34594768882317/34594814566285.png)
![Screenshot](https://assets.relyanceuat.xyz/images/docs/34594768882317/34594814567309.png)
1. Login to your Relyance AI account.
2. Navigate to the **Settings** (bottom-left corner).
3. Select **Integrations**.
4. Search and locate the **Okta** integration card and click on it.
5. Click on the **Add Connection** button on the top right.
6. Provide a meaningful name for the integration and click on the **Add** button.
7. In the **Overview** section, select the integration features you wish to enable, review the **Scope and Permission** and **Endpoint** details, and click on **Continue**. For more details, see

![Screenshot](https://assets.relyanceuat.xyz/images/docs/34594768882317/34594814569869.png)
![Screenshot](https://assets.relyanceuat.xyz/images/docs/34594768882317/34594768876813.png)
![Screenshot](https://assets.relyanceuat.xyz/images/docs/34594768882317/34594768877197.png)

Integration Features

.
8. In the **Connection** section, provide the appropriate values and click on **Continue**:
    1. **Connection Name**: Updates the integration named in Step 6. If you have multiple integrations for the same vendor, assign distinct names to streamline filtering by **Discovery Source** across the **Inventory**, **Visual Maps**, **Assets**, and **Data Flow Analysis** pages.
    2. **Rescan Frequency**: Configures how often Relyance executes scans against this vendor connection.
    3. **Business Atlas Associations** (required): the business entities or products that newly discovered third parties, services and assets from this integration are attributed to. Choose at least one from the **Select Associations** dropdown — the wizard will not advance past this step without one. For more details, see [Business Atlas](/docs/other-settings/business-atlas-overview/).4. For more details, see

Business Atlas

.
9. In the **Authentication** section, choose one of the modes below. **Machine to Machine** is recommended for new connections (see [Part 1](#m2m-method) ).
    1. **OAuth2 / App Token** - Client ID, Client Secret, Okta Subdomain
    2. **Custom** - API Token, Subdomain
    3. **Sandbox OAuth2** - Client ID, Client Secret, Okta domain (the full domain, not just the subdomain prefix)
    4. **Okta Classic** - API Token, Subdomain
10. This step applies only if the **Data Inspection** check box was enabled in Step 7. In the Data Inspection section, provide the appropriate values and click on **Continue**:
    - **Minimum Confidence Level**: Adjusts the sensitivity of Data Inspection. Lower likelihoods (e.g. *unlikely* ) offer more coverage but may produce false positives; higher sensitivity (e.g. *very likely* ) provides greater accuracy but less coverage.
11. Review the configuration summary from the **Completion** section and click on **Finish**.
12. Confirm the integration Status reflects **Connected**.

![Screenshot](https://assets.relyanceuat.xyz/images/docs/34594768882317/34594768874893.png)

![Screenshot](https://assets.relyanceuat.xyz/images/docs/34594768882317/34594814572173.png)

![Screenshot](https://assets.relyanceuat.xyz/images/docs/34594768882317/34594768878349.png)

### Check the connection after setup

Open the connection to see what Relyance is authorised for and what the last scan
actually covered — a missing scope or a partial grant shows up here rather than as
an error:

![The connections list for this integration](https://assets.relyanceuat.xyz/images/docs/ui/okta/02-connections.png)

<!-- terraform-examples:begin (generated from the integration catalog; do not hand-edit) -->

## Manage this integration with Terraform

Connections for this integration can be managed as code with the [Relyance Terraform provider](https://registry.terraform.io/providers/Relyance/relyance/latest). Non-secret fields go in `auth.params`; secret fields go in `auth.secrets_wo`, which is write-only — never stored in Terraform state. Rotate secrets by bumping `auth.secrets_wo_version`.

### OAuth client credentials (M2M)

```hcl
resource "relyance_integration_connection" "okta_0" {
  vendor = "okta"
  name   = "<your connection name>"

  auth = {
    method = "oauth-client-credentials"
    params = {
      client_id = "<client_id>"
      subdomain = "<subdomain>"
      public_key_url = "<public_key_url>"
      data_storage_location = "us"
    }
  }

  scans = { "data-inspection" = { enabled = true } }
}
```

### OAuth (browser authorization)

The **OAuth (browser authorization)** method uses a browser authorization flow, so the connection is created in the Relyance app. Manage it in Terraform afterwards by importing it (`terraform import relyance_integration_connection.example okta/<connection_id>`) or reading it with the `relyance_integration_connection` data source.

### API token

```hcl
resource "relyance_integration_connection" "okta_2" {
  vendor = "okta"
  name   = "<your connection name>"

  auth = {
    method = "api-token"
    params = {
      subdomain = "<subdomain>"
      data_storage_location = "us"
    }
    # Secret fields are write-only: sent to Relyance, never stored in state.
    secrets_wo = {
      api_token = var.okta_api_token
    }
    secrets_wo_version = 1
  }

  scans = { "data-inspection" = { enabled = true } }
}
```

### OAuth (browser authorization) — Sandbox Oauth2

The **OAuth (browser authorization) — Sandbox Oauth2** method uses a browser authorization flow, so the connection is created in the Relyance app. Manage it in Terraform afterwards by importing it (`terraform import relyance_integration_connection.example okta/<connection_id>`) or reading it with the `relyance_integration_connection` data source.

### API token — Okta Classic

```hcl
resource "relyance_integration_connection" "okta_4" {
  vendor = "okta"
  name   = "<your connection name>"

  auth = {
    method = "api-token-okta-classic"
    params = {
      subdomain = "<subdomain>"
      data_storage_location = "us"
    }
    # Secret fields are write-only: sent to Relyance, never stored in state.
    secrets_wo = {
      api_token = var.okta_api_token
    }
    secrets_wo_version = 1
  }

  scans = { "data-inspection" = { enabled = true } }
}
```

<!-- terraform-examples:end -->
