Skip to main content
Version: v3.8.0

External OIDC Authentication

Appendix - not the default path

Datasance PoT v3.8.0 ships with Embedded OIDC Authentication enabled by default. Use this page when your organization requires a dedicated identity provider (Okta, Microsoft Entra ID, Keycloak, or any OIDC-compliant IdP).

Agent routes

Edgelet agent routes (/api/v3/agent/*) use fog-token auth. OIDC does not apply to field agents.

External mode sets AUTH_MODE=external. Controller uses one confidential OIDC client for browser (EdgeOps Console) and CLI (potctl) authentication. Access and refresh tokens are issued by the IdP. Controller validates access JWTs via the issuer JWKS and maps claims to RBAC.

For default group names, claim order, and per-IdP recipes, see External OIDC providers.

When to use external OIDC​

ScenarioUse external OIDC
Corporate SSO requiredYes
Greenfield lab or quick startNo. Use Embedded OIDC
IdP-managed MFA and password policyYes
No external IdP availableNo

Minimal Control Plane YAML​

Set auth.mode: external and point to your IdP issuer. potctl maps these fields to Controller environment variables at deploy time.

auth:
mode: external
issuerUrl: https://auth.example.com/realms/myrealm
client:
id: pot-controller
secret: "<confidential-client-secret>"
controller:
publicUrl: https://controller.example.com:51121
consoleUrl: https://console.example.com:8008

Local Docker installs can set auth.insecureAllowHttp: true when publicUrl uses http://. Production deploys should use HTTPS and valid TLS certificates.

Full YAML field reference: Control Plane YAML. Runtime env mapping: Controller configuration.

Do not set legacy KC_*, auth.realm, or auth.realmKey.

Controller environment (minimum)​

VariableRequiredExample
AUTH_MODEYesexternal
OIDC_ISSUER_URLYesFull issuer URL (not the IdP homepage)
OIDC_CLIENT_IDYespot-controller
OIDC_CLIENT_SECRETYesConfidential client secret
CONTROLLER_PUBLIC_URLYeshttps://controller.example.com
CONSOLE_URLYes (browser login)https://console.example.com
TRUST_PROXYWhen TLS terminates at ingresstrue
AUTH_SESSION_STORE_TYPEHA + browser logindatabase (mysql/postgres)
AUTH_INSECURE_ALLOW_HTTPDevelopment onlytrue when using http://localhost:*

Discovery uses GET {OIDC_ISSUER_URL}/.well-known/openid-configuration.

Flows and endpoints​

Use caseGrant / flowController endpoint
Browser (EdgeOps Console)Authorization code + PKCE S256GET /api/v3/user/oauth/authorize → IdP → GET /api/v3/user/oauth/callback
CLI (potctl)Resource owner password (direct access)POST /api/v3/user/login
Session refreshRefresh tokenPOST /api/v3/user/refresh
ProfileBearer access token (+ UserInfo)GET /api/v3/user/profile
User APIs and operator WebSocketsBearer access JWT/api/v3/* except agent routes

Browser login uses the OAuth BFF. The Console does not POST passwords directly to the IdP. Legacy browser POST /user/login was removed on platform train v3.8.0.

In external mode there is no local AuthUsers row for IdP users. MFA and password policy are owned by the IdP.

IdP client - required settings​

Client type​

Create one confidential Web client for Controller. Public SPA or native clients are not supported for the OAuth BFF.

SettingRequired value
ProtocolOpenID Connect
Client authenticationOn (client_secret_basic or client_secret_post)
PKCES256 required or allowed
Implicit / hybrid / device codeOff

Enabled grant types / flows​

FlowRequired forNotes
Standard flow (authorization code)Browser Sign inRequired
Refresh token (offline_access)Console/CLI refreshRequired for POST /api/v3/user/refresh
Direct access grants (ROPC)CLI POST /user/loginRequired if potctl uses password login against the IdP
Implicit flow—Off

If your organization disabled ROPC (common on Microsoft Entra ID), browser login still works; CLI password login may not.

Redirect URIs​

Register exactly:

{CONTROLLER_PUBLIC_URL}/api/v3/user/oauth/callback

Avoid overly broad wildcards in production.

Web origins (CORS)​

If the Console calls the Controller API from the browser, allow {CONSOLE_URL}.

Scopes​

Controller sends this scope string on authorize (not configurable):

openid profile email groups offline_access

Every scope must be assigned to the client as default and/or optional client scope. The scope name must be groups, not group.

Access token requirements​

Controller validates the access token, not the ID token, on API calls. Groups and roles on the ID token alone are ignored for authorization.

The access token must:

  1. Be a JWT (opaque tokens fail).
  2. Have iss equal to the discovered issuer.
  3. Have aud equal to OIDC_CLIENT_ID (string or array containing it).
  4. Carry RBAC claims on the access token (see External OIDC providers).
  5. Not be a refresh token (token_use must not be refresh).

If aud is Microsoft Graph, Okta api://default, or any API identifier that is not the client ID, Bearer validation fails with 401.

System roles (summary)​

If the access token contains a group or role named admin, sre, developer, or viewer (case-insensitive), Controller grants that system role without a RoleBinding. Do not assign agent-admin or microservice to human IdP users.

Full mapping, custom RoleBindings, and IdP recipes: External OIDC providers.

Issuer discovery (minimum metadata)​

EndpointUsed for
authorization_endpointBrowser OAuth BFF
token_endpointCode exchange, ROPC, refresh
jwks_uriBearer JWT validation
userinfo_endpointGET /user/profile in external mode
revocation_endpointOptional; best-effort logout

MFA and forced password change​

  • Browser: enforced by the IdP during authorize.
  • CLI: IdP password-grant policy applies.
  • Controller does not run embedded interaction UI in external mode.

Verification​

Browser​

  1. EdgeOps Console Sign in → IdP login (no invalid_scope or PKCE errors).
  2. Callback → {CONSOLE_URL}/login#accessToken=...&refreshToken=....
  3. Decode the access token: aud is OIDC_CLIENT_ID; iss matches the issuer; roles/groups claim contains a system role name when expected.
  4. GET /api/v3/user/profile with Authorization: Bearer <accessToken> → 200.

CLI​

curl -sS -X POST '{CONTROLLER_PUBLIC_URL}/api/v3/user/login' \
-H 'Content-Type: application/json' \
-d '{"email":"<login-id>","password":"<pass>","totp":""}'

email is the IdP login identifier (username or UPN). Expect { "accessToken", "refreshToken" }.

Refresh​

curl -sS -X POST '{CONTROLLER_PUBLIC_URL}/api/v3/user/refresh' \
-H 'Content-Type: application/json' \
-d '{"refreshToken":"<refresh>"}'

Troubleshooting​

ErrorLikely cause
invalid_scopeMissing groups or offline_access on the client; wrong scope name; provider requires a full URI scope
Missing parameter: code_challenge_methodPKCE misconfiguration on IdP
redirect_uri mismatchCallback not registered exactly; URI registered as SPA instead of Web
Login works, no refreshToken in browser hashoffline_access not assigned on the IdP client
401 on API routesOpaque access token; aud is not OIDC_CLIENT_ID; wrong iss
403 on API routesRBAC groups/roles not on the access token
CLI login fails, browser worksROPC / direct access grants disabled
OAuth state errors on HAAUTH_SESSION_STORE_TYPE still memory with multiple replicas

HA notes​

For multiple Controller replicas with browser login, set AUTH_SESSION_STORE_TYPE=database (mysql/postgres). memory is only safe for a single replica.

DocumentTopic
Embedded OIDC AuthenticationDefault auth mode
External OIDC providersGroups, roles, Keycloak, Entra ID, Okta, Auth0
Controller configurationFull env var tables
Controller REST APIOAuth and user endpoints
EdgeOps Console configurationconsoleUrl and runtime auth.* paths
Group 3See anything wrong with the document? Help us improve it!