internal/functions/authentication/Connect-ServiceBrowser.ps1

function Connect-ServiceBrowser {
    <#
    .SYNOPSIS
        Interactive logon using the Authorization flow and browser. Supports SSO.
     
    .DESCRIPTION
        Interactive logon using the Authorization flow and browser. Supports SSO.
 
        This flow requires an App Registration configured for the platform "Mobile and desktop applications".
        Its redirect Uri must be "http://localhost"
 
        On successful authentication
     
    .PARAMETER ClientID
        The ID of the registered app used with this authentication request.
     
    .PARAMETER TenantID
        The ID of the tenant connected to with this authentication request.
     
    .PARAMETER SelectAccount
        Forces account selection on logon.
        As this flow supports single-sign-on, it will otherwise not prompt for anything if already signed in.
        This could be a problem if you want to connect using another (e.g. an admin) account.
     
    .PARAMETER Scopes
        Generally doesn't need to be changed from the default '.default'
 
    .PARAMETER LocalPort
        The local port that should be redirected to.
        In order to process the authentication response, we need to listen to a local web request on some port.
        Usually needs not be redirected.
        Defaults to: 8080
     
    .PARAMETER Resource
        The resource owning the api permissions / scopes requested.
 
    .PARAMETER Browser
        The path to the browser to use for the authentication flow.
        Provide the full path to the executable.
        The browser must accept the url to open as its only parameter.
        Defaults to your default browser.
     
    .PARAMETER NoReconnect
        Disables automatic reconnection.
        By default, this module will automatically try to reaquire a new token before the old one expires.
     
    .EXAMPLE
        PS C:\> Connect-ServiceBrowser -ClientID '<ClientID>' -TenantID '<TenantID>'
     
        Connects to the specified tenant using the specified client, prompting the user to authorize via Browser.
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]
        $TenantID,

        [Parameter(Mandatory = $true)]
        [string]
        $ClientID,

        [Parameter(Mandatory = $true)]
        [string]
        $Resource,

        [switch]
        $SelectAccount,

        [AllowEmptyCollection()]
        [string[]]
        $Scopes,

        [int]
        $LocalPort = 8080,

        [string]
        $Browser,

        [switch]
        $NoReconnect
    )
    process {
        Add-Type -AssemblyName System.Web
        if (-not $Scopes) { $Scopes = @('.default') }

        $redirectUri = "http://localhost:$LocalPort"
        $actualScopes = $Scopes | Resolve-ScopeName -Resource $Resource

        if (-not $NoReconnect) {
            $actualScopes = @($actualScopes) + 'offline_access'
        }

        $uri = "https://login.microsoftonline.com/$TenantID/oauth2/v2.0/authorize?"
        $state = Get-Random
        $parameters = @{
            client_id     = $ClientID
            response_type = 'code'
            redirect_uri  = $redirectUri
            response_mode = 'query'
            scope         = $actualScopes -join ' '
            state         = $state
        }
        if ($SelectAccount) {
            $parameters.prompt = 'select_account'
        }

        $paramStrings = foreach ($pair in $parameters.GetEnumerator()) {
            $pair.Key, ([System.Web.HttpUtility]::UrlEncode($pair.Value)) -join '='
        }
        $uriFinal = $uri + ($paramStrings -join '&')
        Write-PSFMessage -Level Verbose -String 'Connect-ServiceBrowser.AuthorizeUri' -StringValues $uriFinal

        $redirectTo = 'https://raw.githubusercontent.com/FriedrichWeinmann/MiniGraph/master/nothing-to-see-here.txt'
        if ((Get-Random -Minimum 10 -Maximum 99) -eq 66) {
            $redirectTo = 'https://www.youtube.com/watch?v=dQw4w9WgXcQ'
        }
        
        # Start local server to catch the redirect
        $http = [System.Net.HttpListener]::new()
        $http.Prefixes.Add("$redirectUri/")
        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 }

        # Execute in default browser
        if ($Browser) { & $Browser $uriFinal }
        else { Start-Process $uriFinal }

        # Get Result
        $task = $http.GetContextAsync()
        $authorizationCode, $stateReturn, $sessionState = $null
        try {
            while (-not $task.IsCompleted) {
                Start-Sleep -Milliseconds 200
            }
            $context = $task.Result
            $context.Response.Redirect($redirectTo)
            $context.Response.Close()
            $authorizationCode, $stateReturn, $sessionState = $context.Request.Url.Query -split "&"
        }
        finally {
            $http.Stop()
            $http.Dispose()
        }

        if (-not $stateReturn) {
            Invoke-TerminatingException -Cmdlet $PSCmdlet -Message "Authentication failed (see browser for details)" -Category AuthenticationError
        }

        if ($stateReturn -match '^error_description=') {
            $message = $stateReturn -replace '^error_description=' -replace '\+',' '
            $message = [System.Web.HttpUtility]::UrlDecode($message)
            Invoke-TerminatingException -Cmdlet $PSCmdlet -Message "Error processing the request: $message" -Category InvalidOperation
        }

        if ($state -ne $stateReturn.Split("=")[1]) {
            Invoke-TerminatingException -Cmdlet $PSCmdlet -Message "Received invalid authentication result. Likely returned from another flow redirecting to the same local port!" -Category InvalidOperation
        }

        $actualAuthorizationCode = $authorizationCode.Split("=")[1]

        $body = @{
            client_id    = $ClientID
            scope        = $actualScopes -join " "
            code         = $actualAuthorizationCode
            redirect_uri = $redirectUri
            grant_type   = 'authorization_code'
        }
        $uri = "https://login.microsoftonline.com/$TenantID/oauth2/v2.0/token"
        try { $authResponse = Invoke-RestMethod -Method Post -Uri $uri -Body $body -ErrorAction Stop }
        catch {
            if ($_ -notmatch '"error":\s*"invalid_client"') { Invoke-TerminatingException -Cmdlet $PSCmdlet -ErrorRecord $_ }
            Invoke-TerminatingException -Cmdlet $PSCmdlet -Message "The App Registration $ClientID has not been configured correctly. Ensure you have a 'Mobile and desktop applications' platform with redirect to 'http://localhost' configured (and not a 'Web' Platform). $_" -Category $_.CategoryInfo.Category
        }
        Read-AuthResponse -AuthResponse $authResponse
    }
}