You will learn: how to build an app that uses named user login credentials to access secure services.
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 tutorial.
In this tutorial you will implement OAuth 2.0 to redirect users to sign in to ArcGIS so they can access the ArcGIS Traffic Layer service.
Register your app to generate a client ID and set a redirect URI to access the secure service:
my-devlab-app://auth
.
Start Visual Studio.
Choose File > New > Project and select the ArcGIS Runtime Application (WPF) template for Visual C#.
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";
Add the following function to the MapViewModel
class to create the basemap:
private void CreateNewMap()
{
Map = new Map(basemapType, latitude, longitude, levelOfDetail);
}
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);
}
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.
IOAuthAuthorizeHandler
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;
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;
}
}
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;
}
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);
}
AddTrafficLayer
method and uncomment out the call to SetOAuthInfo
.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.
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 other layers you can use in your app. Explore ArcGIS Open Data and Los Angeles GeoHub for data you can use in your apps.