diff --git a/EntraAuth/EntraAuth.psd1 b/EntraAuth/EntraAuth.psd1 index cba0e1f..2d39177 100644 --- a/EntraAuth/EntraAuth.psd1 +++ b/EntraAuth/EntraAuth.psd1 @@ -4,7 +4,7 @@ RootModule = 'EntraAuth.psm1' # Version number of this module. -ModuleVersion = '1.8.46' +ModuleVersion = '1.8.49' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/EntraAuth/changelog.md b/EntraAuth/changelog.md index 7e91f50..48a91d8 100644 --- a/EntraAuth/changelog.md +++ b/EntraAuth/changelog.md @@ -1,5 +1,11 @@ # Changelog +## 1.8.49 (2025-07-21) + ++ New: Federation Provider EntraMSI - added a default federation provider for Managed Identities in Entra. ++ Upd: Invoke-EntraRequest - added parameter `-ContentType` ++ Upd: Connect-EntraService - now supports custom RedirectUris for AuthorizationCode flow + ## 1.8.46 (2025-07-11) + New: Register-EntraFederationProvider - Register logic to automatically retrieve the assertion needed in a Federated Credential authentication flow. diff --git a/EntraAuth/functions/Authentication/Connect-EntraService.ps1 b/EntraAuth/functions/Authentication/Connect-EntraService.ps1 index 060a4aa..3694682 100644 --- a/EntraAuth/functions/Authentication/Connect-EntraService.ps1 +++ b/EntraAuth/functions/Authentication/Connect-EntraService.ps1 @@ -25,6 +25,10 @@ Use an interactive logon in your default browser. This is the default logon experience. + .PARAMETER RedirectUri + The Redirect URI to send with the request. + May be different from the actual local port you want to listen on (e.g. when redirecting to an alternative name on top of localhost). + .PARAMETER BrowserMode How the browser used for authentication is selected. Options: @@ -248,6 +252,10 @@ [switch] $Browser, + [Parameter(ParameterSetName = 'Browser')] + [string] + $RedirectUri, + [Parameter(ParameterSetName = 'Browser')] [ValidateSet('Auto', 'PrintLink')] [string] @@ -473,13 +481,14 @@ if (-not $Scopes) { $scopesToUse = $serviceObject.DefaultScopes } Write-Verbose "[$serviceName] Connecting via Browser ($($scopesToUse -join ', '))" - try { $result = Connect-ServiceBrowser @commonParam -SelectAccount -Scopes $scopesToUse -NoReconnect:$($serviceObject.NoRefresh) -BrowserMode $BrowserMode -ErrorAction Stop } + try { $result = Connect-ServiceBrowser @commonParam -SelectAccount -Scopes $scopesToUse -NoReconnect:$($serviceObject.NoRefresh) -BrowserMode $BrowserMode -RedirectUri $RedirectUri -ErrorAction Stop } catch { Write-Warning "[$serviceName] Failed to connect: $_" $PSCmdlet.ThrowTerminatingError($_) } $token = [EntraToken]::new($serviceName, $ClientID, $TenantID, $effectiveServiceUrl, $false, $authUrl) + $token.RedirectUri = $RedirectUri $token.SetTokenMetadata($result) Write-Verbose "[$serviceName] Connected via Browser ($($token.Scopes -join ', '))" } diff --git a/EntraAuth/functions/Core/Invoke-EntraRequest.ps1 b/EntraAuth/functions/Core/Invoke-EntraRequest.ps1 index 48a0af1..4f4ab8c 100644 --- a/EntraAuth/functions/Core/Invoke-EntraRequest.ps1 +++ b/EntraAuth/functions/Core/Invoke-EntraRequest.ps1 @@ -29,6 +29,10 @@ .PARAMETER Header Any additional headers to include on top of authentication and content-type. + + .PARAMETER ContentType + Specify the content-type of this request. + Equivalent to specifying it as a header entry, but added as dedicated parameter for user convenience. .PARAMETER Service Which service to execute against. @@ -74,6 +78,9 @@ [hashtable] $Header = @{}, + + [string] + $ContentType, [ArgumentCompleter({ Get-ServiceCompletion $args })] [ValidateScript({ Assert-ServiceName -Name $_ -IncludeTokens })] @@ -129,6 +136,7 @@ } $serviceObject = $script:_EntraEndpoints.$($tokenObject.Service) + if ($ContentType) { $Header['Content-Type'] = $ContentType } } process { $parameters = @{ diff --git a/EntraAuth/internal/classes/99-EntraToken.ps1 b/EntraAuth/internal/classes/99-EntraToken.ps1 index b58006b..093f0a8 100644 --- a/EntraAuth/internal/classes/99-EntraToken.ps1 +++ b/EntraAuth/internal/classes/99-EntraToken.ps1 @@ -23,6 +23,9 @@ [string]$IdentityID [string]$IdentityType + + # Workflow: Browser + [string]$RedirectUri # Workflow: Client Secret [System.Security.SecureString]$ClientSecret @@ -212,7 +215,7 @@ return } - $result = Connect-ServiceBrowser @defaultParam -SelectAccount + $result = Connect-ServiceBrowser @defaultParam -SelectAccount -RedirectUri $this.RedirectUri $this.SetTokenMetadata($result) } Refresh { diff --git a/EntraAuth/internal/functions/authentication/Connect-ServiceBrowser.ps1 b/EntraAuth/internal/functions/authentication/Connect-ServiceBrowser.ps1 index ad7f921..717d540 100644 --- a/EntraAuth/internal/functions/authentication/Connect-ServiceBrowser.ps1 +++ b/EntraAuth/internal/functions/authentication/Connect-ServiceBrowser.ps1 @@ -34,6 +34,10 @@ .PARAMETER Resource The resource owning the api permissions / scopes requested. + .PARAMETER RedirectUri + The Redirect URI to send with the request. + May be different from the actual local port you want to listen on (e.g. when redirecting to an alternative name on top of localhost). + .PARAMETER Browser The path to the browser to use for the authentication flow. Provide the full path to the executable. @@ -83,6 +87,10 @@ [int] $LocalPort = 8080, + [AllowEmptyString()] + [string] + $RedirectUri, + [string] $Browser, @@ -102,7 +110,8 @@ Add-Type -AssemblyName System.Web if (-not $Scopes) { $Scopes = @('.default') } - $redirectUri = "http://localhost:$LocalPort" + $localUri = "http://localhost:$LocalPort" + if (-not $RedirectUri) { $RedirectUri = $localUri } $actualScopes = $Scopes | Resolve-ScopeName -Resource $Resource if (-not $NoReconnect) { @@ -114,7 +123,7 @@ $parameters = @{ client_id = $ClientID response_type = 'code' - redirect_uri = $redirectUri + redirect_uri = $RedirectUri response_mode = 'query' scope = $actualScopes -join ' ' state = $state @@ -138,7 +147,8 @@ # Start local server to catch the redirect $http = [System.Net.HttpListener]::new() - $http.Prefixes.Add("$redirectUri/") + $http.Prefixes.Add(("$RedirectUri/" -replace '//$', '/')) + if ($localUri -ne $RedirectUri) { $http.Prefixes.Add(("$localUri/" -replace '//$', '/')) } try { $http.Start() } catch { Invoke-TerminatingException -Cmdlet $PSCmdlet -Message "Failed to create local http listener on port $LocalPort. Use -LocalPort to select a different port. $_" -Category OpenError } @@ -211,7 +221,7 @@ $uriFinal client_id = $ClientID scope = $actualScopes -join " " code = $actualAuthorizationCode - redirect_uri = $redirectUri + redirect_uri = $RedirectUri grant_type = 'authorization_code' } $uri = "$AuthenticationUrl/$TenantID/oauth2/v2.0/token" diff --git a/EntraAuth/internal/scripts/03-FederationProviders.ps1 b/EntraAuth/internal/scripts/03-FederationProviders.ps1 index 2ee3d33..14b7cd0 100644 --- a/EntraAuth/internal/scripts/03-FederationProviders.ps1 +++ b/EntraAuth/internal/scripts/03-FederationProviders.ps1 @@ -27,4 +27,20 @@ https://docs.github.com/en/actions/how-tos/writing-workflows/choosing-what-your- $response.Value } } -Register-EntraFederationProvider @param \ No newline at end of file +Register-EntraFederationProvider @param + +$param = @{ + Name = 'EntraMSI' + Description = 'Authenticate as the Managed Identity in the current context.' + Priority = 20 + Test = { + try { + $null = Connect-EntraService -Identity -Resource 'api://AzureADTokenExchange' -ErrorAction Stop + $true + } + catch { $false } + } + code = { + (Connect-EntraService -Identity -Resource 'api://AzureADTokenExchange').AccessToken + } +} \ No newline at end of file