Conditional Access target resources
Microsoft's edit ·
Microsoft's commit message in MicrosoftDocs/entra-docs · commit 229ce3c · +110 −34 lines · also on GitHub
⋯ 172 unchanged lines
173173
174174
| Example scenario | User impact (before → after) | Conditional Access evaluation |
175175
|---|---|---|
176
−| A user signs into VSCode desktop client, which requests openid and profile scopes. | **Before**: User not prompted for MFA</br>**After**: User is prompted for MFA | Conditional Access is now evaluated using Windows Azure Active Directory as the enforcement audience. |
176
+| A user signs into Visual Studio Code desktop client, which requests openid and profile scopes. | **Before**: User not prompted for MFA</br>**After**: User is prompted for MFA | Conditional Access is now evaluated using Windows Azure Active Directory as the enforcement audience. |
177177
| A user signs in using Azure CLI, which requests only `User.Read`. | **Before**: User not prompted for MFA</br>**After**: User is prompted for MFA | Conditional Access is now evaluated using Windows Azure Active Directory as the enforcement audience. |
178178
| A user signs in through a confidential client application (excluded from the policy) that requests only `User.Read` and `People.Read`. | **Before**: User not prompted for MFA</br>**After**: User is prompted for MFA | Conditional Access is now evaluated using Windows Azure Active Directory as the enforcement audience. |
179179
⋯ 23 unchanged lines
203203
204204
```powershell
205205
# ==============================
206
−# Inventory of tenant-owned apps whose delegated consent grants include ONLY
206
+# Inventory of apps whose delegated consent grants include ONLY
207207
# the OIDC scopes + specific directory scopes listed below.
208208
#
209
+# Enhancements incorporated:
210
+# - Supported both PowerShell 5.1 and 7.x
211
+# - Add user sign-in count (last 7 days) per app
212
+#
209213
# Output:
210214
# - ServicePrincipalObjectId (oauth2PermissionGrants.clientId = SP object id)
215
+# - AppId
216
+# - AppDisplayName
217
+# - AppOwnerOrganizationId (for classification)
211218
# - Scopes (union of delegated scopes granted)
219
+# - UserSigninsLast7Days (Successful + Failed)
212220
# ==============================
213221
214222
$TenantId = Read-Host "Enter your Microsoft Entra tenant ID (GUID)"
⋯ 6 unchanged lines
221229
)
222230
223231
Disconnect-MgGraph -ErrorAction SilentlyContinue
232
+
224233
Connect-MgGraph -TenantId $TenantId -Scopes @(
225234
"DelegatedPermissionGrant.Read.All",
226
− "Directory.Read.All"
235
+ "Directory.Read.All",
236
+ "Reports.Read.All"
227237
)
228238
239
+# ------------------------------
229240
# Pull oauth2PermissionGrants (paging)
230
−$uri = "https://graph.microsoft.com/beta/oauth2PermissionGrants?`$select=clientId,scope"
241
+# ------------------------------
242
+$uri = "https://graph.microsoft.com/beta/oauth2PermissionGrants?`$select=clientId,scope,consentType"
231243
$grants = @()
232244
while ($uri) {
233245
$resp = Invoke-MgGraphRequest -Method GET -Uri $uri
⋯ 1 unchanged line
235247
$uri = $resp.'@odata.nextLink'
236248
}
237249
238
−# Build baseline-only candidate set (paste-safe: no leading pipes)
239
−$candidates = @()
240
−foreach ($g in ($grants | Group-Object clientId)) {
241
− $spObjectId = $g.Name
250
+# ------------------------------
251
+# Build baseline-only candidate set (Jun: HashSet per clientId)
252
+# ------------------------------
253
+$scopesByClient = @{} # key: clientId (SP objectId), value: HashSet[string] (case-insensitive)
242254
243
− $scopes = $g.Group |
244
− ForEach-Object { ($_.scope -split '\s+') } |
245
− Where-Object { $_ -and $_.Trim() -ne "" } |
246
− Sort-Object -Unique
255
+foreach ($g in $grants) {
256
+ $cid = $g.clientId.ToString().Trim()
257
+ if (-not $cid) { continue }
247258
248
− if ($scopes.Count -gt 0) {
249
− $outside = $scopes | Where-Object { $_ -notin $BaselineScopes }
250
− if ($outside.Count -eq 0) {
251
− $candidates += [PSCustomObject]@{
252
− ServicePrincipalObjectId = $spObjectId
253
− Scopes = ($scopes -join " ")
254
− }
259
+ if (-not $scopesByClient.ContainsKey($cid)) {
260
+ $scopesByClient[$cid] = [System.Collections.Generic.HashSet[string]]::new(
261
+ [System.StringComparer]::OrdinalIgnoreCase
262
+ )
263
+ }
264
+
265
+ foreach ($s in ($g.scope -split '\s+')) {
266
+ if ($s -and $s.Trim().Length -gt 0) {
267
+ [void]$scopesByClient[$cid].Add($s.Trim())
255268
}
256269
}
257270
}
258271
272
+$candidates = foreach ($cid in $scopesByClient.Keys) {
273
+ $scopes = $scopesByClient[$cid]
274
+ if ($scopes.Count -le 0) { continue }
275
+
276
+ $outside = $scopes | Where-Object { $_ -notin $BaselineScopes }
277
+ if ($outside.Count -eq 0) {
278
+ [PSCustomObject]@{
279
+ ServicePrincipalObjectId = $cid
280
+ Scopes = ($scopes -join ' ')
281
+ }
282
+ }
283
+}
284
+
285
+# ------------------------------
286
+# Pull per-app sign-in summary for last 7 days (Graph REST via Invoke-MgGraphRequest)
287
+# Endpoint: GET /beta/reports/getAzureADApplicationSignInSummary(period='D7')
288
+# In this API output, 'id' corresponds to the appId (clientId)
289
+# ------------------------------
290
+$signInSummary = @()
291
+$signInUri = "https://graph.microsoft.com/beta/reports/getAzureADApplicationSignInSummary(period='D7')"
292
+
293
+while ($signInUri) {
294
+ $resp = Invoke-MgGraphRequest -Method GET -Uri $signInUri
295
+
296
+ if ($resp -and $resp.value) {
297
+ $signInSummary += $resp.value
298
+ }
299
+
300
+ # Paging (if present)
301
+ $signInUri = $resp.'@odata.nextLink'
302
+}
303
+
304
+# appId -> total sign-ins (7d)
305
+$signInCountByAppId = @{}
306
+foreach ($s in $signInSummary) {
307
+ $appId = $s.id
308
+ if (-not $appId) { continue }
309
+
310
+ # PS5.1-safe null handling
311
+ $success = 0
312
+ $failed = 0
313
+ if ($null -ne $s.successfulSignInCount) { $success = [int]$s.successfulSignInCount }
314
+ if ($null -ne $s.failedSignInCount) { $failed = [int]$s.failedSignInCount }
315
+
316
+ $signInCountByAppId[$appId] = $success + $failed
317
+}
318
+
259319
$resultsTenantOwned = @()
260320
$resultsNotTenantOwned = @()
261321
262
−# Filter to tenant-owned or external apps
322
+# ------------------------------
323
+# Filter to tenant-owned or external apps; enrich with appId/displayName + sign-in counts
324
+# ------------------------------
263325
foreach ($c in $candidates) {
264326
try {
265
− $spUri = "https://graph.microsoft.com/beta/servicePrincipals/$($c.ServicePrincipalObjectId)?`$select=id,appOwnerOrganizationId"
327
+ $spUri = "https://graph.microsoft.com/beta/servicePrincipals/$($c.ServicePrincipalObjectId)?`$select=id,appId,displayName,appOwnerOrganizationId"
266328
$sp = Invoke-MgGraphRequest -Method GET -Uri $spUri
267329
330
+ $signinCount7d = 0
331
+ if ($sp.appId -and $signInCountByAppId.ContainsKey($sp.appId)) {
332
+ $signinCount7d = $signInCountByAppId[$sp.appId]
333
+ }
334
+
335
+ $row = [PSCustomObject]@{
336
+ ServicePrincipalObjectId = $c.ServicePrincipalObjectId
337
+ AppId = $sp.appId
338
+ AppDisplayName = $sp.displayName
339
+ AppOwnerOrganizationId = $sp.appOwnerOrganizationId
340
+ Scopes = $c.Scopes
341
+ UserSigninsLast7Days = $signinCount7d
342
+ }
343
+
268344
if ($sp.appOwnerOrganizationId -eq $TenantId) {
269
− $resultsTenantOwned += [PSCustomObject]@{
270
− ServicePrincipalObjectId = $c.ServicePrincipalObjectId
271
− Scopes = $c.Scopes
272
− }
345
+ $resultsTenantOwned += $row
273346
}
274347
else {
275
− $resultsNotTenantOwned += [PSCustomObject]@{
276
− ServicePrincipalObjectId = $c.ServicePrincipalObjectId
277
− Scopes = $c.Scopes
278
− }
348
+ $resultsNotTenantOwned += $row
279349
}
280350
}
281351
catch {
282
− # Ignore non-enumerable / non-tenant-owned service principals
352
+ # Ignore non-enumerable / missing service principals
283353
}
284354
}
285355
356
+# ------------------------------
286357
# Output
287
−'=== All tenant-owned apps whose delegated consent grants include ONLY the OIDC scopes + specific directory scopes ==='
288
−$resultsTenantOwned | Sort-Object ServicePrincipalObjectId
358
+# ------------------------------
359
+'=== Tenant-owned apps whose delegated consent grants include ONLY baseline scopes + user sign-ins (last 7 days) ==='
360
+$resultsTenantOwned |
361
+ Sort-Object UserSigninsLast7Days -Descending |
362
+ Format-Table -AutoSize
289363
290
−'=== All external apps whose delegated consent grants include ONLY the OIDC scopes + specific directory scopes ==='
291
−$resultsNotTenantOwned | Sort-Object ServicePrincipalObjectId
364
+'=== External apps whose delegated consent grants include ONLY baseline scopes + user sign-ins (last 7 days) ==='
365
+$resultsNotTenantOwned |
366
+ Sort-Object UserSigninsLast7Days -Descending |
367
+ Format-Table -AutoSize
292368
```
293369
294370
#### [Usage and Insights report](#tab/usage-and-insights-report)
⋯ 120 unchanged lines
Microsoft's Markdown source from MicrosoftDocs/entra-docs, © Microsoft Corporation, under MIT. Changed lines with up to 3 unchanged lines around each; ChangeIntel kept this copy 10 Oct 04:32 UTC. The commit date is when the source changed, which can be hours or days before Learn published it.