An enterprise-grade, event-driven identity governance framework built using Azure Functions (Python) and Microsoft Graph API. This system continuously audits Microsoft Entra ID tenants to identify identity anomalies, alerts security operations teams via Microsoft Teams Adaptive Cards, and processes one-click administrator approval workflows to execute real-time, zero-trust cloud remediations.
Unmanaged guest accounts, missing Multi-Factor Authentication (MFA) parameters, and permanent over-privileged administrator assignments represent primary attack surfaces for corporate cloud breaches.
This project solves identity sprawl by transforming security monitoring from a manual, reactive checklist into an automated, self-contained auditing cycle. By leveraging serverless infrastructure, the framework minimizes management overhead while ensuring identity configurations maintain continuous compliance with modern zero-trust architecture rules.
- Audit Phase: A time-triggered Azure Function invokes daily scans against premium Microsoft Graph API directory endpoints.
- Analysis Phase: The engine filters accounts based on specific identity criteria (such as guest inactivity tracking or incomplete authentication methods).
- Alert Phase: If a configuration drift or vulnerability is identified, a structured webhook constructs and sends an interactive JSON Adaptive Card directly to an assigned Microsoft Teams IT administration channel.
- Remediation Phase: The IT administrator reviews the card data directly within Teams and clicks the action trigger. An HTTP-triggered Azure Function processes the request payload and instantly updates the configuration in Microsoft Entra ID.
- Cloud Identity Orchestration: Microsoft Entra ID (Features: Entra ID P2 Identity Logging, Group & Account Scans)
- API Management Layer: Microsoft Graph API v1.0 (Endpoints used:
/users,/credentialUserRegistrationDetails,/directoryRoles) - Serverless Execution: Azure Functions (Python v2 Model, featuring Timer Triggers and HTTP Webhook routes)
- Hosting Runtime Infrastructure: Azure Consumption Plan (Optimized for pay-as-you-go free execution tiers)
- ChatOps UI Interface: Microsoft Teams (Incoming Webhooks & JSON-formatted Interactive Adaptive Cards)
To establish secure communication between Azure and your identity data directories, register an isolated service application within your Entra ID tenant to generate API credentials.
- Navigate to the Microsoft Entra Admin Center > Identity > Applications > App registrations.
- Select New Registration, name it
Identity-Access-Governance-Bot, and click Register
- Copy the Application (client) ID and Directory (tenant) ID values.

- Navigate to Certificates & secrets, generate a new client secret, and securely store the secret value string.
Configure explicit least-privilege enterprise directory read and write policies to authorize the background bot engine.
- Inside your App Registration dashboard, click API permissions > Add a permission > Microsoft Graph.
- Choose Application permissions (not Delegated permissions).

- Search for and check these specific permission scopes:
User.ReadWrite.Allโ Required to query guest fields and automatically disable non-compliant targets.
AuditLog.Read.Allโ Grants explicit access to premium sign-in properties (signInActivity).
RoleManagement.Read.Directoryโ Grants read-only visibility into privileged directory group hierarchies.
- Crucial Action: Click "Grant admin consent for [Your Organization Name]" to clear security authorization flags.
Configure your target Microsoft Teams collaboration workspace to receive external JSON payloads securely.
- Open Microsoft Teams, create or choose an IT infrastructure operational channel, and click Manage Channel.

2. Navigate to **Workflows** > search for **Send webhook alerts to a channel**, and click to add.
3. Copy the long webhook endpoint destination URL.
Build and deploy the Python backend compute modules into your Azure Pay-As-You-Go subscription architecture.
- Open the Azure Portal, select Create a Resource, and select Function App.

- Configure basic deployment settings:
- Hosting Plan: Consumption (Serverless, free execution tier)

- Runtime Stack: Powershell
- Version: Select 7.2 (or the highest 7.x version available).
- **Region: Choose your local or closest data center region (e.g., India South Central).

- Storage: Pair it with a standard local LRS storage account block.

- Proceed through the wizard tabs (Hosting, Monitoring) keeping the defaults, and click Review + Create, then Create*


