Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AWS iOS SDK Cognito Developer Authentication (Swift)

I am having a hard time figuring out how to return developer credentials provided by my server (via AWS) to my Example identity provider.

It seems I need to do this synchronously within the refresh method on the ExampleIdentityProvider class. I'm using AFNetworking to make the request, but it is an async GET request. How can I do this synchronously for the refresh method on my IdentityProvider?

The following is in Swift:

class ExampleIdentityProvider: AWSAbstractIdentityProvider  {
    var newToken: String!

    override var token: String {
        get {
            return newToken
        }
        set {
            newToken = newValue
        }
    }

    override func getIdentityId() -> BFTask! {
        if self.identityId != nil {
            return BFTask(result: self.identityId)
        }else{
            return BFTask(result: nil).continueWithBlock({ (task) -> AnyObject! in
                if self.identityId == nil {
                    return self.refresh()
                }
                return BFTask(result: self.identityId)
            })
        }
    }

    override func refresh() -> BFTask! {
        return BFTask(result: nil).continueWithBlock({ (task) -> AnyObject! in
            let result =  AFNETWORKING REQUEST FOR CREDENTIALS TO MY SERVER
            self.identityId = result.identityId
            self.token = result.token

            return BFTask(result: self.identityId)
        })
    }
}
like image 360
northdig Avatar asked Feb 14 '15 01:02

northdig


2 Answers

I believe I have figured it out. I needed to utilize BFTask which is built for handling background tasks with completion.

For people struggling with a Swift implementation of developer authentication with Cognito who might have a similar setup to me, this is how I accomplished it:

class ExampleAppIdentityProvider: AWSAbstractCognitoIdentityProvider {
    var _token: String!
    var _logins: [ NSObject : AnyObject ]!

    // Header stuff you may not need but I use for auth with my server
    let acceptHeader = "application/vnd.exampleapp-api+json;version=1;"
    let authHeader = "Token token="
    let userDefaults = NSUserDefaults.standardUserDefaults()
    let authToken = self.userDefaults.valueForKey("authentication_token") as String

    // End point that my server gives amazon identityId and tokens to authorized users
    let url = "https://api.myapp.com/api/amazon_id/"

    override var token: String {
        get {
            return _token
        }
    }

    override var logins: [ NSObject : AnyObject ]! {
        get {
            return _logins
        }
        set {
            _logins = newValue
        }
    }

    override func getIdentityId() -> BFTask! {
        if self.identityId != nil {
            return BFTask(result: self.identityId)
        }else{
            return BFTask(result: nil).continueWithBlock({ (task) -> AnyObject! in
                if self.identityId == nil {
                    return self.refresh()
                }
                return BFTask(result: self.identityId)
            })
        }
    }

    override func refresh() -> BFTask! {
        let task = BFTaskCompletionSource()
        let request = AFHTTPRequestOperationManager()
        request.requestSerializer.setValue(self.acceptHeader, forHTTPHeaderField: "ACCEPT")
        request.requestSerializer.setValue(self.authHeader+authToken, forHTTPHeaderField: "AUTHORIZATION")
        request.GET(self.url, parameters: nil, success: { (request: AFHTTPRequestOperation!, response: AnyObject!) -> Void in
            // The following 3 lines are required as referenced here: http://stackoverflow.com/a/26741208/535363
            var tmp = NSMutableDictionary()
            tmp.setObject("temp", forKey: "ExampleApp")
            self.logins = tmp

            // Get the properties from my server response
            let properties: NSDictionary = response.objectForKey("properties") as NSDictionary
            let amazonId = properties.objectForKey("amazon_identity") as String
            let amazonToken = properties.objectForKey("token") as String

            // Set the identityId and token for the ExampleAppIdentityProvider
            self.identityId = amazonId
            self._token = amazonToken

            task.setResult(response)
        }, failure: { (request: AFHTTPRequestOperation!, error: NSError!) -> Void in
            task.setError(error)
        })
        return task.task
    }
}

And initialized the ExampleAppIdentityProvider by doing:

    let identityProvider = ExampleAppIdentityProvider()
    let credentialsProvider = AWSCognitoCredentialsProvider(regionType: AWSRegionType.USEast1, identityProvider: identityProvider, unauthRoleArn: GlobalVariables.cognitoUnauthRoleArn, authRoleArn: GlobalVariables.cognitoAuthRoleArn)
    let defaultServiceConfiguration = AWSServiceConfiguration(region: .USEast1, credentialsProvider: credentialsProvider)
    AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = defaultServiceConfiguration

    let transferManager = AWSS3TransferManager.defaultS3TransferManager()

    let uploadRequest = AWSS3TransferManagerUploadRequest()
    uploadRequest.bucket = GlobalVariables.awsBucket
    uploadRequest.key = "\(GlobalVariables.environment)/uploads/users/\(userId)/\(type)/\(timestamp)/original.jpg"
    uploadRequest.ACL = .AuthenticatedRead
    uploadRequest.body = tmpFileUrl

    // Upload file
    let task = transferManager.upload(uploadRequest)

