Skip To Content ArcGIS for Developers Sign In Dashboard

ArcGIS Runtime SDK for .NET

ArcGIS token challenge

This code sample is available for these platforms:
View Sample on GitHub

This sample demonstrates how to prompt the user for a username and password to authenticate with ArcGIS Server to access an ArcGIS token-secured service. Accessing secured services requires a login that's been defined on the server.

Image of ArcGIS token challenge

Use case

Your app may need to access services that are restricted to authorized users. For example, your organization may host ArcGIS services that are only accessible by verified users.

How to use the sample

When you run the sample, the app will load a map that contains a layer from a secured service. Then, you will be challenged for a user name and password to view that layer. Enter the correct user name (user1) and password (user1). If you authenticate successfully, the secured layer will display, otherwise the map will contain only the public layers.

How it works

  1. A custom ChallengeHandler is set for AuthenticationManager that displays a login dialog for entering a username and password.
  2. In response to the attempt to access secured content, the AuthenticationManager calls the challenge handler.
  3. A TokenCredential is created from the entered username and password, and an attempt is made to load the layer.

Relevant API

  • AuthenticationManager
  • TokenCredential

Tags

authentication, security, token

Sample Code

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="ArcGISRuntime.Samples.TokenSecuredChallenge.LoginPage">

    <Grid Margin="20,50,20,0">
        <Grid.RowDefinitions>
            <RowDefinition Height="65"/>
            <RowDefinition Height="70"/>
            <RowDefinition Height="70"/>
            <RowDefinition Height="70"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Label x:Name="LoginLabel" 
           Text="Login to access token-secured service"
           FontSize="14"
           Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"/>
        <Entry x:Name="UsernameEntry"
           Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"
           Margin="10,3"
           Placeholder="Username = user1"/>
        <Entry x:Name="PasswordEntry"
           Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2"
           Margin="10,3"
           IsPassword="True"
           Placeholder="Password = user1"/>
        <Button x:Name="LoginButton"
            Grid.Row="3" Grid.Column="0"
            Text="Login"
            Clicked="LoginButtonClicked"/>
        <Button x:Name="CancelButton"
            Grid.Row="3" Grid.Column="1"
            Text="Cancel"
            Clicked="CancelButtonClicked"/>
    </Grid>
</ContentPage>
using System;
using Xamarin.Forms;

namespace ArcGISRuntime.Samples.TokenSecuredChallenge
{
	public partial class LoginPage : ContentPage
	{

        // Event to provide login information when the user dismisses the view.
        public event EventHandler<LoginEventArgs> OnLoginInfoEntered;

        // Event to report that the login was canceled.
        public event EventHandler OnCanceled;

        public LoginPage()
        {
            this.InitializeComponent();
        }

        // Text to display at the top of the login controls.
        public string TitleText
        {
            set
            {
                LoginLabel.Text = value;
            }
        }

        private void LoginButtonClicked(object sender, EventArgs e)
        {
            // Get the values entered in the text fields.
            string username = UsernameEntry.Text;
            string password = PasswordEntry.Text;

            // Make sure the user entered all values.
            if (String.IsNullOrEmpty(username) || String.IsNullOrEmpty(password))
            {
                Application.Current.MainPage.DisplayAlert("Login", "Please enter a username and password", "OK");
                return;
            }

            // Fire the OnLoginInfoEntered event and provide the login values.
            if (OnLoginInfoEntered != null)
            {
                // Create a new LoginEventArgs to contain the user's values.
                LoginEventArgs loginEventArgs = new LoginEventArgs(username.Trim(), password.Trim());

                // Raise the event.
                OnLoginInfoEntered(sender, loginEventArgs);
            }
        }

        private void CancelButtonClicked(object sender, EventArgs e)
        {
            // Fire the OnCanceled event to let the calling code no the login was canceled.
            if (OnCanceled != null)
            {
                OnCanceled(this, null);
            }
        }
    }

    // Custom EventArgs implementation to hold login information (username and password).
    public class LoginEventArgs : EventArgs
    {
        // Username property.
        public string Username { get; set; }

        // Password property.
        public string Password { get; set; }        

