Select to view content in your preferred language

oAuth2 GenerateCredentialAsync for ArcGIS Online throws javascript errors all of a sudden

912
9
Jump to solution
08-22-2024 07:17 AM
PierreMasson
Regular Contributor

We<re building a WPF windows app with ArcGIS Maps SDK .Net. First thing we did was put in place oAuth2 authentification for our partal and our ArcGIS Online. That was working fine for the last 10 months till yeaterday. Then this morning, when we try to login to our arcGIS Online the login window appear and javascript error message pops over it. Then the call to AuthenticationManager.Current.GenerateCredentialAsync(info.ServiceUri); fails...

When we connect to our portal (the ArcGIS server that we host on our server) it works fine.

What changed between yesterday and today?

Please help.

0 Kudos
1 Solution

Accepted Solutions
dotMorten_esri
Esri Notable Contributor

The fix is now live. Thank you for reporting this!

View solution in original post

9 Replies
MichaelBranscomb
Esri Frequent Contributor

Hi,

I'm not aware of any current issues ArcGIS Online, and having just tried the OAuth workflow with ArcGIS Online in a WPF app, it worked as expected. 

Can you share more info about your setup/project?

  • Is it reproducible on multiple machines in your team/company?
  • What version of the SDK are you referencing?
  • Are you targeting .NET or .NET Framework?
  • What version of the .NET platform/runtime you're using is on the machines where you see the issue?
  • What embedded browser control are you using in your OAuth dialog?

 

Thanks

0 Kudos
dotMorten_esri
Esri Notable Contributor

I can reproduce the issue. Looks like the oauth dialog made a change making it incompatible with WPF's default Internet Explorer browser.
I've started an issue internally to see if we can't get that fixed.

You could look into using WebView2 instead of the default browser control which doesn't seem to be affected.

PierreMasson
Regular Contributor

Thanks, hope they will resolve this soon. Meanwhile do you have a sample of the WebView2 use?

0 Kudos
MichaelBranscomb
Esri Frequent Contributor

Here's an example using WebView2:

 

public class OAuthAuthorizeHandlerWpf : IOAuthAuthorizeHandler
{
    // Embedded Browser: Use a System.Windows.Window to host the sign-in UI provided by the server.
    private Window? authWindow;

    // Use a TaskCompletionSource to track the completion of the authorization.
    private TaskCompletionSource<IDictionary<string, string>>? taskCompletionSource;
    
    // URL for the authorization callback result (the redirect URI configured for your application).
    private string? redirectUrl;

    // Function to initiate an authorization request.
    // It takes the URIs for: the secured service, the authorization endpoint, and the redirect URI.
    public Task<IDictionary<string, string>> AuthorizeAsync(Uri serviceUri, Uri authorizeUri, Uri redirectUri)
    {
        // Don't start an authorization request if one is still in progress.
        if (taskCompletionSource != null && !taskCompletionSource.Task.IsCompleted)
        { throw new Exception("Authentication request already in progress."); }

        // Instantiate the TaskCompletionSource to track the completion of the authorization.
        taskCompletionSource = new TaskCompletionSource<IDictionary<string, string>>();

        // Store the authorization and redirect URLs.
        redirectUrl = redirectUri.AbsoluteUri;

        // Show the sign-in page (schedule to be run on the main UI thread).
        Dispatcher? dispatcher = Application.Current.Dispatcher;
        if (dispatcher == null || dispatcher.CheckAccess())
        {
            // Currently on the UI thread, no need to dispatch.
            ShowLoginWindow(authorizeUri);
        }
        else
        {
            // AuthorizeAsync was called on a separate thread, dispatch to the UI thread.
            Action? authorizeOnUIAction = () => ShowLoginWindow(authorizeUri);
            dispatcher.BeginInvoke(authorizeOnUIAction);
        }

        // Return the task associated with the TaskCompletionSource.
        return taskCompletionSource.Task;
    }

