Providers/Entra/Private/Get-EntraTokenRole.ps1
|
function Get-EntraTokenRole { <# .SYNOPSIS Reads the application permissions out of an access token's roles claim .DESCRIPTION An app-only Graph token carries its granted application permissions in the 'roles' claim. Reading them locally is how this module can tell you what it is allowed to do before it tries, rather than discovering it one 403 at a time in the middle of a run. The claims segment is decoded, not verified. That is correct here and would not be anywhere else: the token was just issued to this process by Entra over TLS and is being used to describe itself, not to authorise anything. Nothing downstream trusts this output for an access decision. A token that grants nothing has no 'roles' claim at all rather than an empty one, which is worth knowing because it is what a brand new app registration with consent never granted looks like. .PARAMETER AccessToken The raw JWT .OUTPUTS System.String[]. The granted application permissions, sorted, or an empty array. .EXAMPLE PS> Get-EntraTokenRole -AccessToken $token DESCRIPTION: Lists the application permissions carried by the token OUTPUT: Directory.Read.All, Group.ReadWrite.All, User.ReadWrite.All USE CASE: Telling the caller up front which seed steps will be refused .NOTES Author: Jeffrey Stuhr Blog: https://www.techbyjeff.net LinkedIn: https://www.linkedin.com/in/jeffrey-stuhr-034214aa/ #> [CmdletBinding()] [OutputType([string[]])] param( [Parameter(Mandatory = $true)] [string]$AccessToken ) $segments = $AccessToken.Split('.') if ($segments.Count -lt 2) { Write-Verbose "Access token is not a three-part JWT; cannot read its roles." return @() } try { $json = [System.Text.Encoding]::UTF8.GetString((ConvertFrom-TestBase64Url -Text $segments[1])) $claims = $json | ConvertFrom-Json } catch { Write-Verbose "Could not decode the token claims: $($_.Exception.Message)" return @() } # Two different claims, because the two grant types describe permissions differently. # An app-only token carries 'roles' as an array of application permissions. A delegated # token carries 'scp' as a single SPACE-SEPARATED STRING of scopes, so reading only # 'roles' reports an interactive session as having no permissions at all. if ($claims.PSObject.Properties['roles'] -and $claims.roles) { return @($claims.roles | Sort-Object) } if ($claims.PSObject.Properties['scp'] -and $claims.scp) { return @([string]$claims.scp -split '\s+' | Where-Object { $_ } | Sort-Object) } return @() } |