        // Store login values passed into the constructor.
        public LoginEventArgs(string username, string password)
        {
            Username = username;
            Password = password;
        }
    }
}
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage x:Class="ArcGISRuntime.Samples.TokenSecuredChallenge.TokenSecuredChallenge"
             xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:ArcGISRuntime.Samples.TokenSecuredChallenge"
             xmlns:esriUI="clr-namespace:Esri.ArcGISRuntime.Xamarin.Forms;assembly=Esri.ArcGISRuntime.Xamarin.Forms">
    <ContentPage.Resources>
        <ResourceDictionary>
            <local:LoadStatusToColorConverter x:Key="StatusToColor"/>
        </ResourceDictionary>
    </ContentPage.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="30"/>
            <RowDefinition Height="30"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <StackLayout x:Name="PublicLayerStatusPanel"
                 Grid.Row="0"
                 Orientation="Horizontal"
                 Margin="10,3">
            <Label Text="{Binding Name}"
             Margin="5,0"/>
            <Label Text="{Binding LoadStatus}"
             TextColor="{Binding LoadStatus, Converter={StaticResource StatusToColor}}"/>
        </StackLayout>
        <StackLayout x:Name="SecureLayerStatusPanel"
                 Grid.Row="1"
                 Orientation="Horizontal"
                 Margin="10,3">
            <Label Text="{Binding Name}"
             Margin="5,0"/>
            <Label Text="{Binding LoadStatus}"
             TextColor="{Binding LoadStatus, Converter={StaticResource StatusToColor}}"/>
        </StackLayout>
        <esriUI:MapView x:Name="MyMapView"
                    Grid.Row="2"/>
    </Grid>
</ContentPage>
// Copyright 2017 Esri.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
// You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific 
// language governing permissions and limitations under the License.

using Esri.ArcGISRuntime.Mapping;
using Esri.ArcGISRuntime.Security;
using System;
using System.Globalization;
using System.Threading.Tasks;
using Xamarin.Forms;

namespace ArcGISRuntime.Samples.TokenSecuredChallenge
{
    [ArcGISRuntime.Samples.Shared.Attributes.Sample(
        name: "ArcGIS token challenge",
        category: "Security",
        description: "This sample demonstrates how to prompt the user for a username and password to authenticate with ArcGIS Server to access an ArcGIS token-secured service. Accessing secured services requires a login that's been defined on the server.",
        instructions: "When you run the sample, the app will load a map that contains a layer from a secured service. Then, you will be challenged for a user name and password to view that layer. Enter the correct user name (user1) and password (user1). If you authenticate successfully, the secured layer will display, otherwise the map will contain only the public layers.",
        tags: new[] { "authentication", "security", "token" })]
    [ArcGISRuntime.Samples.Shared.Attributes.ClassFile("LoginPage.xaml.cs")]
    [ArcGISRuntime.Samples.Shared.Attributes.XamlFiles("LoginPage.xaml")]
    public partial class TokenSecuredChallenge : ContentPage
    {
        // Public and secured map service URLs.
        private string _publicMapServiceUrl = "https://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer";
        private string _secureMapServiceUrl = "https://sampleserver6.arcgisonline.com/arcgis/rest/services/USA_secure_user1/MapServer";

        // Public and secured layer names.
        private string _publicLayerName = "World Street Map - Public";
        private string _secureLayerName = "USA - Secure";

        // A TaskCompletionSource to store the result of a login task.
        private TaskCompletionSource<Credential> _loginTaskCompletionSrc;

        // Page for the user to enter login information.
        private LoginPage _loginPage;

        public TokenSecuredChallenge()
        {
            InitializeComponent();

            // Call a function to display a simple map and set up the AuthenticationManager.
            Initialize();
        }

        private void Initialize()
        {
            // Define a challenge handler method for the AuthenticationManager.
            // This method handles getting credentials when a secured resource is encountered.
            AuthenticationManager.Current.ChallengeHandler = new ChallengeHandler(CreateCredentialAsync);

            // Create the public layer and provide a name.
            ArcGISTiledLayer publicLayer = new ArcGISTiledLayer(new Uri(_publicMapServiceUrl))
            {
                Name = _publicLayerName
            };

            // Create the secured layer and provide a name.
            ArcGISMapImageLayer tokenSecuredLayer = new ArcGISMapImageLayer(new Uri(_secureMapServiceUrl))
            {
                Name = _secureLayerName
            };

            // Bind the layer to UI controls to show the load status.
            PublicLayerStatusPanel.BindingContext = publicLayer;
            SecureLayerStatusPanel.BindingContext = tokenSecuredLayer;

            // Create a new map and add the layers.
            Map myMap = new Map();
            myMap.OperationalLayers.Add(publicLayer);
            myMap.OperationalLayers.Add(tokenSecuredLayer);

            // Add the map to the map view.
            MyMapView.Map = myMap;

            // Create the login UI (will display when the user accesses a secured resource).
            _loginPage = new LoginPage();

            // Set up event handlers for when the user completes the login entry or cancels.
            _loginPage.OnLoginInfoEntered += LoginInfoEntered;
            _loginPage.OnCanceled += LoginCanceled;
        }

