Overview

You will learn: how to use named user login credentials to access the ArcGIS Traffic Layer service.

The ArcGIS Platform supports several security methodologies that you can implement to access ArcGIS premium services and secure content. To implement OAuth 2.0, you can use your ArcGIS account to register an application and get a client id, and then configure your app to redirect users to login with their credentials when the service or content is accessed. This is known as the "named user login" authentication pattern. If the app uses premium services that consume credits, the user's account will be charged, not your account. Alternatively, you can use your own account to set up service proxies that allow your applications to access premium services without asking the user to authenticate themselves. In this case, your account is providing authentication and will be charged credits if credit-consuming services are used. This is known as the "app login" authentication pattern. To learn more about service proxies, see the Set up authenticated services lab.

In this lab you will implement OAuth 2.0 to redirect users to sign into ArcGIS so they can access the ArcGIS Traffic Layer service.

Steps

Configure OAuth2 for your app

  1. Register your app to generate a client id and set a redirect URI to access the secure service:

    1. Sign into ArcGIS for Developers.
    2. Select Dashboard + > New Application to create a new application.
    3. Fill in new application details then select Register New Application. Take note of the Client ID value that is automatically assigned to your app.
    4. On the Authentication tab use the Redirect URIs section to add my-devlab-app://auth.

Create a new ArcGIS Runtime App Visual Studio Project

  1. Start Visual Studio.

  2. Choose File > New > Project and select the ArcGIS Runtime Application (WPF) template for Visual C#.

Set the app settings

  1. Open the MapViewModel.cs code file in your project. Find the definition for the _map field in the class. Notice that the default map is currently set to display the streets basemap. Keep the field declaration (private Map _map) but remove the assignment. Also add the following additional fields:

        private Map _map;
        private BasemapType basemapType = BasemapType.StreetsNightVector;
        private double latitude = 34.05293;
        private double longitude = -118.24368;
        private int levelOfDetail = 11;
        private string trafficLayerURL = "https://traffic.arcgis.com/arcgis/rest/services/World/Traffic/MapServer";
        private string ServerUrl = "https://www.arcgis.com/sharing/rest";
        private string ClientId = "YOUR-APP-CLIENT-ID";
        private string RedirectURI = "my-devlab-app://auth";
    

Load the basemap and the traffic layer

  1. Add the following function to the MapViewModel class to create the basemap:

        private void CreateNewMap()
        {
            Map = new Map(basemapType, latitude, longitude, levelOfDetail);
        }
    
  2. Add the following function to the MapViewModel class to setup authentication and add the traffic layer to the map as an operational layer.

        private void AddTrafficLayer()
        {
            // SetOAuthInfo();
            ArcGISMapImageLayer traffic = new ArcGISMapImageLayer(new Uri(trafficLayerURL));
            Map.OperationalLayers.Add(traffic);
        }
    
  3. Update the constructor to call the new functions:

        public MapViewModel()
        {
            CreateNewMap();
            AddTrafficLayer();
        }
    

At this point you can run and test your app. The basemap should load but the traffic layer will not load until you are able to use the AuthenticationManager to log in a user and get a valid access token.

