Select to view content in your preferred language

Is there a way to repurpose my ArcGISAuthenticationChallengeHandler to authenticate OUTSIDE of the SDK?

478
7
03-24-2026 02:18 PM
jakeHawkenTC
Emerging Contributor

I work on an iOS/iPadOS application which users rely on for life-and-death work, and our users need to be able to have as much information available offline as possible. Some of our clients have multiple .mmpk maps that they need to trust are getting downloaded no matter what. Using the normal path for downloading an .mmpk file using the Esri SDK triggers a type of download that will be terminated if the app crashes, is killed, or is for some reason unloaded from memory by the OS. Since these downloads can be fairly large, there is an increased (and most certainly non-zero) chance that any of these things might happen, forcing the user to have to start the large and long download over again.

As a result, I set up a background session downloader in our codebase to set up an OS-level background download. This makes the download live independently of the app life cycle, allows for the resuming of a past download when the app restarts, and also allows for the app to be loaded back into memory from the app delegate if the download finishes while the app is not currently loaded into memory.

I have everything set up for it except one part: authentication. When `urlSession(_:didReceive:completionHandler:)` is called by the URLSession, I have no idea what to do with the challenge. All our other Esri interactions are mediated through the Esri SDK, and so they are authenticated that way. We have a type that conforms to `ArcGISAuthenticationChallengeHandler` and I feel like I *should* be able to use that here, but I don't see a way to do it, because its primary method (`handleArcGISAuthenticationChallenge(_:)`) expects a type specific to Esri (`ArcGIS.ArcGISAuthenticationChallenge`) which I don't have an instance of in that URLSessionDelegate method.

So, is there a way I can repurpose/reuse the credentials we employed in our `ArcGISAuthenticationChallengeHandler` type to authenticate this background download?

0 Kudos
7 Replies
RyanOlson1
Esri Contributor

@jakeHawkenTC wrote:
Using the normal path for downloading an .mmpk file using the Esri SDK triggers a type of download that will be terminated if the app crashes, is killed, or is for some reason unloaded from memory by the OS.

Please, can you provide details for what you mean by "normal path for downloading"? If you are talking about Jobs, it will utilize ways of keeping the download going. This session goes into great detail in the section on backgrounding, its worth watching: https://mediaspace.esri.com/media/t/1_a756nhrs

If you are kicking off a download from a URL using ArcGISURLSession, you will want to use the method:

`ArcGISEnvironment.backgroundURLSession.download(for:, to:)`

That will utilize a background URLSession, which is out of process, for your download.

Note: There is also experimental api that allow you to pause/resume downloads, etc like so:

`let download = ArcGISEnvironment.urlSession._download(for: request, to: destinationURL)`

This also uses a background URLSession.

Now, if you are targeting iOS 26, you can use BGContinuedProcessingTask, and you can wrap a Job or download in one of those and that is a nice experience. If you want to go down that road let me know and I can provide sample code.

As for this question:

So, is there a way I can repurpose/reuse the credentials we employed in our `ArcGISAuthenticationChallengeHandler` type to authenticate this background download?

No, there is not really a way to do that holistically. The authentication system is extremely complex and usually not just a matter of appending a token. The best way to make requests is to go through ArcGISURLSession. Again, the session I reference above from last year's dev summit talks through this. It's worth watching. Especially sections 2 and 3.

 

0 Kudos
jakeHawkenTC
Emerging Contributor

Thanks for your response. The "normal way" I alluded to was in fact to use the 

`ArcGISEnvironment.backgroundURLSession.download(for:to:)`, but unfortunately our clients have reported that the downloads are interrupted if the app is backgrounded. I suppose I could try the experimental method and see if I get better results.

Thanks for the video recommendation. I'll watch that at the beginning of my work day tomorrow.

0 Kudos
RyanOlson1
Esri Contributor

If you can reproduce what your clients are seeing and get us a reproducible case we can look into what might be missing in order to make sure it's not interrupted.

