6 Secret Ways to Store User Data in Microsoft Entra – And Why You’re Doing It Wrong! + Video

Listen to this Post

Featured Image

Introduction:

Microsoft Entra ID (formerly Azure AD) provides at least six distinct mechanisms to store user-related data beyond default attributes, each with unique security and scalability trade-offs. Directory extensions offer flexibility for custom application data, but storing sensitive information like PII or credentials there can lead to exposure via Graph API queries. Understanding the right storage layer—from custom security attributes to on-premises extension attributes—is critical for preventing data leaks and maintaining compliance.

Learning Objectives:

  • Differentiate between Entra ID’s six user data storage methods and their appropriate use cases.
  • Implement directory extensions and custom security attributes using PowerShell, Azure CLI, and Graph API.
  • Apply security hardening techniques to prevent unauthorized access to sensitive user attributes.

You Should Know:

  1. Directory Extensions – The Flexible Workhorse for Non‑Sensitive Data
    Directory extensions allow you to add custom properties to user, group, or device objects in Entra ID. They synchronize to Microsoft Graph and are ideal for application‑specific data that doesn’t require high confidentiality (e.g., loyalty points, preferred language). However, because any app with delegated `User.Read` permissions can potentially read these extensions by default, storing sensitive data like SSNs or API keys is dangerous.

Step‑by‑step guide to create and assign a directory extension:
– Prerequisites: Install Microsoft Graph PowerShell SDK.

Install-Module Microsoft.Graph -Scope CurrentUser
Connect-MgGraph -Scopes "Application.ReadWrite.All", "User.ReadWrite.All"

– Create a directory extension definition (example: extension for “departmentCode”):

$params = @{
Id = "extension_<your-app-id>_departmentCode"
Description = "Custom department code for internal apps"
TargetTypes = @("User")
Status = "Available"
Owner = "<your-app-id>"
}
New-MgApplicationExtensionProperty -ApplicationId "<your-app-id>" -BodyParameter $params

– Assign a value to a user (replace user ID and value):

$userId = "[email protected]"
$extensionId = "extension_<app-id>_departmentCode"
Update-MgUser -UserId $userId -AdditionalProperties @{$extensionId = "FIN-42"}

– Verify by querying the user with Graph Explorer:
`GET https://graph.microsoft.com/v1.0/users/[email protected]?$select=id,displayName,extension__departmentCode`
– Security note: Restrict read access using application permissions and conditional access policies. Never put regulated data here.

  1. Custom Security Attributes – The Only Safe Place for Sensitive Data
    Custom security attributes are purpose‑built for storing confidential information like employee IDs, clearance levels, or HR flags. They are not exposed via standard Graph queries—you need explicit `CustomSecAttributeDefinition.Read.All` or `CustomSecAttributeAssignment.Read.All` permissions. Additionally, you can define attribute sets with governance controls (e.g., `Sensitive.HR` set with audit logging).

Step‑by‑step guide to configure a custom security attribute and assign to a user:
– Enable custom security attributes (requires Privileged Role Administrator or Global Admin):

 Connect to Microsoft Graph (beta endpoint required)
Connect-MgGraph -Scopes "CustomSecAttributeDefinition.ReadWrite.All", "User.ReadWrite.All"
Select-MgProfile -Name "beta"

– Create an attribute set (e.g., SensitiveData):

$setParams = @{
Id = "SensitiveData"
Description = "Attributes for highly confidential user data"
MaxAttributesPerSet = 25
}
New-MgDirectoryCustomSecurityAttributeSet -BodyParameter $setParams

– Define a custom security attribute inside the set:

$attrParams = @{
AttributeSet = "SensitiveData"
Id = "HRClearance"
Type = "String"
Multivalue = $false
IsCollection = $false
IsSearchable = $true
UsePreDefinedValuesOnly = $false
}
New-MgDirectoryCustomSecurityAttributeDefinition -BodyParameter $attrParams

