-
-
Save nixta/33afa1a17d7a9ea85f0d2d601e55a79f to your computer and use it in GitHub Desktop.
App ID (swift codable)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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) | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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!") | |
} | |
} | |
... | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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!)") | |
} |
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
Thanks a lot for such detailed explanations and insights, it’s definitely worth to know and take to consideration, kudos to you.