OIDC redirect taking a long time

Product: PowerShell Universal
Version: 2.8.0
Authentication: OIDC (AzureAD)
Claims: Group Claims

Hey @adam

We currently have 18 roles and seems to be taking a while to redirect back to PSU. When testing, it take about 20 - 30 seconds to redirect back, but other team members have reporting taking longer. Not sure if the roles would play into that, but giving you an idea.

18 roles
14 AAD groups that go to different roles


image

For the roles, are you using role to claim mapping or policy scripts?

Policy scripts… example:

New-PSURole -Name "Administrator" -Description "Administrators can manage settings of UA, create and edit any entity within UA and view all the entities within UA." -Policy {
    param(
        $User
    )
        
    if ($User.Claims.type -eq '_claim_names') {
        Try {
            $AppSettings = Get-Content -Path "$env:UAPath\appsettings.json" |ConvertFrom-Json
            $ClientId = $AppSettings.Authentication.OIDC.ClientID
            $ClientSecret = $AppSettings.Authentication.OIDC.ClientSecret
            $TenantId = ($User.Claims |Where-Object type -eq 'http://schemas.microsoft.com/identity/claims/tenantid').value
            $UserId = ($User.Claims |Where-Object type -eq 'http://schemas.microsoft.com/identity/claims/objectidentifier').value
            $Params = @{
                Uri = "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token"
                Body = @{
                    'client_id' = $ClientId
                    'client_secret' = $ClientSecret
                    'scope' = 'https://graph.microsoft.com/.default'
                    'grant_type' = 'client_credentials'
                }
                Method = 'Post'
                ContentType = 'application/x-www-form-urlencoded'
                ErrorAction = 'Stop'
            }
            $Token = Invoke-RestMethod @Params |Select-Object -ExpandProperty access_token
            $Params = @{
                Uri = "https://graph.microsoft.com/v1.0/$TenantId/users/$UserId/getMemberObjects"
                Headers = @{
                    Authorization = "Bearer $Token"
                }
                Method = 'Post'
                ContentType = 'application/json'
                Body = (@{
                    securityEnabledOnly = $false
                } |ConvertTo-Json)
            }
            $GroupObjectId = (Invoke-RestMethod @Params).value
            $GroupObjectId -eq '<objectid>'
        }
        Catch {
            throw $_
        }
    }
    else {
        $User.HasClaim('groups', '<objectid>')
    }
} 

Switching to claim mapping made a huge difference.

Thanks!

Cool. Good news! it totally avoids calling PS scripts when doing that.

1 Like

Unfortunately it does not look like I can use the claim mapping :frowning: I am still getting group overage claims since we have users with too many groups.

I’d recommend attempting to cache the extended claims. It’s probably so slow because it needs to make 18x web requests and you could probably reduce that to 1 by cache.

New-PSURole -Name "Administrator" -Description "Administrators can manage settings of UA, create and edit any entity within UA and view all the entities within UA." -Policy {
    param(
        $User
    )
        
    if ($User.Claims.type -eq '_claim_names') {
        # Check cache for claims before executing web request 
        $CacheKey = $User.Identity.Name + "Claims"
        $GroupObjectId = Get-PSUCache -Key $CacheKey
        if ($$GroupObjectId -ne $null)
        {
            $GroupObjectId -eq '<objectid>'
            return
        }

        Try {
            $AppSettings = Get-Content -Path "$env:UAPath\appsettings.json" |ConvertFrom-Json
            $ClientId = $AppSettings.Authentication.OIDC.ClientID
            $ClientSecret = $AppSettings.Authentication.OIDC.ClientSecret
            $TenantId = ($User.Claims |Where-Object type -eq 'http://schemas.microsoft.com/identity/claims/tenantid').value
            $UserId = ($User.Claims |Where-Object type -eq 'http://schemas.microsoft.com/identity/claims/objectidentifier').value
            $Params = @{
                Uri = "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token"
                Body = @{
                    'client_id' = $ClientId
                    'client_secret' = $ClientSecret
                    'scope' = 'https://graph.microsoft.com/.default'
                    'grant_type' = 'client_credentials'
                }
                Method = 'Post'
                ContentType = 'application/x-www-form-urlencoded'
                ErrorAction = 'Stop'
            }
            $Token = Invoke-RestMethod @Params |Select-Object -ExpandProperty access_token
            $Params = @{
                Uri = "https://graph.microsoft.com/v1.0/$TenantId/users/$UserId/getMemberObjects"
                Headers = @{
                    Authorization = "Bearer $Token"
                }
                Method = 'Post'
                ContentType = 'application/json'
                Body = (@{
                    securityEnabledOnly = $false
                } |ConvertTo-Json)
            }
            $GroupObjectId = (Invoke-RestMethod @Params).value

            # Expire shortly afterwards so that on the next login we grab their claims again in case they change
            Set-PSUCache -Key $CacheKey -Value $GroupObjectId -AbsoluteExpirationFromNow ([TimeSpan]::FromSeconds(30))

            $GroupObjectId -eq '<objectid>'
        }
        Catch {
            throw $_
        }
    }
    else {
        $User.HasClaim('groups', '<objectid>')
    }
} 

