Okta API Bearer Authentication
Authenticate programmatic access to the Halo API using OAuth 2.0 Bearer tokens issued by Okta.
Overview
Halo exposes a REST API through the API-Access service. All API requests must include a valid Bearer token in the Authorization header. Tokens are obtained from Okta using the OAuth 2.0 Client Credentials flow (machine-to-machine).
This guide walks through configuring Okta as the Identity Provider for the Halo API-Access service, then testing the integration with Bearer token authentication.
Prerequisites
- A running Halo deployment with API-Access service configured
- An Okta organization with admin access
- Okta authorization server configured for Halo (see Okta Authorization Server)
- Network access from the API client to both Okta and the Halo API endpoint
Setup
Register an API client in Okta
Create a machine-to-machine application
- Login to the Okta Admin Console.
- Navigate to
Applications->Applications->Create App Integration->API Services.

- Provide an appropriate App integration name: e.g.
Glasswall Halo API

Disable DPoP (proof of possession)
- Navigate to the application's
Generaltab ->General Settingssection -> clickEdit. - Under Proof of possession, untick
Require Demonstrating Proof of Possession (DPoP) header in token requests. - Save.
Note: Okta enables DPoP by default for API Services applications. DPoP binds tokens to the client's cryptographic key, preventing token replay attacks. However, the Halo API-Access service does not currently support DPoP token validation, so this must be disabled for Bearer token authentication to work.

Configure client credentials
-
Note the
Client IDand generate aClient Secret.export CLIENT_ID="<your-client-id>"
export CLIENT_SECRET="<your-client-secret>"
Security Note: Store client secrets securely using a secrets manager (e.g., Azure KeyVault). Never commit secrets to source control.

Configure roles (optional)
- If your API client needs write access to license management endpoints, ensure the
roleclaim is configured on the authorization server with theAdminvalue mapped to your client (see API Roles).
Note: Without the
roleclaim, the API defaults to theUserrole. See API Roles to Action Mapping for details on what each role can access.
Note issuer URI and audience
-
From the authorization server configured in the prerequisite step:
export OKTA_ISSUER_URI="https://<your-okta-domain>/oauth2/<authorization-server-id>"
export VALID_AUDIENCE="api://halo"
Add access policy for API client
- Navigate to the authorization server's
Access Policiestab (Security->API-> select your authorization server ->Access Policies). - Add a new access policy:
- Name: e.g.
API Client Access - Description: e.g.
Access policy for Halo API clients - Assign to: the API Services client created above (search by application name
glasswall-halo-apifrom step 2)
- Name: e.g.
- Add a rule:
- Name: e.g.
Allow API Clients - Grant type:
Client Credentials - Leave other settings as defaults or adjust token lifetime as needed.
- Name: e.g.
Note: Access policies for the Client Credentials grant control which clients (by
client_id) can request tokens. Group membership does not apply because there is no user context in this flow. Secure access to the API by restricting who has theclient_idandclient_secretvia your secrets manager.

Configure API-Access service for Okta
The cdrplatform-api-access Helm chart must be configured to validate Bearer tokens issued by your Okta authorization server. The chart exposes authentication settings under the configuration values key.
Deploy the configuration
Note: The
AuthorityandValidIssuermust both be set to the Okta issuer URI. The API-Access service usesAuthorityto discover the JWKS (JSON Web Key Set) for token signature validation, andValidIssuerto verify theissclaim in the token.
export HALO_DOMAIN=<your-halo-domain>
helm upgrade --install cdrplatform-api-access cdrplatform-api-access --reuse-values \
--set configuration.AuthenticationScheme="Bearer" \
--set configuration.Authentication__Schemes__Bearer__Authority="${OKTA_ISSUER_URI:?}" \
--set configuration.Authentication__Schemes__Bearer__ValidIssuer="${OKTA_ISSUER_URI:?}" \
--set configuration.Authentication__Schemes__Bearer__ValidAudiences__0="${VALID_AUDIENCE:?}" \
--set strategy.type="Recreate" \
--set ingress.enabled=true \
--set ingress.tls.enabled=true \
--set ingress.tls.domain="${HALO_DOMAIN:?}" \
--atomic
Multiple audiences (optional)
If multiple API clients or applications need to access the Halo API with different audiences, add additional ValidAudiences entries:
--set configuration.Authentication__Schemes__Bearer__ValidAudiences__0="api://halo-api" \
--set configuration.Authentication__Schemes__Bearer__ValidAudiences__1="<second-client-id>" \
--set configuration.Authentication__Schemes__Bearer__ValidAudiences__2="<third-client-id>"
Halo API usage
Obtain a bearer token
Client Credentials flow (machine-to-machine)
Use this flow for automated systems, CI/CD pipelines, and service-to-service integrations where no user interaction is required.
export TOKEN_ENDPOINT="${OKTA_ISSUER_URI}/v1/token"
Request a token:
curl -s -X POST "${TOKEN_ENDPOINT}" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=${CLIENT_ID}" \
-d "client_secret=${CLIENT_SECRET}" \
-d "scope=halo.api"
Example response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6Ikp...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "halo.api"
}
Extract the token:
export ACCESS_TOKEN=$(curl -s -X POST "${TOKEN_ENDPOINT}" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=${CLIENT_ID}" \
-d "client_secret=${CLIENT_SECRET}" \
-d "scope=halo.api" | jq -r '.access_token')
Authenticate API requests
Include the Bearer token in the Authorization header of every API request:
export HALO_API_URL="https://<your-halo-domain>"
Verify authentication
Test with a protected endpoint. A request without a token should return 401, and a request with a valid token should return 200:
# Without token - expect 401
curl -s -o /dev/null -w "%{http_code}" \
"${HALO_API_URL}/api/v1/policies"
# With token - expect 200
curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
"${HALO_API_URL}/api/v1/policies"
Example: Submit a file for processing
curl -s -X POST \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-F "file=@/path/to/document.pdf" \
"${HALO_API_URL}/api/v1/rebuild/file" \
--output rebuilt_document.pdf
Troubleshooting
| Symptom | Cause | Resolution |
|---|---|---|
401 Unauthorized | Token expired or invalid | Request a new token. Verify CLIENT_ID and CLIENT_SECRET are correct. |
401 Unauthorized with valid token | Audience mismatch | Ensure ValidAudiences in API-Access matches the aud claim in the token. |
403 Forbidden with AdminRoleMissing | Missing Admin role in token | Configure the role claim on the authorization server (see API Roles). Required for license management write operations. |
| Token request fails | Incorrect token endpoint URL | Verify the authorization server ID and Okta domain. Check /.well-known/openid-configuration. |
invalid_scope error | OIDC scopes used with Client Credentials | Use the custom halo.api scope instead of openid, profile, or email. |
invalid_dpop_proof error | DPoP not disabled | Disable DPoP on the API Services application (see Disable DPoP). |
| Network timeout | Firewall or proxy blocking | Ensure the API client can reach both the Okta token endpoint and the Halo API endpoint. |