- Hosting Plan: Consumption (Serverless, free execution tier)
Your code needs to read sensitive connection arguments securely without exposing them in plain text. We will inject your parameters into Azure's secure environment blade.
- Navigate to the Azure Portal and open your newly created Function App.
- On the left sidebar menu, scroll down to the Settings section and click on Configuration (or Environment variables depending on the UI version layout).
- Under the Application settings tab, click + New application setting to add these four exact key-value pairs:
| Key Name | Value to Paste |
|---|---|
TENANT_ID |
Your Microsoft Entra Tenant ID |
CLIENT_ID |
Your registered App's Application ID (Client ID) |
CLIENT_SECRET |
Your App's Client Secret String Value |
TEAMS_WEBHOOK_URL |
The Workflow URL you copied from Microsoft Teams |
- Click Apply or Save at the bottom of the configuration blade, then click Confirm. This instantly restarts your Function App to load the variables safely.
We will organize the application scripts locally before publishing them to the cloud.
Create a folder on your computer named IdentityGovernanceBot. Open
this root folder inside Visual Studio Code (VS Code).
Create the exact directory tree layout below and add the empty files inside it:

IdentityGovernanceBot/
โโโ host.json
โโโ profile.ps1
โโโ requirements.psd1
โโโ IdentityAuditTimer/
โ โโโ function.json
โ โโโ run.ps1
โโโ IdentityRemediatorHttp/
โโโ function.json
โโโ run.ps1
This enables background managed engines to pull modern PowerShell modules automatically.
```json
{
"version": "2.0",
"managedDependency": {
"enabled": true
},
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[4.*, 5.0.0)"
}
}
```
Instructs Azure to handle internal module imports dynamically.
```powershell
@{
'Az' = '10.*'
}
```
Runs standard initialization paths.
```powershell
if ($env:MSI_SECRET) {
Disable-AzContextAutosave -Scope Process | Out-Null
Connect-AzAccount -Identity
}
```
IdentityAuditTimer/function.json
{
"bindings": [
{
"name": "Timer",
"type": "timerTrigger",
"direction": "in",
"schedule": "0 0 0 * * *"
}
]
}using namespace System.Net
# 1. Fetch access credentials from secure App Settings environment
$TenantId = $env:TENANT_ID
$ClientId = $env:CLIENT_ID
$ClientSecret = $env:CLIENT_SECRET
$TeamsWebhookUrl = $env:TEAMS_WORKHOOK_URL
# 2. Authenticate securely with Microsoft Graph API
$Body = @{
Grant_Type = "client_credentials"
Scope = "https://graph.microsoft.com/.default"
Client_Id = $ClientId
Client_Secret = $ClientSecret
}
$TokenResponse = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token" -Method Post -Body $Body
$Headers = @{ Authorization = "Bearer $($TokenResponse.access_token)" }
# 3. Query Microsoft Entra for Inactive Guest Users (>90 Days)
$CutoffDate = (Get-Date).AddDays(-90).ToString("yyyy-MM-dd")
$GraphUrl = "https://graph.microsoft.com/v1.0/users?`$filter=userType eq 'Guest'&`$select=displayName,userPrincipalName,signInActivity"
$Users = (Invoke-RestMethod -Uri $GraphUrl -Method Get -Headers $Headers).value
foreach ($User in $Users) {
$LastSignIn = $User.signInActivity.lastSuccessfulSignInDateTime
# Trigger alert if account is dormant or has never logged in
if (-not $LastSignIn -or ($LastSignIn -lt $CutoffDate)) {
# 4. Construct Teams Workflow Adaptive Card JSON Payload
$Payload = @{
type = "message"
attachments = @(@{
contentType = "application/vnd.microsoft.card.adaptive"
content = @{
type = "AdaptiveCard"
version = "1.4"
body = @(
@{ type = "TextBlock"; text = "๐ก๏ธ Identity Governance Alert"; weight = "Bolder"; size = "Medium"; color = "Attention" },
@{ type = "FactSet"; facts = @(
@{ title = "Target User:"; value = $User.displayName },
@{ title = "User Principal Name:"; value = $User.userPrincipalName },
@{ title = "Violation:"; value = "Dormant Guest Account (>90 Days Inactive)" }
)}
)
actions = @(@{
type = "Action.Http"
title = "Remediate Account"
method = "POST"
url = "https://YOUR_FUNCTION_APP_NAME.azurewebsites.net/api/remediator"
body = "{'user_id': '$($User.userPrincipalName)', 'action': 'disable'}"
headers = @(@{ name = "Content-Type"; value = "application/json" })
})
}
})
}
# 5. Route alert directly into your Microsoft Teams Workflow
$JsonPayload = ConvertTo-Json $Payload -Depth 10
Invoke-RestMethod -Uri $TeamsWebhookUrl -Method Post -ContentType "application/json" -Body $JsonPayload
}
}(YOUR_FUNCTION_APP_NAME in the url string parameters with the name of your actual Azure Function app.)
IdentityRemediatorHttp/function.json
{
"bindings": [
{
"authLevel": "function",
"type": "httpTrigger",
"direction": "in",
"name": "Request",
"methods": ["post"],
"route": "remediator"
},
{
"type": "http",
"direction": "out",
"name": "Response"
}
]
}IdentityRemediatorHttp/run.ps1
using namespace System.Net
param($Request, $TriggerMetadata)
# 1. Catch action variables sent from the Teams button press
$RequestBody = $Request.Body
$UserId = $RequestBody.user_id
$Action = $RequestBody.action
# 2. Acquire Graph Token
$Body = @{
Grant_Type = "client_credentials"
Scope = "https://graph.microsoft.com/.default"
Client_Id = $env:CLIENT_ID
Client_Secret = $env:CLIENT_SECRET
}
$TokenResponse = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$($env:TENANT_ID)/oauth2/v2.0/token" -Method Post -Body $Body
$Headers = @{ Authorization = "Bearer $($TokenResponse.access_token)"; "Content-Type" = "application/json" }
if ($Action -eq "disable") {
# 3. Apply Zero-Trust Security Policy: Block the Account
$PatchUrl = "https://graph.microsoft.com/v1.0/users/$UserId"
$PatchBody = @{ accountEnabled = $false } | ConvertTo-Json
try {
$UpdateResponse = Invoke-WebRequest -Uri $PatchUrl -Method PATCH -Headers $Headers -Body $PatchBody
# 4. Return an instant confirmation response card to Teams
$SuccessCard = @{
type = "AdaptiveCard"
version = "1.4"
body = @(@{
type = "TextBlock"
text = "โ
Remediation Success: Account $UserId has been successfully disabled."
color = "Good"
weight = "Bolder"
})
} | ConvertTo-Json -Depth 5
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
StatusCode = [HttpStatusCode]::OK
Body = $SuccessCard
Headers = @{ "Content-Type" = "application/json" }
})
}
catch {
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
StatusCode = [HttpStatusCode]::InternalServerError
Body = "Remediation Script Encountered an Error: $_"
})
}
}Publish your configurations and scripts directly into your cloud platform runtime environment.
- In VS Code, open the Extensions view (
Ctrl+Shift+X), search for Azure Account and Azure Functions, and install them. - Click the Azure icon that appears in the left activity bar workspace, click Sign in to Azure, and follow the system browser authentication prompt.
- Once logged in, locate the Resources panel inside the Azure extension workspace pane, expand your subscription node, right-click on your targeted Function App, and select Deploy to Function App....
- Select the
IdentityGovernanceBotroot folder directory when prompted. Click Deploy to push the automation code live.
Follow these steps to upload your local PowerShell project files from Visual Studio Code straight into your Azure Pay-As-You-Go subscription.
- Open Visual Studio Code (VS Code).
- Open the Extensions view by clicking the Extensions icon on the left Activity Bar (
Ctrl+Shift+X). - Search for and install these two official extensions:
- Azure Resources
- Azure Functions
- Click on the newly visible Azure icon located on the far left Activity Bar.
- In the Azure panel, click Sign in to Azure....
- A web browser window will automatically launch. Log in using your Azure Pay-As-You-Go subscription credentials.
- Close the browser window once the confirmation message appears.
- In the VS Code Azure panel, locate and expand the Resources section.
- Expand your active subscription tree to locate your target Function App name.
- Right-click on your Function App name and select Deploy to Function App... from the context menu.

