| name | managing-okta |
| description | Use when working with Okta — okta identity and access management for user
lifecycle management, application assignments, MFA status review, group and
policy administration, and system log analysis. Covers user provisioning, SSO
app management, authentication policy review, and security event
investigation. Read this skill before any Okta operations — it enforces
discovery-first patterns and strict read-only safety rules.
|
| connection_type | okta |
| preload | false |
Okta Management Skill
Safely read and audit Okta — the identity and access management platform.
MANDATORY: Discovery-First Pattern
Always discover the Okta org configuration and available resources before performing targeted queries. Never guess user IDs or app IDs.
Phase 1: Discovery
#!/bin/bash
okta_api() {
local endpoint="$1"
local method="${2:-GET}"
curl -s -X "$method" \
-H "Authorization: SSWS $OKTA_API_TOKEN" \
-H "Content-Type: application/json" \
"${OKTA_ORG_URL}/api/v1/${endpoint}"
}
echo "=== Okta Org Info ==="
okta_api "org" | jq '{
id: .id,
company_name: .companyName,
subdomain: .subdomain,
status: .status,
created: .created
}'
echo ""
echo "=== User Count ==="
okta_api "users?limit=1" -I 2>/dev/null | grep -i x-rate-limit || \
okta_api "users?limit=1&filter=status+eq+%22ACTIVE%22" | jq '. | length | "Sample returned: \(.)"' -r
echo ""
echo "=== Applications (first 20) ==="
okta_api "apps?limit=20" | jq -r '.[] | "\(.id)\t\(.label)\t\(.status)\t\(.signOnMode)"' | column -t
echo ""
echo "=== Authentication Policies ==="
okta_api "policies?type=ACCESS_POLICY" | jq -r '.[] | "\(.id)\t\(.name)\t\(.status)"' | column -t
Phase 1 outputs: Org details, app inventory, policy list — only reference these in subsequent operations.
Anti-Hallucination Rules
- NEVER guess user login or ID — always search via
users?search= or users?filter=
- NEVER assume app labels — always list apps in Phase 1
- NEVER fabricate group names — always list via
groups?q=
- ONLY read and list — never create, update, or delete without explicit request
Safety Rules
- READ-ONLY by default: GET requests only —
users, apps, groups, logs, policies
- MASK sensitive data: When displaying user profiles, redact SSN, passwords, recovery questions
- FORBIDDEN without explicit request: POST/PUT/DELETE to users, apps, groups; password resets; MFA factor enrollment
- NEVER print API tokens: Always redact token values in output
Core Helper Functions
#!/bin/bash
okta_api() {
local endpoint="$1"
local method="${2:-GET}"
curl -s -X "$method" \
-H "Authorization: SSWS $OKTA_API_TOKEN" \
-H "Content-Type: application/json" \
"${OKTA_ORG_URL}/api/v1/${endpoint}"
}
okta_api_paginated() {
local endpoint="$1"
local max_pages="${2:-5}"
local url="${OKTA_ORG_URL}/api/v1/${endpoint}"
local page=0
while [ -n "$url" ] && [ $page -lt $max_pages ]; do
response=$(curl -s -D /tmp/okta_headers \
-H "Authorization: SSWS $OKTA_API_TOKEN" \
-H "Content-Type: application/json" \
"$url")
echo "$response"
url=$(grep -i '^link:' /tmp/okta_headers | grep 'rel="next"' | sed 's/.*<\(.*\)>.*/\1/' | tr -d '\r')
page=$((page + ))
}
() {
query=
okta_api
}
Common Operations
User Lookup & Status
#!/bin/bash
SEARCH="${1:?Search term required (email, login, or name)}"
echo "=== User Search: $SEARCH ==="
okta_api "users?search=profile.login+sw+%22${SEARCH}%22+or+profile.email+sw+%22${SEARCH}%22&limit=10" | jq -r '.[] | {
id: .id,
login: .profile.login,
email: .profile.email,
name: "\(.profile.firstName) \(.profile.lastName)",
status: .status,
created: .created,
last_login: .lastLogin,
password_changed: .passwordChanged
}'
MFA Factor Status
#!/bin/bash
USER_ID="${1:?User ID required — discover via user search first}"
echo "=== MFA Factors for $USER_ID ==="
okta_api "users/${USER_ID}/factors" | jq -r '.[] | {
id: .id,
type: .factorType,
provider: .provider,
status: .status,
created: .created,
last_verified: .lastVerifiedAt
}'
echo ""
echo "=== Available (Not Enrolled) Factors ==="
okta_api "users/${USER_ID}/factors/catalog" | jq -r '.[] | "\(.factorType)\t\(.provider)\t\(.status)"' | column -t
Application Assignments
#!/bin/bash
USER_ID="${1:?User ID required}"
echo "=== Apps Assigned to User $USER_ID ==="
okta_api "users/${USER_ID}/appLinks" | jq -r '.[] | "\(.appName)\t\(.label)\t\(.linkUrl)"' | column -t
echo ""
echo "=== App User Count (top 10 apps) ==="
okta_api "apps?limit=10" | jq -r '.[] | .id' | while read app_id; do
label=$(okta_api "apps/$app_id" | jq -r '.label')
count=$(okta_api "apps/${app_id}/users?limit=1" | jq '. | length')
echo "$label\t$count users (sampled)"
done | column -t
System Log Analysis
#!/bin/bash
SINCE="${1:-$(date -u -v-24H +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -d '24 hours ago' +%Y-%m-%dT%H:%M:%SZ)}"
echo "=== System Log Events (since $SINCE) ==="
okta_api "logs?since=${SINCE}&limit=20&sortOrder=DESCENDING" | jq -r '.[] | {
time: .published,
event: .eventType,
severity: .severity,
actor: .actor.displayName,
outcome: .outcome.result,
target: (.target[0].displayName // "N/A"),
client_ip: .client.ipAddress
}'
echo ""
echo "=== Failed Login Attempts ==="
okta_api "logs?since=${SINCE}&filter=eventType+eq+%22user.session.start%22+and+outcome.result+eq+%22FAILURE%22&limit=20" | jq -r '.[] | "\(.published)\t\(.actor.displayName)\t\(.client.ipAddress)\t\(.outcome.reason)"' | column -t
Group & Policy Review
#!/bin/bash
echo "=== Groups (first 20) ==="
okta_api "groups?limit=20" | jq -r '.[] | "\(.id)\t\(.profile.name)\t\(.type)\tMembers: \(.profile.memberCount // "N/A")"' | column -t
echo ""
echo "=== Sign-On Policies ==="
okta_api "policies?type=OKTA_SIGN_ON" | jq -r '.[] | {
id: .id,
name: .name,
status: .status,
description: .description,
priority: .priority
}'
echo ""
echo "=== Password Policies ==="
okta_api "policies?type=PASSWORD" | jq -r '.[] | {
id: .id,
name: .name,
min_length: .settings.password.complexity.minLength,
require_lowercase: .settings.password.complexity.minLowerCase,
require_uppercase: .settings.password.complexity.minUpperCase,
require_number: .settings.password.complexity.minNumber,
max_age_days: .settings.password.age.maxAgeDays
}'
Output Format
Present results as a structured report:
Managing Okta Report
════════════════════
Resources discovered: [count]
Resource Status Key Metric Issues
──────────────────────────────────────────────
[name] [ok/warn] [value] [findings]
Summary: [total] resources | [ok] healthy | [warn] warnings | [crit] critical
Action Items: [list of prioritized findings]
Target ≤50 lines of output. Use tables for multi-resource comparisons.
Counter-Rationalizations
| Shortcut | Counter | Why |
|---|
| "I'll skip discovery and check known resources" | Always run Phase 1 discovery first | Resource names change, new resources appear — assumed names cause errors |
| "The user only asked for a quick check" | Follow the full discovery → analysis flow | Quick checks miss critical issues; structured analysis catches silent failures |
| "Default configuration is probably fine" | Audit configuration explicitly | Defaults often leave logging, security, and optimization features disabled |
| "Metrics aren't needed for this" | Always check relevant metrics when available | API/CLI responses show current state; metrics reveal trends and intermittent issues |
| "I don't have access to that" | Try the command and report the actual error | Assumed permission failures prevent useful investigation; actual errors are informative |
Common Pitfalls
- Rate limits: Okta enforces per-endpoint rate limits (typically 600/min for
/api/v1/users) — check X-Rate-Limit-Remaining header
- Pagination: List endpoints return max 200 items — always follow
Link: rel="next" headers for complete data
- User status lifecycle: STAGED -> PROVISIONED -> ACTIVE -> SUSPENDED/DEPROVISIONED — status transitions are one-directional in some cases
- Search vs filter:
search uses Okta's search engine (eventual consistency); filter is exact match (strong consistency) — use filter for precise lookups
- System log retention: Default 90 days; compressed logs may take longer to query — always specify
since parameter
- API token scope: Token inherits permissions of the admin who created it — insufficient permissions return 403, not empty results
- Group types: OKTA_GROUP (manually managed) vs APP_GROUP (synced from app) vs BUILT_IN — behavior differs by type
- Deprovisioned users: Still exist in Okta but cannot authenticate — include
status eq "DEPROVISIONED" in filter to find them