Hi-
I'm not sure if this is a bug or if my configuration is wrong, but I've gone through the setup described in the Using with MAUI page, but my applications are spawning a new window and I'm not able to intercept the access token.
I'm using .NET 10.
This would happen if you miss the startup code or if your oauth service doesn’t preserve the State parameter per the oauth spec.
See https://dotmorten.github.io/WinUIEx/concepts/Maui.html#use-winuiexs-webauthenticator-instead-of-net-mauis
I followed those steps and still the same result.
<img width="1117" height="610" alt="Image" src="https://github.com/user-attachments/assets/74276960-b76b-4e65-a3b1-50e729875109" />
<img width="1341" height="523" alt="Image" src="https://github.com/user-attachments/assets/465797c1-14c8-4b66-a936-75198718a245" />
<img width="1210" height="672" alt="Image" src="https://github.com/user-attachments/assets/5c4edddb-9750-42f6-825f-a6bbf7680431" />
```
using Microsoft.Extensions.Logging;
using CommunityToolkit.Maui;
using Esri.ArcGISRuntime.Maui;
using Esri.ArcGISRuntime.Toolkit.Maui;
using Klyk.Mobile;
using Microsoft.Maui.LifecycleEvents;
#if WINDOWS
using WinUIEx;
#endif
namespace PrecisionFarms
{
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder.UseMauiApp<App>().ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
})
.UseMauiCommunityToolkit()
.UseArcGISRuntime()
.UseArcGISToolkit().ConfigureApp();
#if WINDOWS
builder.ConfigureLifecycleEvents(events =>
{
events.AddWindows(wndLifeCycleBuilder =>
{
wndLifeCycleBuilder.OnWindowCreated(window =>
{
window.CenterOnScreen(1024,768);
var manager = WinUIEx.WindowManager.Get(window);
manager.PersistenceId = "MainWindowPersistanceId"; // Remember window position and size across runs
manager.MinWidth = 640;
manager.MinHeight = 480;
});
});
});
#endif
#if DEBUG
builder.Logging.AddDebug();
#endif
return builder.Build();
}
}
}
```
I'm using asp.net core authentication provider and don't see any options about preserving state. Do you have any ideas on how to check to see if it's mangling the state parameter?
```
var google = authenticationSection.GetSection("Google");
if (google.Exists())
{
authentication.AddGoogle(options =>
{
options.ClientId = google["ClientId"];
options.ClientSecret = google["ClientSecret"];
options.SaveTokens = true;
//options.Events.OnTicketReceived += TokenUtils.OnTicketRecieved;
});
}
```
I wouldn't know anything about that, but you could hook it up with the source code here and inspect what is happening here: https://github.com/dotMorten/WinUIEx/blob/123947d3be7cecf0a9716b9e5f6648818d682cc7/src/WinUIEx/WebAuthenticator.cs#L178
On first load it hits line 183 on the WebAuthenticator class to return false. Once you try logging in, the CheckOAuthRedirectionActivation() in the "App" never gets called.
Not sure what you mean. It seems like adding this should intercept the redirect from the browser, but maybe I'm using this in the wrong place? The browser does open the appropriate app, but not the active one.
```
public App()
{
if (WinUIEx.WebAuthenticator.CheckOAuthRedirectionActivation())
{
return;
}
this.InitializeComponent();
}
```
Yes it’ll open a new instance and hit this code and should then forward the info to the existing instance and shut this instance down.
If you see a new instance launching it would have to be hitting this app code
Check the above linked issue. It could be related to having multiple instances running at the same time. If not the issue with your debugging is likely that Visual Studio isn't set up to also debug newly launched instances. You could also add a call to `System.Diagnostics.Debugger.Launch()` instead of setting a breakpoint to force the load. Closing for now but feel free to re-open if you find out more details.
Thanks for the help. I don't think it's visual studio, because the sample app works. The appInstanceId is populated in the url, and I don't think that has anything to do with it. I'm at a loss at this point and not sure what to do to get it working.
@dotMorten So, I did a bit of a dissection of the library to figure out what was going on. Turns out the solution was a pretty simple. The default .NET MAUI WebAuthenticator doesn't pass any "state", and I was thinking the "state" you mentioned was the authentication state that ASP.NET Core uses.
What I needed to do on the server side was to extract the state WinUIEx sends which has the appInstanceId and the signinId and return that back to the client in the query string variables. Not sure if you feel that deserves some documentation or not. I'm using a pretty standard implementation of AspNet.Security.OAuth.ArcGIS and it could help future users integrate with Windows.
That's fair. You would think Microsoft would provide that in their sample if that was going create a problem using Windows. The service works fine with iOS, Android and MacCatalyst.
Those platforms are all have single-instance apps and doesn’t need it to resume the correct instance. You could also configure the windows app to be single instance
I realize they are single instance apps, but if you're a person who isn't an expert in all things oauth/windows you're not going to know that you need to round-trip the state parameter for it to work properly.
If you prompt Copilot to write a controller for mobile authentication it doesn't round-trip the state. Gemini returns a similar implementation.
If there was a small blurb in the README that demonstrated how this WebAuthenticator needs the state to function properly it would have saved me a lot of time. If it was there then I assume AI would have given me a heads up when it recommended using this library.
```
[Route("mobileauth")]
public class MobileAuthController : Controller
{
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly UserManager<ApplicationUser> _userManager;
private readonly ITokenService _tokenService; // your JWT generator
public MobileAuthController(
SignInManager<ApplicationUser> signInManager,
UserManager<ApplicationUser> userManager,
ITokenService tokenService)
{
_signInManager = signInManager;
_userManager = userManager;
_tokenService = tokenService;
}
// ---------------------------------------------------------
// 1. Start external login (Google, Apple, etc.)
// ---------------------------------------------------------
[HttpGet("{provider}")]
public IActionResult Start(string provider, string callback, int appId)
{
var redirectUrl = Url.Action(nameof(ExternalCallback), "MobileAuth",
new { callback, appId });
var props = _signInManager.ConfigureExternalAuthenticationProperties(
provider, redirectUrl);
return Challenge(props, provider);
}
// ---------------------------------------------------------
// 2. Provider returns here (Google → /signin-google → this)
// ---------------------------------------------------------
[HttpGet("callback")]
public async Task<IActionResult> ExternalCallback(string callback, int appId)
{
// Read external login info
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
return BadRequest("External login failed");
// Sign in or create user
var result = await _signInManager.ExternalLoginSignInAsync(
info.LoginProvider,
info.ProviderKey,
isPersistent: false);
ApplicationUser user;
if (!result.Succeeded)
{
// Create user if needed
user = new ApplicationUser
{
Email = info.Principal.FindFirstValue(ClaimTypes.Email),
UserName = info.Principal.FindFirstValue(ClaimTypes.Email)
};
await _userManager.CreateAsync(user);
await _userManager.AddLoginAsync(user, info);
}
else
{
user = await _userManager.FindByLoginAsync(
info.LoginProvider,
info.ProviderKey);
}
// ---------------------------------------------------------
// 3. Issue your own JWT
// ---------------------------------------------------------
var jwt = _tokenService.CreateToken(user);
// ---------------------------------------------------------
// 4. Redirect back to MAUI app
// ---------------------------------------------------------
var uri = $"{callback}://auth?token={jwt}&appId={appId}";
return Redirect(uri);
}
}
```
I get that, but I'm going to keep it focus on documenting the client-side of things. I'd rather not get into also documenting how to build an oauth-service that follows the standard. The state parameter isn't only for resuming a multi-instance app. It can be used for a lot of things.
You could consider opening an issue in AspNet.Security.OAuth.ArcGIS to ensure they handle the state parameter and document it correctly.
I don't think it needs to be a full document, but just a note that says this library relies on the state parameter to open the appropriate app instance since that's how it differs in scope from the built in WebAuthenticator, which would be considered the client-side of things.