    // A function to show a sign-in page hosted at the specified Url.
    private void ShowLoginWindow(Uri authorizeUri)
    {
        // Instantiate a Microsoft Edge (Chromium) WebView2 control.
        // See: https://learn.microsoft.com/en-us/microsoft-edge/webview2/
        // Note: When releasing an app that uses Microsoft Edge WebView2, you need distribute the WebView2 Runtime, either:
        // - By distributing the automatically updated Evergreen Runtime or;
        // - By distributing a Fixed Version of the Runtime.
        // See-also: https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution
        WebView2? webView2 = new();

        // Display the web browser in a new window.
        authWindow = new Window
        {
            Icon = null,
            Title = authorizeUri.Authority,
            Content = webView2,
            Width = 400,
            Height = 500,
            WindowStartupLocation = WindowStartupLocation.CenterOwner
        };

        // Set the app as the owner of the authentication dialog window.
        if (Application.Current != null && Application.Current.MainWindow != null)
        {
            authWindow.Owner = Application.Current.MainWindow;
        }

        // Handle the window closed event.
        authWindow.Closed += OnWindowClosed;

        // Set the Source property of the WebView2 to the Authorize URI (e.g. {https://www.arcgis.com/sharing/oauth2/authorize?response_type=code...).
        webView2.Source = authorizeUri;

        // Handle the WebView2 NavigationStarting event in which we'll check if the destination URL is our Redirect URI and then process.
        webView2.NavigationStarting += WebView2_NavigationStarting;

        // Show the dialog to the user.
        authWindow.ShowDialog();
    }

    private void WebView2_NavigationStarting(object? sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationStartingEventArgs e)
    {
        // Check for nulls/empties.
        if (sender == null || sender is not WebView2 || string.IsNullOrEmpty(e.Uri))
            return;

        if (redirectUrl != null)
        {
            // Check if browser was redirected to the Redirect URI (indicates successful authentication).
            if (new Uri(e.Uri).AbsoluteUri.StartsWith(redirectUrl))
            {
                // Cancel the page navigation.
                e.Cancel = true;

                // Call a function to parse key/value pairs from the response URI and set the result for the task completion source with the returned dictionary.
                taskCompletionSource?.SetResult(DecodeParameters(new Uri(e.Uri)));

                // Close the window.
                authWindow?.Close();
            }
        }
    }

    // A function to parse key/value pairs from the provided URI.
    private static Dictionary<string, string> DecodeParameters(Uri uri)
    {
        string? str = string.Empty;
        if (!string.IsNullOrEmpty(uri.Fragment))
            str = uri.Fragment[1..];
        else if (!string.IsNullOrEmpty(uri.Query))
            str = uri.Query[1..]; ;

        // Create new dictionary of key value string pairs to hold the return values.
        Dictionary<string, string> keyValueDictionary = [];

        /*
            * .NET 6 and above includes convenient HttpUtility class.
            */
        // Parse the URI query string into a collection.
        NameValueCollection nameValueCollection = HttpUtility.ParseQueryString(str);
        foreach (string key in nameValueCollection.Keys)
        {
            keyValueDictionary[key] = nameValueCollection[key] ?? string.Empty;
        }

        /*
            * Use .NET Framework compatible pattern.
            */
        // Parse parameters into key / value pairs.
        //string[] keysAndValues = str.Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries);
        //foreach (string kvString in keysAndValues)
        //{
        //    string[] pair = kvString.Split('=');
        //    string? key = pair[0];
        //    string? value = string.Empty;
        //    if (key.Length > 1)
        //    {
        //        value = Uri.UnescapeDataString(pair[1]);
        //    }

        //    keyValueDictionary.Add(key, value);
        //}

        return keyValueDictionary;
    }

    // Handle the browser window closing.
    private void OnWindowClosed(object? sender, EventArgs? e)
    {
        // If the browser window closes, return the focus to the main window.
        if (authWindow != null && authWindow.Owner != null)
        {
            authWindow.Owner.Focus();
        }

        // If the task wasn't completed, the user must have closed the window without logging in.
        if (taskCompletionSource != null && !taskCompletionSource.Task.IsCompleted)
        {
            // Set the task completion source exception to indicate a canceled operation.
            taskCompletionSource.SetCanceled();
        }

        authWindow = null;
    }
}

 

dotMorten_esri
Esri Notable Contributor

Attached a runnable sample. I left the "old" WebBrowser class in there too so you can compare the differences. They are mostly pretty minor

JoeHershman
MVP Regular Contributor

I found the WebView2 looks much better with the newer API

OAuth dialog sizing (WPF 4.7.2) 

 

Thanks,
-Joe
0 Kudos
dotMorten_esri
Esri Notable Contributor

ArcGIS Online has made a fix and it will be published soon.

dotMorten_esri
Esri Notable Contributor

The fix is now live. Thank you for reporting this!

ArvindsDevadas
New Contributor

Thanks. It's working now. Saved me from having to install WebView2.

0 Kudos