Implement an IOAuthAuthorizeHandler

  1. Add the following using statements at the top of your MapViewModel.cs code module:

    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Navigation;
    
  2. To complete OAuth authorization, a web page is presented to your user that prompts for credentials (username and password) directly from arcgis.com. When complete, your app gets the status of that event. In a desktop (WPF) app, an OAuthAuthorizeHandler component handles many of the OAuth details. Add the implementation of this class at the bottom of your MapViewModel.cs module (inside the application namespace).

    public class OAuthAuthorize : IOAuthAuthorizeHandler
    {
        private Window _window;
        private TaskCompletionSource<IDictionary<string, string>> _tcs;
        private string _callbackUrl;
        private string _authorizeUrl;
    
        public Task<IDictionary<string, string>> AuthorizeAsync(Uri serviceUri, Uri authorizeUri, Uri callbackUri)
        {
            if (_tcs != null || _window != null)
            {
                throw new Exception();
            }
            _authorizeUrl = authorizeUri.AbsoluteUri;
            _callbackUrl = callbackUri.AbsoluteUri;
            _tcs = new TaskCompletionSource<IDictionary<string, string>>();
            var dispatcher = Application.Current.Dispatcher;
            if (dispatcher == null || dispatcher.CheckAccess())
            {
                AuthorizeOnUIThread(_authorizeUrl);
            }
            else
            {
                dispatcher.BeginInvoke((Action)(() => AuthorizeOnUIThread(_authorizeUrl)));
            }
            return _tcs.Task;
        }
    
        private void AuthorizeOnUIThread(string authorizeUri)
        {
            var webBrowser = new WebBrowser();
            webBrowser.Navigating += WebBrowserOnNavigating;
            _window = new Window
            {
                Content = webBrowser,
                Height = 600,
                Width = 400,
                WindowStartupLocation = WindowStartupLocation.CenterOwner,
                Owner = Application.Current != null && Application.Current.MainWindow != null
                            ? Application.Current.MainWindow
                            : null
            };
            _window.Closed += OnWindowClosed;
            webBrowser.Navigate(authorizeUri);
            if (_window != null)
            {
                _window.ShowDialog();
            }
        }
    
        void OnWindowClosed(object sender, EventArgs e)
        {
            if (_window != null && _window.Owner != null)
            {
                _window.Owner.Focus();
            }
            if (_tcs != null && !_tcs.Task.IsCompleted)
            {
                // The user closed the window
                _tcs.SetException(new OperationCanceledException());
            }
            _tcs = null;
            _window = null;
        }
    
        void WebBrowserOnNavigating(object sender, NavigatingCancelEventArgs navigationEvent)
        {
            var webBrowser = sender as WebBrowser;
            Uri uri = navigationEvent.Uri;
            if (webBrowser == null || uri == null || _tcs == null || string.IsNullOrEmpty(uri.AbsoluteUri))
            {
                return;
            }
            if (uri.AbsoluteUri.StartsWith(_callbackUrl))
            {
                navigationEvent.Cancel = true;
                var tcs = _tcs;
                _tcs = null;
                if (_window != null)
                {
                    _window.Close();
                }
                tcs.SetResult(DecodeParameters(uri));
            }
        }
    
        private static IDictionary<string, string> DecodeParameters(Uri uri)
        {
            var answer = string.Empty;
            if (!string.IsNullOrEmpty(uri.Fragment))
            {
                answer = uri.Fragment.Substring(1);
            }
            else if (!string.IsNullOrEmpty(uri.Query))
            {
                answer = uri.Query.Substring(1);
            }
            var keyValueDictionary = new Dictionary<string, string>();
            var keysAndValues = answer.Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries);
            foreach (var kvString in keysAndValues)
            {
                var 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;
        }
    }
    

Configure OAuth in the app

  1. Create a function to handle authentication challenges in your app. The following method is called by the AuthenticationManager whenever the app attempts to access secured content. Add this code as a method of your MapViewModel class.

        public async Task<Credential> CreateCredentialAsync(CredentialRequestInfo credentialRequestInfo)
        {
            Credential credential = null;
            try
            {
                credential = await AuthenticationManager.Current.GenerateCredentialAsync(credentialRequestInfo.ServiceUri);
            }
            catch (Exception ex)
            {
                throw (ex);
            }
            return credential;
        }
    
  2. Add the following function to your MapViewModel class to register ArcGIS Online server information with AuthenticationManager and define the challenge handler function. You'll also set the AuthenticationManager to use the OAuthAuthorize class you created previously to handle OAuth authorization requests.

        private void SetOAuthInfo()
        {
            var serverInfo = new ServerInfo
            {
                ServerUri = new Uri(ServerUrl),
                TokenAuthenticationType = TokenAuthenticationType.OAuthImplicit,
                OAuthClientInfo = new OAuthClientInfo
                {
                    ClientId = ClientId,
                    RedirectUri = new Uri(RedirectURI)
                }
            };
            AuthenticationManager.Current.RegisterServer(serverInfo);
            AuthenticationManager.Current.OAuthAuthorizeHandler = new OAuthAuthorize();
            AuthenticationManager.Current.ChallengeHandler = new ChallengeHandler(CreateCredentialAsync);
        }
    

Add a secured layer to your map's operational layers

  1. Go back to your AddTrafficLayer method and uncomment out the call to SetOAuthInfo.

Congratulations, you're done!

Your map should open on your device then show a dialog from ArcGIS Online asking for OAuth login. Once successfully logged in you return to the map with the traffic layer added.

Challenge

Explore basemaps

Try different basemaps available with BasemapType. Now that you are able to authenticate users, you could also define private basemaps on your ArcGIS Online account and load those in your apps. Try it!

Discover layers

Discover other layers you can use in your app. Explore ArcGIS Open Data and Los Angeles GeoHub for data you can use in your apps.