        // AuthenticationManager.ChallengeHandler function that prompts the user for login information to create a credential.
        private async Task<Credential> CreateCredentialAsync(CredentialRequestInfo info)
        {
            // Return if authentication is already in process.
            if (_loginTaskCompletionSrc != null && !_loginTaskCompletionSrc.Task.IsCanceled) { return null; }

            // Create a new TaskCompletionSource for the login operation.
            // Passing the CredentialRequestInfo object to the constructor will make it available from its AsyncState property.
            _loginTaskCompletionSrc = new TaskCompletionSource<Credential>(info);

            // Provide a title for the login form (show which service needs credentials).
#if WINDOWS_UWP
            // UWP doesn't have ServiceUri.GetLeftPart (could use ServiceUri.AbsoluteUri for all, but why not use a compilation condition?)
            _loginPage.TitleText = "Login for " + info.ServiceUri.AbsoluteUri;
#else
            _loginPage.TitleText = "Login for " + info.ServiceUri.GetLeftPart(UriPartial.Path);
#endif
            // Show the login controls on the UI thread.
            // OnLoginInfoEntered event will return the values entered (username and password).
            Device.BeginInvokeOnMainThread(async () => await Navigation.PushAsync(_loginPage));

            // Return the login task, the result will be ready when completed (user provides login info and clicks the "Login" button)
            return await _loginTaskCompletionSrc.Task;
        }

        // Handle the OnLoginEntered event from the login UI.
        // LoginEventArgs contains the username and password that were entered.
        private async void LoginInfoEntered(object sender, LoginEventArgs e)
        {
            // Make sure the task completion source has all the information needed.
            if (_loginTaskCompletionSrc == null ||
                _loginTaskCompletionSrc.Task == null ||
                _loginTaskCompletionSrc.Task.AsyncState == null)
            {
                return;
            }

            try
            {
                // Get the associated CredentialRequestInfo (will need the URI of the service being accessed).
                CredentialRequestInfo requestInfo = (CredentialRequestInfo)_loginTaskCompletionSrc.Task.AsyncState;

                // Create a token credential using the provided username and password.
                TokenCredential userCredentials = await AuthenticationManager.Current.GenerateCredentialAsync
                                            (requestInfo.ServiceUri,
                                             e.Username,
                                             e.Password,
                                             requestInfo.GenerateTokenOptions);

                // Set the task completion source result with the ArcGIS network credential.
                // AuthenticationManager is waiting for this result and will add it to its Credentials collection.
                _loginTaskCompletionSrc.TrySetResult(userCredentials);
            }
            catch (Exception ex)
            {
                // Unable to create credential, set the exception on the task completion source.
                _loginTaskCompletionSrc.TrySetException(ex);
            }
            finally
            {
                // Dismiss the login controls.
                await Navigation.PopAsync();
            }
        }

        private void LoginCanceled(object sender, EventArgs e)
        {
            // Dismiss the login controls.
            Navigation.PopAsync();

            // Cancel the task completion source task.
            _loginTaskCompletionSrc.TrySetCanceled();
        }
    }

    // Value converter class to return a color for the current load status
    // Note: to make this class accessible as a static resource in the shared form (TokenChallengePage.xaml)
    //       the assembly name for each platform had to be changed to the same value ("TokenChallengeForms")
    //       in order to provide a consistent XML namespace value. Another option would be to place such code in
    //       a PCL project rather than a shared project (the shared project would still be needed for the ArcGIS 
    //       Runtime code).
    public class LoadStatusToColorConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            // Default to gray as the text color.
            Color statusColor = Color.Gray;

            // Check the provided load status value.
            switch ((int)value)
            {
                // Green for loaded, red for not loaded (or failure to load), gray if still loading.
                case (int)Esri.ArcGISRuntime.LoadStatus.Loaded:
                    statusColor = Color.Green;
                    break;
                case (int)Esri.ArcGISRuntime.LoadStatus.Loading:
                    statusColor = Color.Gray;
                    break;
                case (int)Esri.ArcGISRuntime.LoadStatus.FailedToLoad:
                    statusColor = Color.Red;
                    break;
                case (int)Esri.ArcGISRuntime.LoadStatus.NotLoaded:
                    statusColor = Color.Red;
                    break;
            }

            return statusColor;
        }
        
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            // No need to convert the other way.
            throw new NotImplementedException();
        }
    }
}