I created a struct named GlobalVariables with global environment variables that hold values for bucket, unAuthRoleArn, authRoleArn, etc. Of course you don't have to do that, but I'm mentioning it in case someone is confused.

like image 92
northdig Avatar answered Sep 28 '22 12:09

northdig


you can generate you custom class for cognito authentication

import AWSS3
import AWSCore
import Alamofire

//This variable is store aws credential token
var cachedLogin : NSDictionary?
final class AmazonIdentityProvider : AWSCognitoCredentialsProviderHelper{

    // Handles getting the login
    override func logins() -> AWSTask<NSDictionary> {
        guard let cachedLoginObj = cachedLogin else {
            return getCredentials().continueWith(block: { (credentialTask) -> AWSTask<NSDictionary> in
                guard let credential = credentialTask.result else {
                    return AWSTask(result: nil)
                }

                self.setCognitoTokenKey(credential: credential)

                return AWSTask(result: cachedLogin)
            }) as! AWSTask<NSDictionary>
        }
        return AWSTask(result: cachedLoginObj)
    }

    // Handles getting a token from the server
    override func token() -> AWSTask<NSString> {
        return getCredentials().continueWith(block: { (credentialTask) -> AWSTask<NSString> in
            guard let credential = credentialTask.result else {
                return AWSTask(result: nil)
            }

            self.setCognitoTokenKey(credential: credential)

            return AWSTask(result: credential.token as NSString)
        }) as! AWSTask<NSString>
    }

    // Handles getting the identity id
    override func getIdentityId() -> AWSTask<NSString> {

        return getCredentials().continueWith(block: { (credentialTask) -> AWSTask<NSString> in
            guard let credential = credentialTask.result else {
                return AWSTask(result: nil)
            }

            self.setCognitoTokenKey(credential: credential)

            return AWSTask(result: credential.identityId as NSString)
        }) as! AWSTask<NSString>
    }

    //This method is used to AWS Token set
    func setCognitoTokenKey(credential : AmazonCognitoCredential){
        let login: NSDictionary = ["cognito-identity.amazonaws.com": credential.token]
        cachedLogin = login
        self.identityId = credential.identityId
    }

    // Gets credentials from server
    func getCredentials() -> AWSTask<AmazonCognitoCredential> {
        let tokenRequest = AWSTaskCompletionSource<AmazonCognitoCredential>()
        getAwsToken { (isSuccess, error, credentials) in
            if isSuccess
            {
                tokenRequest.set(result: credentials)
            }
            else
            {
                tokenRequest.set(error: error!)
            }
        }
        return tokenRequest.task
    }
    typealias CompletionBlock = (_ success:Bool,_ errorMassage:Error?,_ responce:AmazonCognitoCredential?) -> Void
    func getAwsToken(complitionBlock : @escaping CompletionBlock)  {
//Your server token code
}
/// AmazonCognito credential custom class
final class AmazonCognitoCredential {
    let token: String
    let identityId: String

    init(token: String, identityId: String) {
        self.token = token
        self.identityId = identityId
    }
}

and you can use in app delegate

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

    AWSDDLog.sharedInstance.logLevel = .all
    let identityProvider = AmazonIdentityProvider()
    let credentialsProvider = AWSCognitoCredentialsProvider(regionType: .USEast1, unauthRoleArn: CognitoRoleUnauth, authRoleArn: CognitoRoleAuth, identityProvider: identityProvider)
    let configuration = AWSServiceConfiguration(region: .USWest2, credentialsProvider: credentialsProvider)
    AWSServiceManager.default().defaultServiceConfiguration = configuration

    let task = identityProvider.getIdentityId()
    task.continueWith { (task:AWSTask) -> Any? in
        if (task.error != nil ) {
            print("\(String(describing: task.error))")
        } else {
            print("Task result: \(String(describing: task.result))")
        }
        return nil
    }
    return true
}
like image 23
Pratik Lad Avatar answered Sep 28 '22 11:09

Pratik Lad