– Assign the attribute value to a user (using Graph beta):

$assignBody = @{
SecurityAttributeAssignments = @(
@{
Id = "SensitiveData_HRClearance"
AttributeSet = "SensitiveData"
AttributeName = "HRClearance"
Value = "TopSecret"
}
)
}
Invoke-MgGraphRequest -Method PATCH -Uri "https://graph.microsoft.com/beta/users/[email protected]" -Body $assignBody

– Read the sensitive attribute (requires elevated permissions):

$uri = "https://graph.microsoft.com/beta/users/[email protected]?`$select=id,displayName&`$expand=securityAttributeAssignments"
Invoke-MgGraphRequest -Method GET -Uri $uri

– Best practice: Create a dedicated admin role for managing custom security attributes and audit all assignment changes via Azure Monitor.

3. Three Hidden Storage Methods You Probably Forgot

Beyond directory extensions and custom security attributes, Entra ID offers four additional (but riskier) data storage locations:
– onPremisesExtensionAttributes (1–15) – Inherited from on‑prem AD sync. Can store up to 15 string values but are visible in Graph and easily overlooked.
– Schema extensions (legacy Azure AD Graph only) – Deprecated but still present in some tenants; avoid for new projects.
– Employee data fields (EmployeeId, EmployeeHireDate) – Part of the core schema, but often misused to hold non‑employee data.
– User attributes via Microsoft 365 settings (e.g., UsageLocation, PreferredDataLocation) – Tied to licensing and compliance, not general‑purpose storage.

To check which methods are already storing data in your tenant:

 List all directory extension properties across users (Graph PowerShell)
Get-MgUser -All -Property Id, DisplayName | ForEach-Object {
$user = $_
$extensions = Get-MgUserExtension -UserId $user.Id
if ($extensions) { [bash]@{User=$user.DisplayName;Extensions=$extensions.Values} }
}
  1. Linux & Windows Commands to Audit Exposed User Attributes (API Security)
    Attackers often query Microsoft Graph with compromised tokens to enumerate directory extensions and custom attributes. Use the following commands (Linux with `curl` / Windows with Invoke-RestMethod) to test your own tenant’s exposure.

On Linux (bash with jq):

 Obtain an access token for a test user (using device code flow)
tenant_id="your-tenant-id"
client_id="your-public-client-id"
curl -X POST https://login.microsoftonline.com/$tenant_id/oauth2/v2.0/devicecode \
-d "client_id=$client_id" -d "scope=User.Read Directory.Read.All" -s | jq .

After user signs in, exchange device code for token (store token variable)
token="<access_token>"

Try to read directory extensions of any user (privilege escalation test)
curl -X GET "https://graph.microsoft.com/v1.0/users?`$select=id,displayName,extension_" \
-H "Authorization: Bearer $token" -s | jq '.value[] | {displayName, extensions: .}'

On Windows (PowerShell):

 Test for over‑exposed custom security attributes (requires elevated token)
$token = "<access_token>"
$headers = @{Authorization = "Bearer $token"}
Invoke-RestMethod -Uri "https://graph.microsoft.com/beta/users?`$expand=securityAttributeAssignments" -Headers $headers | ConvertTo-Json -Depth 10