- Select your local root folder path
IdentityGovernanceBotwhen prompted for the workspace resource. - Click Deploy to confirm and initiate the file packaging upload sequence.
- Watch the execution progress panel in the bottom-right notification banner of your VS Code workspace.
- Wait for the status indicator message to display "Deployment successful". Your PowerShell automation engine is now live in the cloud.
Follow these steps to safely share your project code with recruiters on GitHub without exposing confidential environment secrets.
local.settings.json file. It contains your private application registration keys and client secret strings.
- Inside your root folder path
IdentityGovernanceBot, create a brand new file named exactly.gitignore. - Open the file and insert this single line of text:
local.settings.json - Save the file. This tells Git to permanently ignore your local secrets file so it can never be pushed to a public repository.
- Click the Source Control icon on the left Activity Bar (
Ctrl+Shift+G). - Click the Initialize Repository button at the top of the pane.
- In the input text box, type a clear commit message, such as:
Initial commit - Identity Governance Bot Code. - Click the checkmark icon or click the arrow next to the Commit button to commit your local workspace files.
- Click the blue Publish Branch button.
- Select Publish to GitHub public repository from the dropdown option list.
- VS Code will automatically prompt you to log into your GitHub account, build the remote cloud repository, and securely upload your project tracking history.
Follow this walkthrough to simulate an identity vulnerability in a controlled environment and test your automation bot framework end-to-end.
Because a new sandbox tenant does not contain old historical data, create a guest user who has never logged in before. The bot will flag it as "dormant" since its last sign-in log will be blank.
- Sign into the Microsoft Entra Admin Center.
- Go to Identity > Users > All users.
- Click + New user at the top of the interface and select Invite external user.
- Fill out these profile properties:
- Click Invite.
- Open a private incognito browser window, log into your personal email inbox, locate the invitation message from Microsoft, and click the verification link to accept the invite. Stop thereโdo not attempt to log in further.