I’ve made a change to how this will work in 2.8.3. Rather than running the role scripts concurrently (which is what is happening now), they will run in parallel. This should really help the situation even without caching.

1 Like

Thanks for the suggestion! However, if a user doesn’t get the overage claim (my user for example) it should just go to the else statements to get the group object id and shouldn’t need to get any cache correct?

Yep. That’s correct. Is it still really slow without the overage?

Yes. The screen shot in my op is without an overage claim ~20 secs.

Ok and I take it this is new behavior as of 2.8?

No. I believe it started in 2.7.x but I didn’t think anything of it until some end users started asking about it.

Ah, interesting. Do you have a test box you can test a nightly build on? I can add some additional profiling\logging to help narrow down what’s happening as well as include the concurrent evaluation.

Yep. Definitely.

1 Like

Running a nightly build of 2.8 now. I added some more profiling and what I see is the Microsoft Secret Store module is extremely slow. Most of the time executing role evaluations is due to Secret Store access. I’m going to have to update our documentation and open an issue on the Secret Store repo to see if we can figure out what’s going on here. It adds almost a 300ms delay per access to the store. It seems to get worse with more frequent access.

I did make some changes to some threading\synchronization stuff we were doing to guard against multi-access to the secret cmdlets so it should be slightly faster but any use of this module is going to cause performance issues.

I added a bunch of secrets to my environment and you can see that invoking the actual policy takes under 50ms, looking up secrets takes seconds.

I don’t even have that many secrets and am the only one logging in.

1 Like

Gotcha. I’ll be able to test on Friday. But we do have about 10 secrets. I could try and switch back to using Import-Clixml instead of the secrets module as well.

Couldn’t sleep. Reduced login speed from 2000ms to 80ms in my environment by changing how policies are processed to avoid creating multiple runspaces and importing secrets only during environment startup and not during policy processing.

Kicked off a nightly 2.8 build for this.

3 Likes

Awesome! Thanks a lot for taking the time to take a look. Appreciate it man.

Hey good morning @adam

Product: PowerShell Universal
Version: 2.8.3
Build: 1861748841

I’ve downloaded the latest nightly build on v2.8.3 (1861748841) and simulated what we have in production (minus all of the scheduled jobs since our dev is not licensed). Just to give you an idea of our setup:

  • 28 scheduled jobs
  • ~500 jobs run a day
  • 10 dashboards
  • 12 secrets (Stored in Azure KeyVault)
  • 1 secret (Stored locally that registers the keyvault)
  • 18 total roles

Here are the results from logging in with our current policy scripts/overage claim script. If the latest nightly on 2.8.3 included how you processed policies, the login time was insanely fast.

When we tried to use KeyVault previously, the login time would be ~1 min or 2. Please let me know if there is anything else you would like me to test out.

Thanks again!