If you see sensitive attributes without proper permissions, your tenant has a data leakage risk. Mitigate by:
– Removing `Directory.Read.All` from applications that don’t need it.
– Using `CustomSecAttributeAssignment.Read.All` scoped only to specific security principals.
– Implementing Conditional Access policies that require MFA for Graph API calls.

  1. Step‑by‑Step: Hardening Entra ID Data Storage Against Insider Threats
    Insiders with legitimate `User.Read.All` can query directory extensions and on‑prem extension attributes. To protect sensitive data without breaking apps:

  2. Inventory all current extensions using `Get-MgApplicationExtensionProperty` for each enterprise app.

  3. Migrate sensitive values from directory extensions to custom security attributes (see section 2).
  4. Block Graph API read access for high‑risk users using Conditional Access “Session control – App enforced restrictions”:
    Create a CA policy to block Graph for non‑admin users (using Microsoft Graph PowerShell)
    $conditions = @{
    Applications = @{IncludeApplications = @("00000003-0000-0000-c000-000000000000")}  Microsoft Graph
    Users = @{ExcludeRoles = @("62e90394-69f5-4237-9190-012177145e10")}  Exclude Global Admins
    }
    New-MgIdentityConditionalAccessPolicy -DisplayName "Block Graph for regular users" -State "enabledForReportingButNotEnforced" -Conditions $conditions -GrantControls @{BuiltInControl="block"}
    
  5. Set up alerts for bulk reads of custom security attributes using Azure Monitor:

    AuditLogs
    | where Category == "CustomSecurityAttribute"
    | where OperationName == "Read custom security attribute"
    | summarize Count = count() by UserPrincipalName, ClientAppUsed
    | where Count > 20
    

  6. Entra ID vs. Azure Key Vault – When to Stop Using User Attributes Altogether
    If you’re storing API secrets, certificates, or database connection strings in any Entra ID user attribute – stop immediately. User attributes are not encrypted at rest with a customer‑managed key (CMK) and appear in audit logs in plain text. Instead, use:

– Azure Key Vault for secrets, with references stored as custom security attribute (just the Key Vault URI).
– Managed Identities for app authentication – never store passwords in user extensions.

Example: Retrieve a secret from Key Vault using an Entra ID user’s custom attribute as a pointer:

 User has custom security attribute "KeyVaultSecretURI" = "https://myvault.vault.azure.net/secrets/db-password"
$uri = (Get-MgUser -UserId $userId -Property "securityAttributeAssignments").securityAttributeAssignments | Where-Object {$_.Id -eq "KeyVaultSecretURI"} | Select-Object -ExpandProperty Value
$secret = (Get-AzKeyVaultSecret -VaultName "myvault" -Name ($uri -split "/")[-1]).SecretValueText

What Undercode Say:

  • Directory extensions are convenient but dangerous for sensitive data – they’re readable by any app with basic Graph permissions, making them a prime target for token theft attacks.
  • Custom security attributes are the only “least privilege” storage – they require explicit permission grants and can be hidden from standard queries, but misconfiguration still leads to leaks (e.g., granting `CustomSecAttributeAssignment.Read.All` to all users).

The core mistake organizations make is treating all six storage methods as interchangeable. In reality, each has a specific security boundary. Directory extensions belong to application-level data; custom security attributes belong to HR‑level secrets; and on‑prem attributes belong to legacy sync. Without a clear classification policy, you’ll inevitably store a Social Security number in a directory extension that an external contractor’s app can read via Graph.

Prediction:

Within 18 months, Microsoft will deprecate directory extensions for storing any personally identifiable information (PII) by introducing automated compliance scanners in Entra ID. These scanners will flag tenants that store sensitive data in non‑custom security attributes and block Graph API access for violating applications. Organizations that fail to migrate their custom user data to custom security attributes or Azure Key Vault will face unexpected service interruptions and audit failures. The attack surface shift will move from token‑based Graph enumeration to targeted abuse of overly permissive `CustomSecAttributeAssignment.Read.All` grants, leading to new detection rules focused on attribute‑level access anomalies.

▶️ Related Video (74% Match):

🎯Let’s Practice For Free:

IT/Security Reporter URL:

Reported By: Nathanmcnulty Did – Hackers Feeds
Extra Hub: Undercode MoN
Basic Verification: Pass ✅

🔐JOIN OUR CYBER WORLD [ CVE News • HackMonitor • UndercodeNews ]

💬 Whatsapp | 💬 Telegram

📢 Follow UndercodeTesting & Stay Tuned:

𝕏 formerly Twitter 🐦 | @ Threads | 🔗 Linkedin | 🦋BlueSky