Skip to content

Commit afda2a8

Browse files
committed
feat(okta): call userinfo endpoint to fetch groups and name claims
Access tokens from Okta do not include group claims configured at the app level. Call /v1/userinfo on each request to retrieve groups, name, and email from the profile — falling back to JWT claims if absent. Adds OKTA_USERINFO_URL env var to override the default derived URL.
1 parent c2909a8 commit afda2a8

1 file changed

Lines changed: 37 additions & 3 deletions

File tree

keep/identitymanager/identity_managers/okta/okta_authverifier.py

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import os
33

44
import jwt
5+
import requests
56
from fastapi import Depends, HTTPException
67

78
from keep.api.core.config import config
@@ -47,6 +48,12 @@ def __init__(self, scopes: list[str] = []) -> None:
4748
self.jwks_client = jwt.PyJWKClient(self.jwks_url)
4849
logger.info(f"Initialized JWKS client with URL: {self.jwks_url}")
4950

51+
# Userinfo endpoint for fetching group claims not present in the access token
52+
self.userinfo_url = os.environ.get(
53+
"OKTA_USERINFO_URL",
54+
f"{self.okta_issuer}/v1/userinfo" if self.okta_issuer else None,
55+
)
56+
5057
# Build group → Keep role mapping from environment variables.
5158
# OKTA_ADMIN_GROUPS, OKTA_NOC_GROUPS, OKTA_WEBHOOK_GROUPS accept
5259
# comma-separated Okta group names.
@@ -64,6 +71,23 @@ def __init__(self, scopes: list[str] = []) -> None:
6471

6572
self.auto_create_user = config("OKTA_AUTO_CREATE_USER", default=True, cast=bool)
6673

74+
def _get_userinfo(self, token: str) -> dict:
75+
"""Call Okta's userinfo endpoint to retrieve claims not present in the access token (e.g. groups)."""
76+
if not self.userinfo_url:
77+
return {}
78+
try:
79+
resp = requests.get(
80+
self.userinfo_url,
81+
headers={"Authorization": f"Bearer {token}"},
82+
timeout=5,
83+
)
84+
if resp.status_code == 200:
85+
return resp.json()
86+
logger.warning(f"Userinfo endpoint returned {resp.status_code}")
87+
except Exception:
88+
logger.exception("Failed to call userinfo endpoint")
89+
return {}
90+
6791
def _verify_bearer_token(self, token: str = Depends(oauth2_scheme)) -> AuthenticatedEntity:
6892
if not token:
6993
raise HTTPException(status_code=401, detail="No token provided")
@@ -82,10 +106,20 @@ def _verify_bearer_token(self, token: str = Depends(oauth2_scheme)) -> Authentic
82106
options={"verify_exp": True}
83107
)
84108

109+
# Enrich with userinfo claims (groups, name, etc. may not be in the access token)
110+
userinfo = self._get_userinfo(token)
111+
85112
tenant_id = payload.get("keep_tenant_id", "keep")
86-
email = payload.get("email") or payload.get("sub") or payload.get("preferred_username")
87-
name = payload.get("name") or payload.get("displayName") or email
88-
groups = payload.get("groups", [])
113+
email = (
114+
payload.get("email") or userinfo.get("email")
115+
or payload.get("sub") or payload.get("preferred_username")
116+
)
117+
name = (
118+
userinfo.get("name") or userinfo.get("displayName")
119+
or payload.get("name") or payload.get("displayName")
120+
or email
121+
)
122+
groups = userinfo.get("groups", []) or payload.get("groups", [])
89123

90124
logger.info(f"Token claims — email={email}, name={name}, groups={groups}, group_mappings={self.group_mappings}")
91125

0 commit comments

Comments
 (0)