Skip to content

Instantly share code, notes, and snippets.

@nixta
Last active December 19, 2023 12:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nixta/33afa1a17d7a9ea85f0d2d601e55a79f to your computer and use it in GitHub Desktop.
Save nixta/33afa1a17d7a9ea85f0d2d601e55a79f to your computer and use it in GitHub Desktop.
App ID (swift codable)
//
// AGSPortal+AppID.swift
//
// Created by Nicholas Furness on 4/13/18.
// Copyright © 2018 Esri. All rights reserved.
//
import Foundation
import ArcGIS
private struct AppIdToken : Decodable {
let token:String
let expiresIn:Int
private enum CodingKeys: String, CodingKey {
case token = "access_token"
case expiresIn = "expires_in"
}
}
extension AGSPortal {
public enum AppIDError : LocalizedError {
case badUrl
case badJsonResponse
public var errorDescription: String? {
switch self {
case .badUrl:
return "URL to token service could not be constructed from the portal URL."
case .badJsonResponse:
return "The response from the token service could not be interpreted!"
}
}
}
public func getAppIDToken(clientId:String, clientSecret:String, duration:Int? = nil, callback:@escaping (AGSCredential?, Error?)->Void) {
load { [unowned portal = self] error in
guard error == nil else {
print("Error loading portal!")
callback(nil, error)
return
}
guard let tokenUrl = URL(string: "sharing/rest/oauth2/token", relativeTo: portal.url) else {
print("Couldn't construct token URL!")
callback(nil, AppIDError.badUrl)
return
}
var appIdParameters:[String:Any] = [
"f": "json",
"client_id": clientId,
"client_secret": clientSecret,
"grant_type": "client_credentials"
]
if let duration = duration, duration > 0 {
appIdParameters["expiration"] = duration
}
let request = AGSRequestOperation(remoteResource: nil, url: tokenUrl,
queryParameters: appIdParameters,
method: .postFormEncodeParameters)
request.registerListener(AGSOperationQueue.shared(), forCompletion: { (result, error) in
guard error == nil else {
print("Error getting token! \(error!.localizedDescription)")
callback(nil, error)
return
}
guard let tokenData = result as? Data else {
print("Expected Data, but didn't get Data")
callback(nil, AppIDError.badJsonResponse)
return
}
var tokenInfo:AppIdToken!
do {
tokenInfo = try JSONDecoder().decode(AppIdToken.self, from: tokenData)
} catch {
print("Couldn't decode token info! \(error)")
callback(nil, error)
return
}
let credential = AGSCredential(token: tokenInfo.token, referer: nil)
credential.tokenUrl = tokenUrl
callback(credential, nil)
})
AGSOperationQueue.shared().addOperation(request)
}
}
}
import UIKit
import ArcGIS
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, AGSAuthenticationManagerDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
AGSAuthenticationManager.shared().delegate = self
return true
}
var arcgisPortal = AGSPortal.arcGISOnline(withLoginRequired: false)
func authenticationManager(_ authenticationManager: AGSAuthenticationManager, didReceive challenge: AGSAuthenticationChallenge) {
// We enter here because a call to some AGSRemoteResource (e.g. an AGSRouteTask)
// failed with an authentication error. This is our chance to generate and provide
// a token that we want the runtime to use. If we return nil, the authentication
// error will be propagated through to the network caller.
//
// If this method is not implemented, the default Runtime username/password
// prompt will be displayed (unless some OAuth configuration has been set up,
// in which case Runtime will enter the OAuth workflow).
// Here we will try to get a valid credential using the ClientID/ClientSecret.
// Note, I have kept this on AGSPortal because the token REST endpoint is associated
// with the portal so it makes logical sense. Otherwise we would need to pass in the
// URL to the portal anyway. Portal is a very lighweight object so this isn't too
// onerous. If needed, we can revisit this.
//
// IMPORTANT: Embedding ClientID and ClientSecret in your app binary is dangerous and
// a VERY BAD IDEA. Instead, you will need to determine some other way to deliver the
// ClientID and ClientSecret to your application securely at runtime, (e.g. with CloudKit
// and CoreData, or your own secure user login store) and then storing it on device
// in a secure manner like the KeyChain. This sample code does NOT follow that advice.
arcgisPortal.getAppIDToken(clientId: "<YOUR CLIENT ID>", clientSecret: "<YOUR CLIENT SECRET>") { (credential, error) in
if let error = error {
print("Error getting credential using AppID! \(error)")
// By calling continue, we will propagate the authentication error
// to the caller. One would need to check the logs to see that the
// attempt to get this credential also failed.
challenge.continue(with: nil)
return
}
guard let credential = credential else {
// We should always have a credential if there was no error, but
// it doesn't hurt to defend. Note that this will terminate the app.
preconditionFailure("Didn't get a credential… That's strange")
}
// Now we have a credential. Runtime will retry the call that failed,
// adding that credential to the credential cache (and the keychain, if you've
// opted into synchronizing the cache with the keychain),
// and will dequeue the requests that were waiting based off their need
// for the credential that Runtime didn't have.
challenge.continue(with: credential)
print("Provided a valid AppID based credential!")
}
}
...
}
// Sample usage
var portal:AGSPortal = AGSPortal.arcGISOnline(withLoginRequired: false)
portal.getAppIDToken(clientId: clientId, clientSecret: clientSecret) { (credential, error) in
guard error == nil else {
print("Could not get credential! \(error!.localizedDescription)")
return
}
print("Got a credential: \(credential!)")
}
@manish-kumar-git
Copy link

I am able to generate AGOL token but unable to set and use for AuthenticationManager in Android and iOS SDK? Also its not clear how authentication manager will authenticate secured feature layer every time i set the a layer. Some sample will help a lot.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment