Build an Okta Identity Dashboard
Connect to Okta's REST API and visualize identity data with Python.
Introduction
30 Second Summary
Every organization needs to know who has access to what. Whether it's a school managing student accounts or a company tracking employee logins, identity data is the foundation of security.
In this project, you will build an interactive dashboard that connects to Okta's REST API, pulls live identity data, and displays it with charts and filterable tables using Python and Streamlit.
What You'll Build
A Python-powered web dashboard that authenticates with Okta's Management API and visualizes user, group, and application data in real time.
By the end of this project, you'll have:
- A Python module that authenticates with Okta's REST API using token-based auth and handles paginated responses.
- An interactive Streamlit dashboard with pie charts, bar charts, and stat cards showing identity metrics.
- A reusable data pipeline that fetches JSON from any REST API and transforms it into visual reports.
- Secret Mission: Add System Log data to display recent login activity and security events on your dashboard.
Are there any prerequisites?
None. This guide walks you through installing Python, setting up an Okta account, and configuring everything from scratch. All you need is a computer with an internet connection.
Set Up Your Environment
Before you can connect to any API and build a dashboard, you need the right tools installed on your machine.
In this step, you will install Python, set up a dedicated project folder with an isolated environment, and confirm that all the libraries your dashboard needs are ready to go.
In this step, get ready to:
- Install Python 3.14 and verify it works in your terminal.
- Create a project folder with a virtual environment and install all required packages.
- Create a credentials file and confirm Streamlit runs successfully.
Install Python 3.14
Python is the programming language that powers your dashboard, your API calls, and your data processing. You need version 3.14 or higher for compatibility with the libraries this project uses.
- Open the Python 3.14.6 download page in your browser:
https://www.python.org/downloads/release/python-3146/
- Scroll down to the files table and download the installer for your operating system:
Windows
- Click Windows installer (64-bit) (marked as Recommended).
- Run the downloaded installer.
- On the first screen, check the box that says Add Python to PATH.
- Click Install Now.
- Wait for the installation to complete, then click Close.
macOS
- Click macOS 64-bit universal2 installer.
- Open the downloaded .pkg file and follow the prompts to install.
- Click Continue through each screen, then click Install.
Why check Add Python to PATH?
Adding Python to your PATH lets you run python from any terminal window without typing the full file path to the executable. Without this, your terminal will not recognize Python commands.
- Close any open terminal windows, then open a new terminal to pick up the PATH change. Verify your installation by running the version check command for your operating system:
Windows
python --version
macOS
python3 --version
✔️ I see Python 3.14 or higher
You should see output like Python 3.14.6. Python is ready. Move on to the next substep.
ⓧ I see an older version
Your system has an older Python installed, but this project requires 3.14 or higher.
- Download and install Python 3.14.6 from the link above. The new installer adds 3.14 alongside your existing version.
- On Windows, make sure Add Python to PATH is checked so the new version takes priority.
- On macOS, after installing, use python3.14 --version to confirm the new version is available. You may need to use python3.14 instead of python3 for the rest of this project if your system default still points to the older version.
- Close and reopen your terminal, then run the version check again.
ⓧ Command not found
Python is not installed on your machine yet.
- Go back to the download link above and install Python 3.14.6 using the platform instructions.
- On Windows, check Add Python to PATH during installation.
- Close and reopen your terminal after installation.
- Run the version check command again.
Still seeing command not found?
On Windows, make sure you opened a brand new terminal window after installation. On macOS, try python3.14 --version if python3 --version does not work.
help me troubleshoot why Python is not found after installing it
Create your project folder and virtual environment
A virtual environment keeps your project's packages isolated from any other Python projects on your system. This prevents version conflicts between libraries.
- In VS Code, open the integrated terminal by pressing Ctrl+` (backtick key, above Tab).
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir okta-dashboard
cd okta-dashboard
- Confirm the folder exists by running:
ls
You should see the empty okta-dashboard folder contents (nothing listed yet, which is expected).
- Open this folder in VS Code by clicking File in the top menu bar.
- Click Open Folder.
- Navigate to Desktop, select the okta-dashboard folder, then click Open.
- If VS Code asks whether you trust the authors of the files in this folder, click Yes, I trust the authors.
- Open a new terminal in the project folder by pressing Ctrl+` (backtick). This terminal automatically starts inside okta-dashboard.
- Create a virtual environment by running this command:
Windows
python -m venv venv
macOS
python3 -m venv venv
- Activate the virtual environment by running:
Windows
venv\Scripts\activate
macOS
source venv/bin/activate
You should see (venv) appear at the beginning of your terminal prompt. This confirms the virtual environment is active.
Not seeing (venv) in your prompt?
On Windows, if you see an error about execution policies, run Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser first, then try the activate command again.
On macOS, make sure you used source before the path. Without it, the command will not work.
help me activate my Python virtual environment
- Install the required packages by running this command:
pip install requests==2.34.2 pandas==2.3.0 streamlit==1.58.0 plotly==6.8.0 python-dotenv==1.1.0
What are these packages?
- requests makes HTTP calls to the Okta API.
- pandas transforms JSON data into tables you can filter and sort.
- streamlit turns your Python script into an interactive web dashboard.
- plotly renders interactive pie charts and bar charts.
- python-dotenv loads your credentials from a .env file so they stay out of your code.
- Verify all packages installed successfully by running:
pip list
You should see requests, pandas, streamlit, plotly, and python-dotenv listed in the output along with their version numbers.
Seeing install errors?
Make sure (venv) appears in your terminal prompt. If it does not, your virtual environment is not active. Run the activate command again before retrying the install.
help me fix pip install errors in my virtual environment
Create your credentials file and verify Streamlit
Your dashboard needs two pieces of information to connect to Okta: your organization's domain and an API token. You will store these in a .env file that keeps sensitive values separate from your code.
- In VS Code's Explorer sidebar, click the New File icon (a page with a plus sign) at the top of the file list.
- Name the file .env and press Enter.
- Paste the following placeholder content into the file:
OKTA_DOMAIN=dev-123456.okta.com
OKTA_API_TOKEN=your-api-token-here
What is in this file?
OKTA_DOMAIN is the web address of your Okta organization. You will get your real domain when you sign up for Okta in the next step.
OKTA_API_TOKEN is an authentication key that proves your script has permission to read data from Okta. You will generate this in the next step as well.
- Save the file with Ctrl+S (Windows) or Cmd+S (macOS).
- Confirm Streamlit is working by running the demo command in your terminal:
streamlit hello
A new browser tab should open automatically showing the Streamlit demo page with colorful animations and example charts.
Browser did not open?
If the browser does not open automatically, look in your terminal for a line that says Local URL: http://localhost:8501. Copy that URL and paste it into your browser manually.
If you see an error that streamlit is not recognized, make sure your virtual environment is still active (you should see (venv) in your prompt).
help me get streamlit hello to work
- Once you have confirmed the demo page loads, press Ctrl+C in your terminal to stop the Streamlit server.
✔️ Awesome, I've got everything!
Great. Make sure you have saved your .env file.
ⓧ I'd like to double check the full code
Here is what your .env file should look like:
OKTA_DOMAIN=dev-123456.okta.com
OKTA_API_TOKEN=your-api-token-here
Your development environment is ready. Next up, you will create an Okta account and generate the API credentials that bring your dashboard to life.
Set Up Okta and Create an API Token
Your Python environment is ready. Now you need a source of identity data for your dashboard to visualize.
In this step, you will sign up for an Okta Integrator Free Plan, populate it with test users and groups, and generate an API token that your Python script will use to authenticate with Okta's REST API.
In this step, get ready to:
- Sign up for an Okta Integrator Free Plan.
- Create test users and groups in your Okta org.
- Generate an API token for authentication.
Sign up for the Okta Integrator Free Plan
Okta is an identity management platform that stores user accounts, group memberships, and application assignments. The Integrator Free Plan gives you a fully functional org with up to 10 active users at no cost.
- Open your browser and navigate to https://developer.okta.com/signup/.
- Select Sign up for Integrator Free Plan in the Okta Workforce Identity tile.
- Enter your school or business email address and complete the signup form.
- Check your email for a verification message from Okta and click the activation link.
What happens after signup?
After verifying your email, Okta creates your org and assigns you a unique domain (e.g., dev-123456.okta.com). You are automatically granted the super administrator role, which gives you full access to all API endpoints.
Once you can see the Okta Admin Console dashboard, check the outcome below that matches your experience.
✔️ I'm signed in to the Admin Console
You are all set. Note your org domain from the browser address bar (e.g., dev-123456.okta.com). You will need it shortly.
Continue with the next substep below to add test data.
ⓧ My email was not accepted
Some email domains are not accepted by Okta's signup. If this happened to you, you can still complete the project using sample data.
- Create a new file called sample_data.json in your okta-dashboard folder.
- Paste the following mock data into sample_data.json:
{
"users": [
{
"id": "00u1example",
"status": "ACTIVE",
"profile": {
"firstName": "Alice",
"lastName": "Johnson",
"email": "alice@example.com",
"login": "alice@example.com"
},
"lastLogin": "2026-06-30T14:22:00.000Z"
},
{
"id": "00u2example",
"status": "ACTIVE",
"profile": {
"firstName": "Bob",
"lastName": "Smith",
"email": "bob@example.com",
"login": "bob@example.com"
},
"lastLogin": "2026-06-28T09:15:00.000Z"
},
{
"id": "00u3example",
"status": "STAGED",
"profile": {
"firstName": "Carol",
"lastName": "Davis",
"email": "carol@example.com",
"login": "carol@example.com"
},
"lastLogin": null
},
{
"id": "00u4example",
"status": "PROVISIONED",
"profile": {
"firstName": "Dan",
"lastName": "Wilson",
"email": "dan@example.com",
"login": "dan@example.com"
},
"lastLogin": null
}
],
"groups": [
{
"id": "00g1example",
"profile": {
"name": "Engineering",
"description": "Engineering team"
},
"memberCount": 2
},
{
"id": "00g2example",
"profile": {
"name": "Marketing",
"description": "Marketing team"
},
"memberCount": 2
}
],
"apps": [
{
"id": "0oa1example",
"label": "Slack",
"status": "ACTIVE",
"signOnMode": "SAML_2_0"
},
{
"id": "0oa2example",
"label": "GitHub",
"status": "ACTIVE",
"signOnMode": "OPENID_CONNECT"
}
]
}
- Save sample_data.json.
- Skip the remaining substeps in this step and continue to Step 3. Your dashboard will load this file automatically when it cannot connect to a live Okta org.
Create test users and groups
Your Okta org starts empty. Adding test users and groups gives you real data to visualize on your dashboard.
First, create 4 test users with different statuses so your charts show meaningful variety.
- In the Admin Console, click Directory in the left sidebar.
- Click People.
- Click Add Person.
- Enter Alice as the first name, Johnson as the last name, and alice@example.com as the username.
- Under Password, select Set by admin and enter any password you like.
- Uncheck User must change password on first login.
- Click Save.
- Repeat the process to add 3 more users: Bob Smith (bob@example.com), Carol Davis (carol@example.com), and Dan Wilson (dan@example.com).
Different user statuses
Users you activate immediately will show as ACTIVE. If you skip sending the activation email, users will show as STAGED or PROVISIONED. Having a mix of statuses makes your dashboard charts more interesting.
Next, create 2 groups and assign users to them.
- Click Directory in the left sidebar, then click Groups.
- Click Add Group.
- Enter Engineering in the Name field and Engineering team in the Description field.
- Click Save.
- Create a second group named Marketing with the description Marketing team.
- Open the Engineering group, navigate to the Members tab, and add Alice Johnson and Bob Smith.
- Open the Marketing group and add Carol Davis and Dan Wilson as members.
You should now see 4 users on the Directory > People page and 2 groups (plus the default Everyone group) on the Directory > Groups page.
Create an API token and update your credentials
Your Python script needs a way to prove its identity when calling Okta's API. Okta uses an SSWS token scheme where you pass your token in the Authorization header of every request.
- In the Admin Console, click Security in the left sidebar.
- Click API.
- Click the Tokens tab.
- Click Create Token.
- Enter Dashboard Token as the token name.
- Click Create Token.
- Copy the token value immediately. This value is only shown once. If you lose it, you must create a new token.
Copy your token now
Okta displays the token value only once at creation time. If you close this dialog without copying the token, you cannot retrieve it again. You would need to delete this token and create a new one.
Now update your .env file with your real credentials.
- Switch back to VS Code and open your .env file.
- Replace dev-123456.okta.com with your actual Okta domain (visible in your browser address bar when signed in to the Admin Console).
- Replace your-api-token-here with the token value you just copied.
- Save .env.
Why a .env file?
Storing credentials in a .env file keeps them separate from your code. The python-dotenv package loads these values as environment variables at runtime. This means you can share your code without accidentally exposing your API token.
Lost your token value?
If you closed the dialog before copying, go back to Security > API > Tokens, revoke the old token, and click Create Token again to generate a new one.
help me create a new Okta API token
Your Okta org is populated with test data and your credentials are safely stored. Next up, you will write a Python module that connects to Okta's API and pulls this data programmatically.
Connect to the Okta API with Python
You have an Okta org with test users, groups, and an API token ready to go. Now it is time to write the Python code that actually talks to Okta.
In this step, you will build a Python module that authenticates with Okta's REST API, pulls down identity data, and gracefully falls back to sample data if the connection fails.
In this step, get ready to:
- Create an authenticated API client that handles pagination.
- Fetch users, groups, and applications from Okta.
- Add a fallback that loads sample data when the API is unreachable.
Create the API client file with authentication
Every request to Okta's Management API needs an Authorization header containing your token in the SSWS scheme. Your client module will read the credentials from .env and build a reusable function that sends authenticated requests and handles paginated results automatically.
- Create a new file called okta_client.py in your okta-dashboard folder by running this command in your VS Code terminal:
touch okta_client.py
- Confirm the file exists by running:
ls
You should see okta_client.py listed alongside your .env and sample_data.json files.
- Open okta_client.py in the VS Code editor by clicking on it in the Explorer sidebar.
- Add the imports and configuration block by pasting this code:
import os
import json
import requests
from dotenv import load_dotenv
load_dotenv()
OKTA_DOMAIN = os.getenv("OKTA_DOMAIN")
OKTA_API_TOKEN = os.getenv("OKTA_API_TOKEN")
BASE_URL = f"https://{OKTA_DOMAIN}"
What does this code do?
load_dotenv() reads your .env file and loads OKTA_DOMAIN and OKTA_API_TOKEN into environment variables.
os.getenv() pulls those values into Python variables. BASE_URL constructs the full Okta API base address from your domain.
- Below the config block, add the okta_get() function that sends authenticated, paginated GET requests:
def okta_get(endpoint, params=None):
"""Make an authenticated GET request to the Okta API."""
url = f"{BASE_URL}{endpoint}"
headers = {
"Authorization": f"SSWS {OKTA_API_TOKEN}",
"Accept": "application/json",
}
all_results = []
while url:
response = requests.get(url, headers=headers, params=params, timeout=30)
response.raise_for_status()
all_results.extend(response.json())
# Handle pagination via Link header
links = response.headers.get("Link", "")
url = None
for link in links.split(","):
if 'rel="next"' in link:
url = link.split(";")[0].strip().strip("<>")
params = None # params are already in the next URL
break
return all_results
What does this code do?
- The Authorization: SSWS {token} header tells Okta who you are. This is Okta's proprietary token-based auth scheme.
- response.raise_for_status() throws an error if the API returns a 4xx or 5xx status code, so you catch problems immediately.
- The while url loop handles pagination. Okta returns a Link header with rel="next" pointing to the next page of results. The loop follows that link until there are no more pages.
What is pagination?
When an API has hundreds or thousands of records, it splits the response across multiple "pages" instead of sending everything at once. Okta uses cursor-based pagination and includes the URL for the next page in the response's Link header.
Your code follows that next link automatically so you always get the full dataset.
- Save okta_client.py by pressing Cmd+S (macOS) or Ctrl+S (Windows).
Seeing a red underline on import lines?
Make sure your virtual environment is activated. Check that the bottom-left corner of VS Code shows your venv interpreter. If it shows a system Python instead, click the interpreter name and select the one inside your venv folder.
help me fix import errors in VS Code with a virtual environment
Add the data-fetching functions
With okta_get() handling authentication and pagination, you can now write thin wrapper functions for each Okta endpoint.
- Add these three functions below okta_get() in okta_client.py:
def fetch_users():
"""Fetch all users from Okta."""
return okta_get("/api/v1/users", params={"limit": 200})
def fetch_groups():
"""Fetch all groups from Okta."""
return okta_get("/api/v1/groups", params={"limit": 200})
def fetch_apps():
"""Fetch all applications from Okta."""
return okta_get("/api/v1/apps", params={"limit": 200})
What does this code do?
- Each function calls a different Okta REST API endpoint: /api/v1/users, /api/v1/groups, and /api/v1/apps.
- The limit: 200 parameter tells Okta to return up to 200 records per page. If there are more, okta_get() follows the pagination link automatically.
- Save okta_client.py.
Not sure where to paste?
Make sure these three functions sit directly below the return all_results line of okta_get(). Each function should be at the top level of the file (no indentation), separated by a single blank line.
help me place my fetch functions in okta_client.py
Add the fallback data loader and test the module
Not every learner will have a live Okta connection. The load_data() function tries the live API first, then falls back to your sample_data.json file if anything goes wrong.
- Add the load_data() function and the test block below your fetch functions in okta_client.py:
def load_data():
"""Load data from Okta API, falling back to sample data."""
if OKTA_DOMAIN and OKTA_API_TOKEN:
try:
users = fetch_users()
groups = fetch_groups()
apps = fetch_apps()
return {"users": users, "groups": groups, "apps": apps}
except Exception as e:
print(f"Failed to connect to Okta: {e}")
print("Falling back to sample data...")
with open("sample_data.json", "r") as f:
return json.load(f)
if __name__ == "__main__":
data = load_data()
print(f"Users: {len(data['users'])}")
print(f"Groups: {len(data['groups'])}")
print(f"Apps: {len(data['apps'])}")
What does this code do?
- load_data() first checks if both OKTA_DOMAIN and OKTA_API_TOKEN exist. If they do, it tries fetching live data from Okta.
- If the API call fails for any reason (bad token, network issue, expired org), the except block catches the error and the function loads sample_data.json instead.
- The if __name__ == "__main__" block only runs when you execute the file directly. It prints a quick summary so you can confirm everything works.
- Save okta_client.py.
- Test your module by running this command in your terminal:
python okta_client.py
You should see output like this (the exact numbers depend on whether you are hitting the live API or sample data):
Users: 4
Groups: 2
Apps: 2
Seeing a connection error or 401 Unauthorized?
- If you see "Failed to connect to Okta" followed by the counts, that is normal. Your code fell back to sample data successfully.
- If you see a 401 error, double check that your OKTA_API_TOKEN value in .env matches the token you copied from the Admin Console. Make sure there are no extra spaces or quotes around the value.
- If you see FileNotFoundError for sample_data.json, confirm the file is in the same folder as okta_client.py.
help me debug my okta_client.py connection issue
✔️ Awesome, I've got everything!
Great. Double check you have saved okta_client.py and move on to the next step.
ⓧ I'd like to double check the full code
Here is the complete okta_client.py file. Compare yours against it:
import os
import json
import requests
from dotenv import load_dotenv
load_dotenv()
OKTA_DOMAIN = os.getenv("OKTA_DOMAIN")
OKTA_API_TOKEN = os.getenv("OKTA_API_TOKEN")
BASE_URL = f"https://{OKTA_DOMAIN}"
def okta_get(endpoint, params=None):
"""Make an authenticated GET request to the Okta API."""
url = f"{BASE_URL}{endpoint}"
headers = {
"Authorization": f"SSWS {OKTA_API_TOKEN}",
"Accept": "application/json",
}
all_results = []
while url:
response = requests.get(url, headers=headers, params=params, timeout=30)
response.raise_for_status()
all_results.extend(response.json())
# Handle pagination via Link header
links = response.headers.get("Link", "")
url = None
for link in links.split(","):
if 'rel="next"' in link:
url = link.split(";")[0].strip().strip("<>")
params = None # params are already in the next URL
break
return all_results
def fetch_users():
"""Fetch all users from Okta."""
return okta_get("/api/v1/users", params={"limit": 200})
def fetch_groups():
"""Fetch all groups from Okta."""
return okta_get("/api/v1/groups", params={"limit": 200})
def fetch_apps():
"""Fetch all applications from Okta."""
return okta_get("/api/v1/apps", params={"limit": 200})
def load_data():
"""Load data from Okta API, falling back to sample data."""
if OKTA_DOMAIN and OKTA_API_TOKEN:
try:
users = fetch_users()
groups = fetch_groups()
apps = fetch_apps()
return {"users": users, "groups": groups, "apps": apps}
except Exception as e:
print(f"Failed to connect to Okta: {e}")
print("Falling back to sample data...")
with open("sample_data.json", "r") as f:
return json.load(f)
if __name__ == "__main__":
data = load_data()
print(f"Users: {len(data['users'])}")
print(f"Groups: {len(data['groups'])}")
print(f"Apps: {len(data['apps'])}")
Your API client is working. Next up, you will use this data to build an interactive dashboard with charts and filterable tables.
Build the Interactive Dashboard
Your Python client can pull live data from Okta. Now you need a way to actually see that data.
In this step, you will build an interactive web dashboard using Streamlit and Plotly. The dashboard displays metrics, charts, and a filterable table so you can explore your Okta identity data visually.
In this step, get ready to:
- Create a Streamlit app that loads your Okta data into pandas DataFrames.
- Display stat cards and interactive Plotly charts for user status and group membership.
- Add a filterable user directory table with a sidebar refresh button.
Create the dashboard file and connect your data
Streamlit apps are regular Python scripts that run top-to-bottom. Each time a user interacts with a widget, Streamlit re-executes the entire script. Caching prevents expensive API calls from repeating on every interaction.
- Create a new file called dashboard.py in your okta-dashboard folder.
- Add the imports, page configuration, sidebar, and data loading code by pasting the following into dashboard.py:
import streamlit as st
import pandas as pd
import plotly.express as px
from okta_client import load_data
st.set_page_config(
page_title="Okta Identity Dashboard",
page_icon="\ud83d\udd10",
layout="wide",
)
st.title("Okta Identity Dashboard")
# Sidebar
with st.sidebar:
st.header("Controls")
if st.button("Refresh Data"):
st.cache_data.clear()
@st.cache_data(ttl=300)
def get_data():
return load_data()
data = get_data()
# Convert to DataFrames
users_df = pd.json_normalize(data["users"])
groups_df = pd.json_normalize(data["groups"])
apps_df = pd.json_normalize(data["apps"])
What does this code do?
The imports bring in Streamlit for the web UI, pandas for tabular data, Plotly for charts, and your own okta_client module for fetching data.
The @st.cache_data(ttl=300) decorator caches the result of get_data() for 5 minutes. This means Streamlit reuses the cached data instead of calling the Okta API on every interaction. Clicking the Refresh Data button in the sidebar clears that cache, forcing a fresh API call on the next rerun.
Finally, pd.json_normalize() flattens the nested JSON from the API into flat DataFrames. Nested keys like profile.firstName become column names.
- Save dashboard.py.
- Launch the dashboard by running this command in your terminal:
streamlit run dashboard.py
- A browser tab opens automatically. You should see the title "Okta Identity Dashboard" and a sidebar on the left with a Controls header and a Refresh Data button.
Browser did not open?
Check your terminal output for a URL (usually http://localhost:8501). Copy and paste it into your browser manually. If you see a ModuleNotFoundError, make sure your virtual environment is activated.
help me debug why my Streamlit app is not launching
Build the metrics row and charts
With data flowing into the dashboard, you can now display summary metrics and visual breakdowns. Streamlit's column layout places multiple widgets side by side.
- Add the metrics row and charts below your existing code in dashboard.py:
# Metrics row
col1, col2, col3 = st.columns(3)
with col1:
st.metric("Total Users", len(users_df))
with col2:
st.metric("Total Groups", len(groups_df))
with col3:
st.metric("Total Apps", len(apps_df))
st.divider()
# Charts row
chart_col1, chart_col2 = st.columns(2)
with chart_col1:
st.subheader("Users by Status")
if "status" in users_df.columns:
status_counts = users_df["status"].value_counts().reset_index()
status_counts.columns = ["Status", "Count"]
fig_status = px.pie(
status_counts,
values="Count",
names="Status",
hole=0.4,
)
st.plotly_chart(fig_status, use_container_width=True)
with chart_col2:
st.subheader("Groups")
if "profile.name" in groups_df.columns:
fig_groups = px.bar(
groups_df,
x="profile.name",
y="memberCount" if "memberCount" in groups_df.columns else None,
labels={"profile.name": "Group Name", "memberCount": "Members"},
)
st.plotly_chart(fig_groups, use_container_width=True)
What does this code do?
The st.columns(3) call creates three equal-width columns. Each column displays a stat card with st.metric() showing the total count of users, groups, and apps.
Below the metrics, px.pie() creates a donut chart (the hole=0.4 parameter cuts out the center) showing how many users are ACTIVE, STAGED, or PROVISIONED. The bar chart on the right uses px.bar() to show member count per group.
- Save dashboard.py.
- Switch back to your browser. Streamlit auto-reloads the page. You should now see three stat cards (showing 4, 2, and 2 if using sample data), a pie chart breaking down user statuses, and a bar chart showing your two groups.
Charts not appearing?
Make sure you pasted the new code after the apps_df line, not inside an existing block. Also check that the indentation matches exactly. Python is whitespace-sensitive.
help me fix my Streamlit charts not rendering
Add the filterable user directory
Charts give you a high-level overview, but sometimes you need to drill into individual records. A filterable table lets you search by user status without writing new queries.
- Add the user directory section below your charts code in dashboard.py:
st.divider()
# User table with filter
st.subheader("User Directory")
if "status" in users_df.columns:
status_options = ["All"] + sorted(users_df["status"].unique().tolist())
selected_status = st.selectbox("Filter by Status", status_options)
if selected_status == "All":
filtered_df = users_df
else:
filtered_df = users_df[users_df["status"] == selected_status]
else:
filtered_df = users_df
display_columns = []
for col in ["profile.firstName", "profile.lastName", "profile.email", "status", "lastLogin"]:
if col in filtered_df.columns:
display_columns.append(col)
if display_columns:
st.dataframe(
filtered_df[display_columns].rename(
columns={
"profile.firstName": "First Name",
"profile.lastName": "Last Name",
"profile.email": "Email",
"status": "Status",
"lastLogin": "Last Login",
}
),
use_container_width=True,
)
What does this code do?
The st.selectbox() widget creates a dropdown menu populated with all unique status values from your data (ACTIVE, STAGED, PROVISIONED) plus an "All" option. When you pick a status, Streamlit reruns the script and the filtered_df variable holds only matching rows.
The st.dataframe() call renders the filtered results as an interactive table. The .rename() method transforms the flattened column names (like profile.firstName) into reader-friendly headers (like "First Name").
- Save dashboard.py.
- Switch back to your browser. Below the charts you should now see a "User Directory" heading, a Filter by Status dropdown, and a table showing First Name, Last Name, Email, Status, and Last Login columns.
- Try selecting ACTIVE from the dropdown. The table should instantly filter to show only Alice and Bob.
Table is empty or columns are missing?
If you see an empty table, confirm your sample_data.json has the profile object nested inside each user with firstName, lastName, and email fields. The pd.json_normalize() function expects that exact nesting to produce profile.firstName columns.
help me fix my empty Streamlit dataframe table
✔️ Awesome, I've got everything!
Great. Double check you have saved dashboard.py and your browser shows the complete dashboard with stat cards, charts, and a filterable table.
ⓧ I'd like to double check the full code
Here is the complete dashboard.py file. Compare yours against this:
import streamlit as st
import pandas as pd
import plotly.express as px
from okta_client import load_data
st.set_page_config(
page_title="Okta Identity Dashboard",
page_icon="\ud83d\udd10",
layout="wide",
)
st.title("Okta Identity Dashboard")
# Sidebar
with st.sidebar:
st.header("Controls")
if st.button("Refresh Data"):
st.cache_data.clear()
@st.cache_data(ttl=300)
def get_data():
return load_data()
data = get_data()
# Convert to DataFrames
users_df = pd.json_normalize(data["users"])
groups_df = pd.json_normalize(data["groups"])
apps_df = pd.json_normalize(data["apps"])
# Metrics row
col1, col2, col3 = st.columns(3)
with col1:
st.metric("Total Users", len(users_df))
with col2:
st.metric("Total Groups", len(groups_df))
with col3:
st.metric("Total Apps", len(apps_df))
st.divider()
# Charts row
chart_col1, chart_col2 = st.columns(2)
with chart_col1:
st.subheader("Users by Status")
if "status" in users_df.columns:
status_counts = users_df["status"].value_counts().reset_index()
status_counts.columns = ["Status", "Count"]
fig_status = px.pie(
status_counts,
values="Count",
names="Status",
hole=0.4,
)
st.plotly_chart(fig_status, use_container_width=True)
with chart_col2:
st.subheader("Groups")
if "profile.name" in groups_df.columns:
fig_groups = px.bar(
groups_df,
x="profile.name",
y="memberCount" if "memberCount" in groups_df.columns else None,
labels={"profile.name": "Group Name", "memberCount": "Members"},
)
st.plotly_chart(fig_groups, use_container_width=True)
st.divider()
# User table with filter
st.subheader("User Directory")
if "status" in users_df.columns:
status_options = ["All"] + sorted(users_df["status"].unique().tolist())
selected_status = st.selectbox("Filter by Status", status_options)
if selected_status == "All":
filtered_df = users_df
else:
filtered_df = users_df[users_df["status"] == selected_status]
else:
filtered_df = users_df
display_columns = []
for col in ["profile.firstName", "profile.lastName", "profile.email", "status", "lastLogin"]:
if col in filtered_df.columns:
display_columns.append(col)
if display_columns:
st.dataframe(
filtered_df[display_columns].rename(
columns={
"profile.firstName": "First Name",
"profile.lastName": "Last Name",
"profile.email": "Email",
"status": "Status",
"lastLogin": "Last Login",
}
),
use_container_width=True,
)
Your dashboard is live. You have a complete identity management view with metrics, charts, and a filterable directory. Next up, you will tackle the Secret Mission and add login activity tracking from Okta's System Log.
Secret mission
Add Login Activity from System Logs
Your dashboard shows who exists in your org, but not what they're doing. Real identity dashboards track login activity and flag suspicious events. Can you pull System Log data from Okta and visualize login trends over time?
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally and uses Okta's free Integrator plan, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Resources you used:
- okta-dashboard project folder (Python files, virtual environment, sample data).
- Okta Integrator Free Plan org with test users, groups, and an API token.
Keep everything running
No action needed. Choose this if you are still actively building or exploring the dashboard.
- Your Okta org stays active as long as you sign in at least once every 180 days.
- Your API token remains valid as long as it is used at least once every 30 days.
- The Streamlit dashboard runs locally on your machine whenever you start it.
- Python and the pip packages remain installed on your system.
Pause - I'll come back to this later
Shut down the running Streamlit process to free up memory. Your Okta org and local files remain available for when you return.
- In the terminal running Streamlit, press Ctrl+C to stop the server.
- Your Okta org remains available. Sign in periodically (at least once every 180 days) to keep it active.
- Your project files stay on your Desktop until you delete them manually.
Delete - I don't want to use this again
Remove all project resources and revoke access. Follow each item below to clean up completely.
- Stop the Streamlit server by pressing Ctrl+C in the terminal.
- Revoke your API token in the Okta Admin Console. Navigate to Security > API > Tokens. Find the token named Dashboard Token and click Revoke (under the Actions menu), then confirm.
- Deactivate your test users. Navigate to Directory > People. Click a user's name, then select More Actions > Deactivate. Click Deactivate in the confirmation dialog. Repeat for each test user.
- Your Okta org will auto-deactivate after 180 consecutive days with no sign-ins. There is no self-service org deletion.
- Delete the project folder from your machine. On macOS or Linux, run:
cd ~/Desktop
rm -rf okta-dashboard
On Windows, run:
cd ~\Desktop
Remove-Item -Recurse -Force okta-dashboard
- Delete the .env file if you stored it outside the project folder. Check that no copy of your API token remains on disk.
Nice Work!
Nice Work!
You just built a full-stack identity dashboard powered by Python. Your app connects to a live Okta organization, pulls identity data through a REST API, and visualizes it all in an interactive browser-based dashboard.
You've learned how to:
- Built a Python module that authenticates with Okta's REST API using the SSWS token scheme and handles paginated responses automatically.
- Created an interactive Streamlit dashboard with Plotly pie charts, bar charts, stat cards, and filterable data tables.
- Set up a professional Python development environment with virtual environments, dependency pinning, and secure credential management via .env files.
- Secret Mission: Added System Log integration showing login activity trends as a time-series chart and security events filtered by event type.
Ready to quiz yourself?