Skip to content

Okta Integration

Shoehorn supports Okta as both an authentication provider (OIDC) and an orgdata provider (Okta Management API for user/team sync). This guide walks through the exact Okta app configuration, Helm values, and troubleshooting mapped to the error codes Shoehorn emits on /auth-error.

  • An Okta organization with admin access
  • Shoehorn deployed via the Helm chart: see Helm deployment
  • Your Shoehorn domain (e.g. shoehorn.example.com) reachable from browsers that will sign in

In the Okta admin console, go to Applications → Create App Integration and select:

  • Sign-in method: OIDC – OpenID Connect
  • Application type: Web Application

Then configure the following fields exactly: the non-default values matter:

FieldValue
App nameShoehorn
Client authenticationClient secret
Proof of possession (PKCE)Require PKCE as additional verification
Client secretsGenerate one and copy it: you will store this as OKTA_CLIENT_SECRET
Grant typeAuthorization Code    ☑ Refresh Token
Refresh token behaviorRotate token after every use
Sign-in redirect URIshttps://<your-domain>/api/v1/auth/callback
Sign-out redirect URIshttps://<your-domain>
Login initiated byApp Only
Controlled accessAssign to the Okta groups that should have Shoehorn access

Save, then copy the Client ID: you will store this as OKTA_CLIENT_ID.

If users cannot sign in after completing the rest of this guide, check Applications → Shoehorn → Sign On → Sign On Policy. A permissive starting rule is:

  • Catch-all rule: Access = Allowed, Authentication = Any 2 factor types

Tighten this once signing in works end-to-end.

Okta does not include group membership in the ID token by default. Without this claim, Shoehorn cannot map your Okta groups to Shoehorn roles: logins will land on /auth-error?code=GROUPS_CLAIM_MISSING.

  1. Security → API → Authorization Servers: select the default authorization server (or your custom one)
  2. Claims tab → Add Claim
FieldValue
Namegroups
Include in token typeID Token, Always
Value typeGroups
FilterMatches regex, value .*
Include inAny scope

Save. Use Okta’s Token Preview (same authorization server page) to confirm the token now carries a groups array.

Required for production. Without it, every login fails with INVALID_CLAIMS and the user lands on /auth-error. Shoehorn uses this claim as the tenant id for every request; if it’s missing, the request is rejected.

  1. Go to Security → API → Authorization Servers and select the same authorization server you used for the groups claim
  2. Open the Claims tab and click Add Claim
FieldValue
Nameokta_org
Include in token typeID Token, Always
Value typeExpression
ValueYour Okta org id, or an expression that returns it. A literal like "acme" works for a single-tenant deploy. For per-user attribution use something like user.organization.
Include inAny scope

The value is an opaque tenant id, so pick something stable. Once a user has signed in with one value, switching to another would orphan their existing data.

Use Okta’s Token Preview to confirm the ID token now carries okta_org.

4. (Optional) Create an API Token for Orgdata Sync

Section titled “4. (Optional) Create an API Token for Orgdata Sync”

If you want Shoehorn to sync users and teams proactively from Okta: rather than only resolving memberships on individual logins:

  1. Security → API → TokensCreate Token
  2. Name: Shoehorn Orgdata Sync
  3. Copy the token: you will store this as OKTA_API_TOKEN

The token runs as the creating user. If that user loses read access to users or groups, sync breaks silently: prefer creating the token as a dedicated service account.

The chart’s examples/values-okta.yaml is the canonical reference. It stays in sync with the chart schema. Copy it and fill in the CHANGE_ME markers.

The Okta-specific keys you’ll set:

PathSource
auth.provider: oktarequired
auth.okta.domainstep 1 — bare host, no scheme
auth.okta.clientIdstep 1
auth.okta.clientSecretRef.keythe key inside your Secret that holds the client secret
auth.okta.apiTokenSecretRef.keythe key holding the Management API token, only if you enable orgdata sync (step 4)
auth.orgdata.enabled: trueto sync users and teams from Okta
rbac.roleAssignment.tenantAdmin.user or .groupa single email or Okta group; the matching account gets tenant:admin on first sign-in while no roles exist yet

