Skip To Content ArcGIS for Developers Sign In Dashboard

ArcGIS Runtime SDK for .NET

Certificate authentication with PKI

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

Access secured portals using a certificate.

Use case

PKI (Public Key Infrastructure) is a certificate authentication method to secure resources without requiring users to remember passwords. Government agencies commonly issue smart cards using PKI to access computer systems.

How to use the sample

NOTE: You must provide your own ArcGIS Portal with PKI authentication configured.

Provide a URL to a PKI-enabled server, then use the certificate selection UI to select an appropriate certificate for that server.

How it works

  1. Create the X.509 certificate store, referring to the user's certificates.
  2. Open the certificate store in read-only mode.
  3. Find all certificates that are currently valid.
  4. Display a content dialog, allowing the user to select a certificate.
  5. Create the ArcGIS Runtime credential with the chosen certificate.
  6. [Workaround] Use Windows.Web.Http.HttpClient to force certificate selection to work.
  7. Create the Portal, explicitly passing in the credential that was created.

Relevant API

  • CertificateCredential

Additional information

ArcGIS Enterprise requires special configuration to enable support for PKI. PKI authentication can be set up to work with accounts managed by Windows Active Directory or LDAP.

NOTE: UWP apps must declare the following capabilities in their manifest: Private Networks (Client & Server), Shared User Certificates.

Tags

smartcard, PKI, certificate, store, X509, authentication, login, passwordless

Sample Code

<UserControl 
    x:Class="ArcGISRuntime.UWP.Samples.CertificateAuthenticationWithPKI.CertificateAuthenticationWithPKI"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:x509certificates="using:System.Security.Cryptography.X509Certificates">
    <UserControl.Resources>
        <DataTemplate x:DataType="x509certificates:X509Certificate2" x:Key="CertificateTemplate">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                </Grid.RowDefinitions>
                <TextBlock Text="User:"
                           Grid.Row="0" Grid.Column="0"/>
                <TextBlock Text="Issuer:" 
                           Grid.Row="1" Grid.Column="0"/>
                <TextBlock Text="Valid until:" 
                           Grid.Row="2" Grid.Column="0"/>
                <TextBlock Text="{Binding Subject}"
                           Grid.Row="0" Grid.Column="1"/>
                <TextBlock Text="{Binding Issuer}"
                           Grid.Row="1" Grid.Column="1"/>
                <TextBlock Text="{Binding NotAfter}"
                           Grid.Row="2" Grid.Column="1"/>
            </Grid>
        </DataTemplate>
    </UserControl.Resources>
    <Grid>
        <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="150" />
            </Grid.ColumnDefinitions>
            <TextBlock Text="Enter the URL to a portal that you have a certificate for:"
                       Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"/>
            <TextBox x:Name="PortalUrlTextbox" 
                     PlaceholderText="https://portal.yourcompany.com/gis/"
                     Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Margin="0,10" MinWidth="300" />
            <Button Content="Choose a certificate" 
                    HorizontalAlignment="Stretch"
                    Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2"
                    Click="Button_Click" />
            <TextBlock Text="User: "
                       Margin="5"
                       Grid.Row="3" Grid.Column="0"/>
            <TextBlock x:Name="LoggedInUsername" 
                       Margin="5"
                       Grid.Row="3" Grid.Column="1"
                       Text="Not logged in" />
        </Grid>
    </Grid>
</UserControl>
// Copyright 2019 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.Portal;
using Esri.ArcGISRuntime.Security;
using System;
using System.Net.Http;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Windows.UI.Popups;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace ArcGISRuntime.UWP.Samples.CertificateAuthenticationWithPKI
{
    [ArcGISRuntime.Samples.Shared.Attributes.Sample(
        "Certificate authentication with PKI",
        "Security",
        "Access secured portals using a certificate.",
        "")]
    public partial class CertificateAuthenticationWithPKI
    {
        private string _serverUrl;

        public CertificateAuthenticationWithPKI()
        {
            InitializeComponent();
        }

        public async Task<Credential> CreateCertCredentialAsync(CredentialRequestInfo info)
        {
            // Handle challenges for a secured resource by prompting for a client certificate.
            Credential credential = null;

            try
            {
                // Create an X509 store for reading certificates for the current user.
                var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);

                // Open the store in read-only mode.
                store.Open(OpenFlags.ReadOnly);

                // Get a list of certificates that are currently valid.
                X509Certificate2Collection certificates = store.Certificates.Find(X509FindType.FindByTimeValid, DateTime.Now, true);

                // Create a dialog for showing the list of certificates.
                ContentDialog dialog = new ContentDialog();
                dialog.CloseButtonText = "Select certificate";

                // Create a list view for rendering the list.
                ListView listview = new ListView();

                // Use a template defined as a resource in XAML.
                listview.ItemTemplate = (DataTemplate) this.Resources["CertificateTemplate"];

                // Display the items in the listview.
                listview.ItemsSource = certificates;

                // Display the listview in the dialog.
                dialog.Content = listview;

                // Display the dialog.
                await dialog.ShowAsync();

                // Make sure the user chose a certificate.
                if (listview.SelectedItems.Count > 0)
                {
                    // Get the chosen certificate.
                    X509Certificate2 cert = (X509Certificate2) listview.SelectedItem;

                    // Create a new CertificateCredential using the chosen certificate.
                    credential = new Esri.ArcGISRuntime.Security.CertificateCredential(cert)
                    {
                        ServiceUri = new Uri(_serverUrl)
                    };
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex);
            }

            // Return the CertificateCredential for the secured portal.
            return credential;
        }

        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                // Workaround for HTTP client bug affecting System.Net.HttpClient.
                // https://github.com/dotnet/corefx/issues/37598
                var httpClient = new Windows.Web.Http.HttpClient();
                await httpClient.GetStringAsync(new Uri(PortalUrlTextbox.Text));
                // End workaround

                // Store the server URL for later reference.
                _serverUrl = PortalUrlTextbox.Text;

                // Configure the authentication manager.
                AuthenticationManager.Current.ChallengeHandler = new ChallengeHandler(CreateCertCredentialAsync);

                // Create the portal.
                ArcGISPortal portal = await ArcGISPortal.CreateAsync(new Uri(_serverUrl));

                // Update the UI with the logged in user.
                LoggedInUsername.Text = portal.User.FullName;
            }
            catch (UriFormatException)
            {
                await new MessageDialog("Couldn't authenticate. Enter a valid URL first.").ShowAsync();
            }
            catch (HttpRequestException ex)
            {
                if (ex.Message.Contains("404"))
                {
                    await new MessageDialog("404: Not Found").ShowAsync();
                }
                else if (ex.Message.Contains("403"))
                {
                    await new MessageDialog("403: Not authorized; did you use the right certificate?").ShowAsync();
                }
                else
                {
                    System.Diagnostics.Debug.WriteLine(ex);
                    await new MessageDialog("Couldn't authenticate. See debug output for details").ShowAsync();
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex);
                await new MessageDialog("Error authenticating; see debug output for details.").ShowAsync();
            }
        }
    }
}