The download will run out of process when using the method you referenced above. It will continue to run when the app is backgrounded. It will even continue if iOS decides to terminate the app while the app is backgrounded. In that case where a backgrounded app is terminated, iOS will relaunch the app once the download is complete. However, there is a step required in that relaunch where you will need to re-request the download on relaunch and that will wire it up to the request that was completed in the background and it will finish immediately.

If you are seeing issues with that, get us a reproducible case and we can help you out.

iOS 26 has new the BGContinuedProcessingTask which simplifies all this and I highly recommend using that when available. Here is some code that you can use for iOS 26 along with that "experimental" (`_DownloadContoller`) API I mention above:

#if os(iOS) && !targetEnvironment(macCatalyst)
public import BackgroundTasks

@available(iOS 26.0, *)
public extension BGContinuedProcessingTask {
    /// Binds a download controller to this continued processing task such that
    /// the download progress is monitored and the task progress is updated accordingly.
    /// - Parameter downloadController: The download controller to bind to.
    @MainActor
    func _bind(to downloadController: _DownloadController) {
        // Cancel download if task expires or is cancelled.
        expirationHandler = {
            downloadController.cancel()
        }
        
        // Set initial progress on the task.
        let taskProgress = progress
        taskProgress.totalUnitCount = 100
        
        // Observe progress on the job and update the task.
        Task {
            let observer = downloadController.progress.observe(\.fractionCompleted, options: [.initial]) { progress, _ in
                taskProgress.completedUnitCount = Int64(progress.fractionCompleted * 100)
            }
            
            do {
                _ = try await downloadController.response
                setTaskCompleted(success: true)
            } catch {
                setTaskCompleted(success: false)
            }
            observer.invalidate()
        }
    }
}
#endif

 And the usage of it will look something like this:

    @available(iOS 26.0, *)
    func startContinuedProcessingTask(for download: _DownloadController, url: URL) throws {
        let cptIdentifier = (Bundle.main.bundleIdentifier ?? "") + ".cpt.jobs" + ".\(UUID().uuidString)"
        BGTaskScheduler.shared.register(forTaskWithIdentifier: cptIdentifier, using: .main) { task in
            let task = task as! BGContinuedProcessingTask
            task._bind(to: download)
        }
        
        let request = BGContinuedProcessingTaskRequest(
            identifier: cptIdentifier,
            title: "Downloading File",
            subtitle: url.lastPathComponent
        )
        request.strategy = .fail
        try BGTaskScheduler.shared.submit(request)
    }
0 Kudos
jakeHawkenTC
Emerging Contributor

Thanks! I can reproduce the bug by killing the app and the download doesn't finish. That said, I think it might relate to this bit that you said:
there is a step required in that relaunch where you will need to re-request the download on relaunch and that will wire it up to the request that was completed in the background and it will finish immediately.

How do I got about wiring that up, assuming I'm using the ArcGIS SDK to download it? What do I need to do to get that resume functionality?

0 Kudos
jakeHawkenTC
Emerging Contributor

Also, to be clear, I'm working in a legacy codebase that uses a `AppDelegate` and a `SceneDelegate`, if that makes a difference. I only mention it because the example in this video uses an `App` struct instead.

0 Kudos
RyanOlson1
Esri Contributor

Killing the app with the app switcher will kill all downloads associated with the app, even ones started with background URLSessions. That is the case whether you use the ArcGISURLSession or your own URLSession. That is documented behavior from Apple. 

How do I got about wiring that up, assuming I'm using the ArcGIS SDK to download it? What do I need to do to get that resume functionality?

To wire that up, you should just need to make the same exact request you made before with, when the app is re=launched. Make sure you are using `ArcGISEnvironment.backgroundURLSession`.

So you need to save some sort of state for when you make a download, and remove that state when it is finished. If an app is launched and there were downloads in progress, you can tell from that state that was saved, and remake those requests.

0 Kudos
RyanOlson1
Esri Contributor

Jake, how are things going with this issue? Is there anything else that we can help with?

Thanks

0 Kudos