For the secret keys (okta_client_secret, okta_api_token, session/JWT keys, postgres/valkey/meili keys), follow the secret-creation command in the chart’s README or values-okta.yaml header. Postgres and DB passwords must be URL-safe: use openssl rand -hex, not -base64.

The chart’s schema enforces that auth.okta.domain and auth.okta.clientId are set whenever auth.provider: okta. helm install refuses with a minLength: got 0, want 1 error if either is missing, and rejects the example placeholders (your-org.okta.com, 0oaXXXXXXXXXXXXXX) with a field-named error.

Once installed, a browser login flows end-to-end:

  1. User visits https://shoehorn.example.com → Shoehorn redirects to Okta
  2. User authenticates in Okta → Okta redirects back to /api/v1/auth/callback with an ID token
  3. Shoehorn verifies the ID token signature against Okta’s JWKS (fetched from auth.okta.domain), then validates issuer and audience
  4. If the user doesn’t exist in Shoehorn, it JIT-provisions them using sub, email, and groups claims
  5. Shoehorn resolves roles:
    • First it checks rbac.roleAssignment.tenantAdmin.user/.group against the user’s email/Okta groups (bootstrap path, only while the roles table is empty)
    • Then it applies Shoehorn-side group → role mappings configured under Admin → Directory → Teams
  6. If auth.orgdata.enabled, a targeted sync fires for the new user so team-based mappings resolve immediately rather than waiting for the next scheduled sync

Logout follows the OIDC RP-initiated logout spec:

  1. User clicks Sign out → browser hits /api/v1/auth/logout
  2. Shoehorn clears its session, then discovers Okta’s end_session_endpoint and redirects the browser there
  3. Okta clears its own session and redirects back to your Shoehorn domain
  4. The user lands signed-out; re-opening Shoehorn prompts Okta login afresh

When login fails, Shoehorn redirects to /auth-error?code=<CODE>. The code tells you where to look:

Symptom / codeLikely causeFix
code=GROUPS_CLAIM_MISSINGGroups claim not configured on the authorization serverRepeat step 2. Use Okta’s Token Preview to confirm the claim appears.
code=INVALID_CLAIMSokta_org claim missing or emptyRepeat step 3. Use Token Preview to confirm okta_org is set.
code=NO_ROLESUser has Okta groups, but none map to a Shoehorn roleConfigure a mapping under Admin → Directory → Teams, or set rbac.roleAssignment.tenantAdmin.user/.group for the bootstrap path (only fires while no roles exist yet).
code=INVALID_DOMAINOKTA_DOMAIN is the full URL, not the bare domain, or the token endpoint is unreachableUse your-org.okta.com, no scheme, no slash. Check your cluster’s egress network path to Okta.
code=JWKS_UNAVAILABLEShoehorn couldn’t fetch Okta’s signing keysUsually transient. If it persists, verify OKTA_DOMAIN and your cluster’s egress network path to Okta.
Login redirects back to Okta in a loopSession cookie expiring at epoch 0 (very old Shoehorn build)Upgrade to Shoehorn ≥ v0.4.3.
Test Connection fails with “invalid API token”Token was revoked or the creating user was deactivatedRecreate the token as a dedicated service account.
Test Connection fails with “host unreachable”OKTA_DOMAIN contains the full URLUse your-org.okta.com, no scheme, no slash.
After logout, user is auto-signed back inend_session_endpoint not called: misconfigured appConfirm Sign-out redirect URIs in the Okta app includes https://<your-domain>.

When debugging claim issues, paste the ID token into jwt.ms and check:

  • iss matches https://<domain> (or https://<domain>/oauth2/<authz-server-id> if you use a custom authorization server)
  • aud equals your OKTA_CLIENT_ID
  • groups contains the expected array
  • sub is the stable Okta user ID (e.g. 00uXXXXXXXXXXXXXX)

Once deployed, visit Admin → Integrations → External → Okta in Shoehorn. The Test Connection button calls the Okta Management API with your stored token and returns a green banner on success or the exact upstream error on failure. It’s the fastest way to verify OKTA_API_TOKEN before investigating deeper.

Shoehorn lets you mix auth and orgdata providers:

AuthenticationOrgdataSupported
OktaOkta✅ Most common
OktaZitadel
ZitadelOkta
Oktanone (orgdata disabled)✅ Relies on rbac.roleAssignment.tenantAdmin + in-app role assignment