Skip to content

Authenticate with PKI certificate

View on GitHub

Access secured portals using a certificate.

Image of Authenticate with PKI certificate sample

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

  1. Enter the URL to your PKI-secured portal.
  2. Click the connect button to search for web maps stored on the portal.
  3. You will be prompted to browse for a certificate and enter a password.
  4. If you authenticate successfully, the portal item results will be displayed in the list.
  5. Select a web map item to display it in the map view.

How it works

  1. The AuthenticationManager object is configured with a challenge handler that will prompt for a PKI certificate if a secure resource is encountered.
  2. When a search for portal items is performed against a PKI-secured portal, the Authenticator creates a NetworkCredential from the information entered by the user.
  3. If the user authenticates, the search returns a list of web maps (PortalItem) and the user can select one to display as a Map.

Relevant API

  • ArcGISEnvironment
  • AuthenticationManager
  • Authenticator
  • Portal

Additional information

ArcGIS Enterprise requires special configuration to enable support for PKI. See Using Windows Active Directory and PKI to secure access to your portal and Use LDAP and PKI to secure access to your portal in Portal for ArcGIS.

NOTE: Certificates installed on iOS are not available to user apps. Therefore, you will be prompted to browse for a certificate file when accessing PKI secured ArcGIS resources.

NOTE: Ensure that PKI certificates are available on the iOS device or storage drives, allowing you to browse for a certificate file when accessing PKI secured ArcGIS resources.

Tags

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

Sample Code

AuthenticateWithPKICertificateView.swift
Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
// Copyright 2025 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
//
//   https://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.

import ArcGIS
import ArcGISToolkit
import SwiftUI

struct AuthenticateWithPKICertificateView: View {
    /// The model that backs this view.
    @StateObject private var model = Model()

    var body: some View {
        Form {
            switch model.portalContent {
            case .success(let success):
                ForEach(success.results, id: \.id?.rawValue) { item in
                    Button(item.title) {
                        model.selectedItem = .init(portalItem: item)
                    }
                    .buttonStyle(.plain)
                }
            case .failure:
                urlEntryView
                ContentUnavailableView(
                    "Error",
                    systemImage: "exclamationmark.triangle",
                    description: Text("Error searching specified portal.")
                )
            case nil:
                if model.isConnecting {
                    ProgressView()
                        .frame(maxWidth: .infinity)
                } else {
                    urlEntryView
                }
            }
        }
        .onTeardown {
            // Reset the challenge handlers and clear credentials
            // when the view disappears so that user is prompted to enter
            // credentials every time the sample is run, and to clean
            // the environment for other samples.
            await model.teardownAuthenticator()
        }
        .sheet(item: $model.selectedItem) { selectedItem in
            NavigationStack {
                MapView(map: Map(item: selectedItem.portalItem))
                    .navigationTitle(selectedItem.portalItem.title)
                    .navigationBarTitleDisplayMode(.inline)
                    .toolbar {
                        ToolbarItem(placement: .topBarTrailing) {
                            Button("Done") {
                                model.selectedItem = nil
                            }
                        }
                    }
            }
            .interactiveDismissDisabled()
            .highPriorityGesture(DragGesture())
            .pagePresentation()
        }
        .animation(.default, value: model.isConnecting)
        .authenticator(model.authenticator)
    }

    @ViewBuilder private var urlEntryView: some View {
        Section {
            HStack {
                TextField("PKI Secured Portal URL", text: $model.portalURLString)
                    .onSubmit { Task { await model.connectToPortal() } }
                    .autocapitalization(.none)
                    .keyboardType(.URL)
                Button("Connect") {
                    Task { await model.connectToPortal() }
                }
                .disabled(model.portalURL == nil)
            }
        }
    }
}

/// A value that represents an item selected by the user.
private struct SelectedItem: Identifiable {
    /// The portal item that was selected.
    let portalItem: PortalItem

    var id: ObjectIdentifier {
        ObjectIdentifier(portalItem)
    }
}

extension AuthenticateWithPKICertificateView {
    @MainActor
    class Model: ObservableObject {
        /// The authenticator to handle authentication challenges.
        let authenticator = Authenticator()

        /// The URL string entered by the user.
        @Published var portalURLString = ""

        /// The fetched portal content.
        @Published var portalContent: Result<PortalQueryResultSet<PortalItem>, Error>?

        /// A Boolean value indicating if a portal connection is in progress.
        @Published var isConnecting = false

        /// The selected item.
        @Published fileprivate var selectedItem: SelectedItem?

        /// The URL of the portal.
        var portalURL: URL? { URL(string: portalURLString) }

        init() {
            setupAuthenticator()
        }

        /// Connects to the portal and finds a batch of webmaps.
        func connectToPortal() async {
            precondition(portalURL != nil)

            isConnecting = true
            defer { isConnecting = false }

            do {
                let portal = Portal(url: portalURL!)
                try await portal.load()
                let results = try await portal.findItems(queryParameters: .items(ofKinds: [.webMap]))
                portalContent = .success(results)
            } catch {
                portalContent = .failure(error)
            }
        }

        /// Sets up the authenticator to handle challenges.
        private func setupAuthenticator() {
            // Setting the challenge handlers here when the model is created so user is prompted to enter
            // credentials every time trying the sample. In real world applications, set challenge
            // handlers at the start of the application.

            // Sets authenticator as ArcGIS and Network challenge handlers to handle authentication
            // challenges.
            ArcGISEnvironment.authenticationManager.handleChallenges(using: authenticator)

            // In your application you may want to uncomment this code to persist
            // credentials in the keychain.
            // setupPersistentCredentialStorage()
        }

        /// Stops the authenticator from handling the challenges and clears credentials.
        nonisolated func teardownAuthenticator() async {
            // Resets challenge handlers.
            ArcGISEnvironment.authenticationManager.handleChallenges(using: nil)

            // In your application, code may need to run at a different
            // point in time based on the workflow desired. For example, it
            // might make sense to remove credentials when the user taps
            // a "sign out" button.
            await ArcGISEnvironment.authenticationManager.revokeOAuthTokens()
            await ArcGISEnvironment.authenticationManager.clearCredentialStores()
        }

        /// Sets up new ArcGIS and Network credential stores that will be persisted in the keychain.
        private func setupPersistentCredentialStorage() {
            Task {
                try await ArcGISEnvironment.authenticationManager.setupPersistentCredentialStorage(
                    access: .whenUnlockedThisDeviceOnly,
                    synchronizesWithiCloud: false
                )
            }
        }
    }
}

#Preview {
    AuthenticateWithPKICertificateView()
}

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.