Hey Maxime (and Will!),
So “UE_HORDE_TOKEN” will be utilized within the context of a build machine, within the context of a Horde Agent as the runtime wrapper. Now, the aforementioned jwyExpirtTimeHours is indeed used by the “UE_HORDE_TOKEN” code path, as well as anyone requesting a token via the /api/v1/admin/token rest endpoint.
>it was my understanding that the UE_HORDE_TOKEN was inserted for *Horde Agents* performing a build job, distinct from say a client/IDE without a horde agent installed running UBA
- This is correct. I can see where my initial message was perhaps a bit confusing (I’ve since edited it for clarity)!
Apologies in advance for some verbosity, - I want to document some of the control flow here for verbosity. Just thinking aloud (still getting familiarized with these other use cases of OIDC), I’m curious why the interactive path is not being executed on earlier, and instead, culminating in a stale token so late. I’d be curious to get your debug log output to get a sense of what’s going on here, but below there is a bit of my investigation.
The callstack that I’m investigating is this:
> EpicGames.Horde.dll!EpicGames.Horde.HordeHttpAuthHandlerState.GetAccessTokenAsync(bool interactive, System.Threading.CancellationToken cancellationToken) Line 294 C# EpicGames.Horde.dll!EpicGames.Horde.HordeHttpAuthHandler.SendAsync(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) Line 63 C# System.Net.Http.dll!System.Net.Http.HttpClient.SendAsync.__Core|83_0(System.Net.Http.HttpRequestMessage request, System.Net.Http.HttpCompletionOption completionOption, System.Threading.CancellationTokenSource cts, bool disposeCts, System.Threading.CancellationTokenSource pendingRequestsCts, System.Threading.CancellationToken originalCancellationToken) Line 530 C# System.Net.Http.dll!System.Net.Http.HttpClient.SendAsync(System.Net.Http.HttpRequestMessage request, System.Net.Http.HttpCompletionOption completionOption, System.Threading.CancellationToken cancellationToken) Line 518 C# System.Net.Http.Json.dll!System.Net.Http.Json.HttpClientJsonExtensions.FromJsonAsyncCore<EpicGames.Horde.Server.GetServerInfoResponse, System.Text.Json.JsonSerializerOptions>(System.Func<System.Net.Http.HttpClient, System.Uri, System.Threading.CancellationToken, System.Threading.Tasks.Task<System.Net.Http.HttpResponseMessage>> getMethod, System.Net.Http.HttpClient client, System.Uri requestUri, System.Func<System.IO.Stream, System.Text.Json.JsonSerializerOptions, System.Threading.CancellationToken, System.Threading.Tasks.ValueTask<EpicGames.Horde.Server.GetServerInfoResponse>> deserializeMethod, System.Text.Json.JsonSerializerOptions jsonOptions, System.Threading.CancellationToken cancellationToken) Line 61 C# System.Net.Http.Json.dll!System.Net.Http.Json.HttpClientJsonExtensions.FromJsonAsyncCore<EpicGames.Horde.Server.GetServerInfoResponse>(System.Func<System.Net.Http.HttpClient, System.Uri, System.Threading.CancellationToken, System.Threading.Tasks.Task<System.Net.Http.HttpResponseMessage>> getMethod, System.Net.Http.HttpClient client, System.Uri requestUri, System.Text.Json.JsonSerializerOptions options, System.Threading.CancellationToken cancellationToken) Line 25 C# System.Net.Http.Json.dll!System.Net.Http.Json.HttpClientJsonExtensions.GetFromJsonAsync<EpicGames.Horde.Server.GetServerInfoResponse>(System.Net.Http.HttpClient client, System.Uri requestUri, System.Text.Json.JsonSerializerOptions options, System.Threading.CancellationToken cancellationToken) Line 39 C#
With the following being a dispatched background task:
- GetAuthStateHandlerAsync
- GetAuthStateInternalAsync // I wonder if in this method, we could do a better check to see whether the token is stale, and could use a refresh through either poking the OIDCProvider or an "result = await oidcTokenManager.LoginAsync(oidcProvider, cancellationToken);"
This is before the AddWorkerAsync invocation, and there is a lot going on in here with respect to the WindowsTokenStore (which could contain a refresh token), and acquiring the “session” token. Furthermore, placing a breakpoint inside of the OidcTokenClient constructor gives us the opportunity to track the token store, and CreateTokenManager to see how the providers are passed in.
Just to revisit this statement, and contrast it with my workflow:
- “I’d like to know how to obtain a long-term token so I don’t have to update the BuildConfiguration.xml file every time I run UAT”
- Mine: I occasionally get an interactive auth window that requires me to re-auth with our Horde server; which I receive a refreshed token
Just highlighting some of the most relevant code to the above flow:
`// From HordeHttpAuthHandler.cs - GetAuthStateInternalAsync
OidcTokenInfo? result = null;
if (oidcTokenManager.GetStatusForProvider(oidcProvider) != OidcStatus.NotLoggedIn)
{
try
{
result = await oidcTokenManager.TryGetAccessToken(oidcProvider, cancellationToken);
}
catch (Exception ex)
{
_logger.LogTrace(ex, “Unable to get access token; attempting login: {Message}”, ex.Message);
}
}
if (result == null && interactive)
{
_logger.LogInformation(“Logging in to {Server}…”, _serverUrl);
result = await oidcTokenManager.LoginAsync(oidcProvider, cancellationToken);
}
return new AuthState(_clock, authConfig.Method, result, interactive);`* OidcTokenManager.TryDoRefreshTokenAsync is invoked by HordeHttpAuthHandler.TryGetAccessToken
+ I’d expect your token to get refreshed here if it goes down this path
+ Just reading the code, it looks like we are enacting the _redirectUris
This has me wondering if for your azure provider flow, refreshing is just not working as expected? I would expect the refresh to fail, and then fallback to an interactive login. What happens when you blow away your oidcTokenStore.dat from C:\Users\…\AppData\Local\UnrealEngine\Common\OidcToken?
Kind regards,
Julian