Instead of waiting for the midnight schedule trigger, force the function app to execute right now.
- Open the Azure Portal and go to your Function App dashboard.
- In the left navigation menu, look under the Functions section and click Functions.
- Click on your timer module: IdentityAuditTimer.

- In its inner left sidebar, select Code + Test.
- Click the Test/Run button located on the top command strip.
- A configurations panel will slide out from the right side of the screen. Leave the request input body completely empty and click the green Run button at the bottom.

- Watch the Logs streaming console window at the bottom. Confirm that the script logs show a successful Graph API connection, identify your test guest user, and send an alert notification payload to Teams.
- Open your Microsoft Teams application client.
- Open your dedicated IT Security Operations team space and select the
identity-alertschannel. - Look at the bottom of the channel's Posts conversation feed. Verify that a new post containing an interactive Adaptive Card has arrived with these values:
- Title: ๐ก๏ธ Identity Governance Alert
- Target User:
Test Dormant Guest - Violation:
Dormant Guest Account (>90 Days Inactive) - Check that a prominent interactive action button labeled "Remediate Account" is visible.
- Click the Remediate Account button directly inside that Microsoft Teams post.
- The Teams canvas will forward an HTTP POST remediation payload containing your target user parameter strings back to your live listening API endpoint (
IdentityRemediatorHttp). - Watch the post item update inline. The active button will disappear, and the Adaptive Card layout will automatically rewrite itself to display this confirmation message:
โ Remediation Success: Account Test Dormant Guest has been successfully disabled.
- Switch back to your Microsoft Entra Admin Center window.
- Navigate to Identity > Users > All users.
- Select your
Test Dormant Guestuser account profile card to review its inner directory properties database. - Locate the Account status metric row visibility block.
- Verify that the indicator has instantly been rewritten to read Account Enabled: No.

