aws-amplify / aws-sdk-ios

AWS SDK for iOS. For more information, see our web site:
https://aws-amplify.github.io/docs
Other
1.68k stars 879 forks source link

2.4.0 breaks custom login provider? #357

Closed helloniklas closed 7 years ago

helloniklas commented 8 years ago

So I spent ages getting a custom login provider to work. I have a class BYOIProvider extending AWSAbstractCognitoIdentityProvider based on some AWS sample. This was all working fine. However, upgrading to 2.4.0 the AWSAbstractCognitoIdentityProvider is no longer available? Do I have to rewrite this code now?

simoami commented 8 years ago

@yosuke-matsuda Thanks for the review feedback. At this point I just want to close the loop on the login and signup lifecycle. Then I can start handling token expiration and apply your other recommendations.

bpeck81 commented 8 years ago

@yosuke-matsuda, like @simoami I'm trying to sign in with facebook, and it would be great if you could help me fix this. I implemented the custom identity provider manager in my project and feed it a facebook token like this

let token = FBSDKAccessToken.currentAccessToken().tokenString
let loginProvider = CustomIdentityProvider(tokens: ["graph.facebook.com": token])

let credentialsProvider = AWSCognitoCredentialsProvider(regionType: AWSRegionType.USEast1, identityPoolId: Constants.identityPoolId, identityProviderManager: loginProvider)

But in the identity pool dashboard, its not authorizing the id with facebook. Am I missing something?

bpeck81 commented 8 years ago

I added credentialsProvider.getIdentityId() and it's now logging in. Is this method some kind of refresh?

whiskey commented 8 years ago

Kind of, yes. If you want to wait for the new IdentityID to be fetched before you continue with your code, add .continueWithBlock ….

bpeck81 commented 8 years ago

Okay thanks, I'm trying to understand how this all works. So my intuition is that, I add the facebook token to the credentials provider. CredentialsProvider.getIdentityId()syncs the phone token with AWS, sending the facebook token. AWS checks the facebook token with facebook, authorizes it, and and returns the new identityID to the phone with updated authorization. Is that right? And this was discussed earlier, but say the user wanted to logout. How should I remove credentials from the credentials provider and unauthorize the user? Thanks again for the help

whiskey commented 8 years ago

As fas as I got it, the IdentityID is kind of a meta object which 1.) provides a unique id with the AWS ecosystem and 2.) references to credentials for providers such as Facebook, Twitter, User Pools, etc. If you enable anonymous access for your Cognito Identity Pool the IdentityID has 0 linked logins.

So in your example you might get an id A on initial startup, login to Facebook and keep the same id A, now with one login linked. The fun part starts with multiple devices: If you repeat this now on a new device it gets a new id B and when you authenticate to the same Facebook account, this id B is (kind of) merged into id A. By this you can pull out a new device, do a login and you get access to your existing id A and move on as if the app has ever been on your device. [As far as I've seen it in my app and pulled out of the documentation]

bpeck81 commented 8 years ago

Okay that makes sense. I'm also trying to use user pools, do you know how to get the authenticated token after signing up. I'm calling user.confirmSignUp() followed by user.getSession(username, password). How can I get the proper token to add to credentialsProvider? And to remove credentials should I call credentialsProvider.clearKeychain()then credentialsProvider.getIdentityId()? I really appreciate this help thanks again

rogerioufjf commented 8 years ago

yosuke-matsuda do you know when the sample will be updated? I still can't figure out how to implement de Developer Authentication on the SDW 2.4.5. Tanks

Takuro-Ito commented 8 years ago

@yosuke-matsuda @simoami Amazon's Developer Guide is still old as you can see below. (last update: 04/19/2016)

NSString *token = [FBSDKAccessToken currentAccessToken].tokenString;
credentialsProvider.logins = @{ @(AWSCognitoLoginProviderKeyFacebook): token };

I'm kinda beginner of iOS programming. So, I can't convert the custom class written in Swift to the one in Objective-C.

class CustomIdentityProvider: NSObject, AWSIdentityProviderManager {
    var tokens : [NSString : NSString]?
    init(tokens: [NSString : NSString]) {
        self.tokens = tokens
    }
    @objc func logins() -> AWSTask {
        return AWSTask(result: tokens)
    }
}

let customProviderManager = CustomIdentityProvider(tokens: logins!)

self.credentialsProvider = AWSCognitoCredentialsProvider(
   regionType: Constants.COGNITO_REGIONTYPE,
   identityPoolId: Constants.COGNITO_IDENTITY_POOL_ID,
   identityProviderManager: customProviderManager
)

Could you tell me an example woking in Objective-C? I Need your help. Thank you.

andresgarza commented 8 years ago

Any updates on this? It's been 3 months and sample apps haven't been updated.

apatrida commented 8 years ago

This has been a mess, regardless of the speech given by @yosuke-matsuda in his comment https://github.com/aws/aws-sdk-ios/issues/357#issuecomment-215284491 there are people like me and my team who read the samples that are available publically now, the docs, the blog posts, and we stumble through lost days of work but only for IOS where the API seems to have gone off the rails.

@yosuke-matsuda your message linked above was not helpful and basically said "it's your fault for upgrading". Well, people coming in now that are NOT upgrading are still hurt by the lack of caretaking around this release.

We are still working on more detailed documentation with sample apps. They will be available shortly. If API documentation is not enough, and you need more help, please watch out for 2.x.0 releases and evaluate thoroughly before jumping on it right away since they contain some breaking changes.

Well, you didn't do this for months so more people suffer. Now how many comments from above need to be read, parsed, discarded, sorted through, link scanned, and new errors before something more official happens?

helloniklas commented 8 years ago

@apatrida you probably better off using something like 0auth instead of AWS own auth system. It's so poorly implemented you'll waste a lot of time on it.

andresgarza commented 8 years ago

After wasting a couple of days, I think I got developer login providers working on 2.4.5. Here's the basic logic of our code:

First, we created a custom AWSCognitoCredentialsProviderHelper and AWSIdentityProviderManager:

class CognitoCredentialsProviderHelper: AWSCognitoCredentialsProviderHelper {

    override var identityPoolId: String {
        return "us-east-1:XYZ"
    }

    private var _identityProviderManager: IdentityProviderManager = {
        return IdentityProviderManager()
    }()

    override var identityProviderManager: AWSIdentityProviderManager? {
        return self._identityProviderManager
    }

    var _token: String!

    override func clear() {
        super.clear()
        self._token = nil
        self.identityId = nil
    }

    func refresh() -> AWSTask! {
        return AWSTask(result: nil).continueWithSuccessBlock({ (task: AWSTask!) -> AnyObject! in
            let task = AWSTaskCompletionSource()

            Alamofire.request(
                .GET, "https://www.example.com/auth/cognito", parameters: []
            ).response { request, response, data, error in
                if let error = error {
                    task.setError(error)
                } else {
                    let json = jsonFromData(data!) // turns data into JSON (we use SwiftyJSON)

                    self.identityId = json["identityId"].stringValue
                    self._token = json["token"].stringValue

                    task.setResult(self._token)
                }
            }

            return task.task
        })
    }

    override func getIdentityId() -> AWSTask {
        if (self.identityId != nil) {
            return AWSTask(result: self.identityId!)
        }

        return AWSTask(result: nil).continueWithBlock({ (task) -> AnyObject! in
            if (self.identityId == nil) {
                return self.refresh()
            }

            return AWSTask(result: self.identityId!)
        })
    }

    override func token() -> AWSTask {
        if (self._token != nil) {
            return AWSTask(result: self._token!)
        }

        return AWSTask(result: nil).continueWithBlock({ (task) -> AnyObject! in
            if (self._token == nil) {
                return self.refresh()
            }

            return AWSTask(result: self._token!)
        })
    }
}

class IdentityProviderManager: NSObject, AWSIdentityProviderManager {
    @objc func logins() -> AWSTask {
        let userIdentifier = getCognitoUserIdentifier() // In our case, we use the current logged in user's id
        return AWSTask(result: ["developer-provider-name": userIdentifier])
    }
}

With these, we were then able to set the defaultServicesConfiguration to a credentialsProvider using our custom identityProvider:

let credentialsProvider = AWSCognitoCredentialsProvider(
    regionType: .USEast1,
    identityProvider: CognitoCredentialsProvider()
)

AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = AWSServiceConfiguration(
    region: .USEast1,
    credentialsProvider: credentialsProvider
)

Please note that we are still testing that everything works as expected, but so far we've been able to get CognitoSync working properly.

Hope this is of some help to someone.

sahilanguralla commented 8 years ago

@andresgarza I am trying to integrate Amazon S3 utility with developer authenticated cognito identity provider but when I run the app, it raises error while download file saying identityId can't be nil So, I logged in overridden getIdentityId() to check whether it is being called or not. And what I found was that it was not being called. I also added logging to token() method and it was being logged after the aws logger said getting identity Id. So it means, even for getting identityId token() was being called. Could you please help me out with this as I am stuck in it for past many days?

Thanks in advance!

FilippoGiove commented 8 years ago

Thanks to this forum I'm now able to login in my app with my BYOIProvider and also using a Twitter Account. But I'm not able to switch from one provider to another after a logout. I think that the problem is how I init the AWSCognitoCredentialsProvider. If I use initWithRegionType:identityPoolId:unauthRoleArn:authRoleArn:identityProviderManager:, I'm able to login in Twitter, but not with my own Developer Authentication provider. Viceversa if I init the AWSCognitoCredentialsProvider with initWithRegionType:unauthRoleArn:authRoleArn:identityProvider: happens precisely the opposite. In according with the documentation, using one or other of them, the authentication flow used is different. I also read somewhere that is not possible to update or re-init the AWSServiceConfiguration

So, how can I switch from my BYOIProvider to Twitter and viceversa after a logout?

behrooziAWS commented 8 years ago

@FilippoGiove Hi Filippo, if you want to use both your BYOIProvider and Twitter together you should use initWithRegionType:identityPoolId:unauthRoleArn:authRoleArn:identityProviderManager: . In your implementation you should use identityProviderName and token from your BYOIProvider when logins is called and when they are using Twitter you should return logins for Twitter. Remember to call clearCredentials on the credentials provider when the user switches to force it to get new credentials on the next AWS call.

FilippoGiove commented 8 years ago

Thanks @behrooziAWS for your quickly reply.

In this way I have the following scenarios:

1 - I can login with Twitter, but If I logout (calling clearCredentials) and try to login with my BYOIProvider the following error occurs:

AWSiOSSDK v2.4.5 [Error] AWSIdentityProvider.m line:304 | __52-[AWSCognitoCredentialsProviderHelper getIdentityId]_block_invoke.255 | GetId failed. Error is [Error Domain=com.amazonaws.AWSCognitoIdentityErrorDomain Code=11 "(null)" UserInfo={__type=NotAuthorizedException, message=Invalid login token. Can't pass in a Cognito token.}]
2016-07-29 20:59:11.760 Dipty[3029:989075] AWSiOSSDK v2.4.5 [Error] AWSCredentialsProvider.m line:577 | __44-[AWSCognitoCredentialsProvider credentials]_block_invoke.352 | Unable to refresh. Error is [Error Domain=com.amazonaws.AWSCognitoIdentityErrorDomain Code=11 "(null)" UserInfo={__type=NotAuthorizedException, message=Invalid login token. Can't pass in a Cognito token.}]

The logins map is set in this way:

Logins: {
    "cognito-identity.amazonaws.com" = "BYOIProvider Token";
}

2 - Viceversa, if I log first in Twitter and the try to log with my BYOIProvider, I have the following error:

AWSiOSSDK v2.4.5 [Error] AWSCredentialsProvider.m line:527 | __44-[AWSCognitoCredentialsProvider credentials]_block_invoke | In refresh, but identityId is nil.
2016-07-29 21:04:07.662 Dipty[3035:990677] AWSiOSSDK v2.4.5 [Error] AWSCredentialsProvider.m line:577 | __44-[AWSCognitoCredentialsProvider credentials]_block_invoke.352 | Unable to refresh. Error is [Error Domain=com.amazonaws.AWSCognitoCredentialsProviderErrorDomain Code=1 "identityId shouldn't be nil" UserInfo={NSLocalizedDescription=identityId shouldn't be nil}]

The logins map is set in this way:

Logins: {
     "api.twitter.com" = "XXXXX-YYYYYY--> Twitter token format";
}
behrooziAWS commented 8 years ago

@FilippoGiove Hi Filippo, thanks for that detailed output. It looks like I gave you incorrect advice on the constructor to use. You should use: initWithRegionType:identityProvider: and pass the BYOIProvider as the identityProvider. The [http://docs.aws.amazon.com/AWSiOSSDK/latest/Protocols/AWSCognitoCredentialsProviderHelper.html#//api/name/identityProviderManager identityProviderManager] your BYOIProvider returns should return the logins map for either your BYOIProvider or twitter depending on who you are logged in as. There is also some work to return the proper identityId when getIdentityId is called on your BYOIProvider. I'll dig into that part a bit further.

behrooziAWS commented 8 years ago

@FilippoGiove Take a look at this sample code for ideas on how to implement getIdentityId.

behrooziAWS commented 8 years ago

@sahilanguralla Are you sure you are correctly implementing getIdentityId to return the identityId returned by GetOpenIdTokenForDeveloperIdentity that was passed back from your backend after authentication with your backend?

sahilanguralla commented 8 years ago

@behrooziAWS Yes, I have implemented it as it is (@andresgarza implementation) and replaced by Backend API Call in refresh() method. I added print() statements to check function call sequence. Only token() was called which in turn called refresh(). Also If I call refresh() at app launch, it works fine. Sounds to be some issue with AWSTask. But still getIdentityId() was never called.

andresgarza commented 8 years ago

@sahilanguralla something I forgot to mention was that on app launch, I authenticate my user with my own server and after that was successful, I call credentialsProvider.credentials() to force the credentials to refresh.

I did notice that getIdentityId was not called, but my refresh method does get called, and that sets the identifyId directly self.identityId = json["identityId"].stringValue

sahilanguralla commented 8 years ago

@andresgarza Thanks for the reply. I am doing the same in my case. It started working after that.

Now, I am stuck with AWS Pre Signed URL Service. It generates a URL which says:

<Error>
<Code>PermanentRedirect</Code>
<Message>
The bucket you are attempting to access must be addressed using the specified endpoint. Please send all future requests to this endpoint.
</Message>
</Error>

The region of the bucket is Global. So it should be accessible via any region.

@behrooziAWS If you could also look into this issue and suggest something, that would be great.

Thanks in advance.

biogerm commented 8 years ago

@yosuke-matsuda @simoami @helloniklas

Totally agree to have the implementation included in the tutorial or in a blog post.

Took me a few days to get through...

Tutorials are misleading and for such a "common" function, if you have to write a "custom" class for it, you definitely want your user to understand why...

More importantly, login is probably the very first function any AWS user starts with. I can't describe how hard it was for me to start using AWS when the very first tutorial doesn't work. Who would expect that? :)

vincent-coetzee commented 8 years ago

I agree totally with all the comments above, the documentation and sample code is really useless if one is using the latest SDKs. I have only recently started work with AWS Cognito and so naturally assumed going with the latest SDK would be the best choice, obviously not. I have a question regarding the unification of external login providers and User Pools. We wish to use Facebook, Twitter and Google as external login providers, as well as using an AWS Cognito User Pool for users who are not already registered with Facebook, Twitter or Google. I have managed to get Facebook, Twitter and Google logins working using a Custom Identity Provider, what I can NOT figure out is how to integrate User Pools into this equation. I have tried authenticating the user using the User Pool and that works fine, but when I add the identity token I receive from the User Pool into the logins keyed under "cognito-identity.amazonaws.com" I get an error

Invalid login token. Issuer doesn't match providerName

when trying to retrieve credentials. I am not sure how to proceed here, and I have read all the documentation from cover to cover to try and understand the process, but it's as clear as muddy water, the sample code I have won't even build when using the latest version of the SDK. Could someone please explain to me how to user a UserPool to authenticate a user in conjunction with external login providers. I do apologise if I have lost the plot here, but I find the documentation on AWS Cognito quite disgusting.

The steps I am following are

I created a custom identity provider as follows

class AWSCustomIdentityProvider: NSObject, AWSIdentityProviderManager
    {
    static let FacebookTokenKey = "graph.facebook.com"
    static let GoogleTokenKey = "accounts.google.com"
    static let TwitterTokenKey = "api.twitter.com"
    static let CognitoTokenKey = "cognito-identity.amazonaws.com"

    private var tokens : [NSString : NSString] = [:]

    func addToken(key:String,value:String)
        {
        tokens[key] = value
        }

    @objc func logins() -> AWSTask
        {
        return AWSTask(result: tokens)
        }
    }

I init a credentials provider as follows using my custom identity provider, and with my identity pool details

_credentialsProvider = AWSCognitoCredentialsProvider(regionType:.USEast1,identityPoolId:"us-east-1:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",identityProviderManager:_identityProvider)
let configuration = AWSServiceConfiguration(region:.USEast1, credentialsProvider:_credentialsProvider)
 AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = configuration

I add the token I receive from the external identity provider to the logins in my custom identity provider as necessary

I request credentials from the credentials provider when I need them

This all works for the external identity providers, and I assumed I could just use the identity token I received from the UserPool in the same way as the identity token I receive from the external identity providers, but keyed in the logins as "cognito-identity.amazonaws.com": user pool identity token. However this breaks with the "Invalid login token. Issuer doesn't match providerName" error when I attempt to retrieve credentials. Could someone please explain what I am doing wrong and how to use UserPools in this scenario.

Many thanks in advance

beeth0ven commented 8 years ago

I have this problem too! With AWSMobileHubHelper framework I can use Facebook and google login, But when integrate with user pool, I can login to my user pool independently, but I can't integrate it with federated id pool. I think there's a little steps I've missing. I've post my code here: Sample Code

Thanks for any help!

bpeck81 commented 8 years ago

Unlike with external providers, User Pool does not require that you directly add a new login to the credentials provider. I use this code to assign the user pool credential to the credentials provider:

let serviceConfiguration = AWSServiceConfiguration(region: .USEast1, credentialsProvider: nil)

let userPoolConfiguration = AWSCognitoIdentityUserPoolConfiguration(clientId: "YOUR_CLIENT_ID", clientSecret: "YOUR_CLIENT_SECRET", poolId: "YOUR_USER_POOL_ID")

AWSCognitoIdentityUserPool.registerCognitoIdentityUserPoolWithConfiguration(serviceConfiguration, userPoolConfiguration: userPoolConfiguration, forKey: "UserPool")

let pool = AWSCognitoIdentityUserPool(forKey: "UserPool")

let credentialsProvider = AWSCognitoCredentialsProvider(regionType: .USEast1, identityPoolId: "YOUR_IDENTITY_POOL_ID", identityProviderManager:pool)

Then get the user from pool.getUser(), call user.getsession(), and credentialsprovider.getIdentityId()

I am not sure that this is the best way, but it works for me.

bpeck81 commented 8 years ago

@vincent-coetzee I think you can add the user pool login the way you are trying but the token key you are using is wrong. I believe it is this cognito-idp.<region>.amazonaws.com/<YOUR_USER_POOL_ID>

eleibu commented 8 years ago

@behrooziAWS Thank you for the code. Unfortunately for me it gives errors. Sign in works fine but if I close the app and return to it after a few hours, I get this: "GetCredentialsForIdentity keeps failing. Clearing identityId did not help.".

I've been grappling with Developer Authenticated Identities under v2.4 of the API for months now. I've found the code below seems to work almost all the time. I say "almost" because I had been using it without any problems for weeks, but one of my users received the same error (as above) the other day. Signing out and back in fixed it, but I'm worried that there is a subtle problem which will appear occasionally.

Here is my code. If anyone can explain why the error occurred, or what this code is missing, I'd be extremely grateful:

class DeveloperAuthenticatedIdentityProvider: AWSCognitoCredentialsProviderHelper {
    var cachedLogin: [NSString : NSString]?

    override func logins() -> AWSTask {
        if (self.identityId != nil) && (self.cachedLogin != nil) {
            return AWSTask(result: cachedLogin)
        } else {
            return self.getLogin()
        }
    }

    func getLogin() -> AWSTask {
        return self.token().continueWithBlock { (task:AWSTask!) -> AnyObject! in
            if (task.result != nil) {
                let jsonDictionary = task.result as! NSDictionary

                self.identityId = jsonDictionary["identityId"] as? String

                let login: [NSString : NSString] = ["cognito-identity.amazonaws.com" : jsonDictionary["token"] as! String]
                self.cachedLogin = login
                return AWSTask(result: login)
            } else {
                return AWSTask(result: nil)
            }
        }
    }

    override func token() -> AWSTask {
        let awstask = AWSTaskCompletionSource()
        let request = AFHTTPSessionManager()

        var username = "username"
        var password = "password"
        let apiUrl = "https://" + username + ":" + password + "@something.com/api"

        request.GET(apiUrl, parameters: nil, progress: nil,
            success: {
                (task: NSURLSessionDataTask, response: AnyObject?) -> Void in

                awstask.setResult(response)
            },
            failure: {
                (task: NSURLSessionDataTask?, error: NSError) -> Void in

                awstask.setError(error)
            }
        )

        return awstask.task
    }
}

When the app launches I set the Default Service Configuration like this:

let identityProvider = DeveloperAuthenticatedIdentityProvider(regionType: cognitoRegion, identityPoolId: identityPoolId, useEnhancedFlow: true, identityProviderManager: nil)
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: cognitoRegion, unauthRoleArn: nil, authRoleArn: nil, identityProvider: identityProvider)
let configuration = AWSServiceConfiguration(region: defaultServiceRegion, credentialsProvider: credentialsProvider)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = configuration

After authenticating my user with my server I simply call an AWS service (such as invoking a Lambda function). The API seems to take care of retrieving the identityId / token automatically (ie. no need to manually call credentialsProvider.credentials() or credentialsProvider.getIdentityId()).

mystafer commented 8 years ago

I have followed this thread and I understand using the AWSIdentityProviderManager to provide logins mechanism to AWSCognitoCredentialsProvider instead of assigning the property directly works during initialization. However, how does one link an account to an existing identity with the 2.4 library changes? I would like to transition a guest user to a logged in user or attach a second login to an existing identity.

Even if I create a custom AWSIdentityProviderManager as described to allow login tokens to be altered, there is no way to refresh the credentials manager. If I resetKeychain on the credentials manager the anonymous identity is lost and a new identity is used based on the provider's login values.

behrooziAWS commented 8 years ago

@mystafer invalidateCachedTemporaryCredentials will just clear the credentials and leave the identity id. http://docs.aws.amazon.com/AWSiOSSDK/latest/Classes/AWSCognitoCredentialsProvider.html#//api/name/invalidateCachedTemporaryCredentials

behrooziAWS commented 8 years ago

@eleibu one thing that I notice is your getLogins will return successfully even on error with a nil logins map. Seems like return self.token().continueWithBlock should use continueWithSuccessBlock instead to pass the error on up otherwise. You can't go from an authenticated user to an unauthenticated user (nil logins) so if you've authenticated once successfully, this will cause access denied if your api ever returns an error.

eleibu commented 8 years ago

@behrooziAWS Thank you very much, I'll give that a try.

By the way, like you and @sahilanguralla, I found that getIdentityId() was never called, which is why I didn't implement it. I note that I have implemented logins() and you've implemented refresh(). Do you know which of these is required? I've checked and can confirm that logins() is being called.

Another difference between your code and mine is the "developer-provider-name". Seems strange to me that it would work either way.

mystafer commented 8 years ago

@behrooziAWS Thank you for pointing me in the direction of invalidateCachedTemporaryCredentials.

I found that it invalidated the credentials locally but did not affect the identity in the pool when I checked the AWS dashboard. I could verify this because immediately after invalidating the credentials there was no communication with the server.

However, I did find that if I finished with a call to credentials() after invalidating that I could force the server to synchronize with the client and the login was indeed attached to the identity. Is the call to credentials() a requirement after invalidation to update the server or is there a better technique?

aidanlister commented 8 years ago

Is AWSSTSCredentialsProvider broken as well? We upgraded and it stopped working.

Edit:

Oh I see, this is actually our code.

rbarbish commented 8 years ago

Please update http://docs.aws.amazon.com/cognito/latest/developerguide/developer-authenticated-identities.html since AWSAbstractCognitoIdentityProvider has been removed...

mnolanjr98 commented 8 years ago

Anyone running into the issue where the AWSCognitoCredentialsProvider returns the same identityId value regardless of which user pool user being authenticated with? The identity pool seems to treat all of my user pool ids as unauthenticated identity requests (the login count for my pool is 0 in the federated identity dashboard), even though I have logged into this app on the same device with a few different accounts.

This is causing issues when I later try to use Cognito Sync. All users end up sharing the same dataset(s).

bartleby commented 8 years ago

WTF? where is documentation? how to work with new AWS SDK (2.4.*)?

xquezme commented 8 years ago

Demo not working with expired tokens. Got "Invalid login token. Not a Cognito token". Same backend.

xquezme commented 8 years ago

[day after headache]

Current default implementation of [AWSCognitoCredentialsProviderHelper logins] :

    if (self.identityProviderManager && self.useEnhancedFlow) {
        self.cachedLogins = nil;
        return [[self getIdentityId] continueWithSuccessBlock:^id _Nullable(AWSTask<NSString *> * _Nonnull task) {
            if(self.cachedLogins){
                return [AWSTask taskWithResult:self.cachedLogins];
            }
            else {
                return [self.identityProviderManager logins];
            }
        }];
    }
    return [[self token] continueWithSuccessBlock:^id _Nullable(AWSTask<NSString *> * _Nonnull task) {
        if (!task.result) {
            return [AWSTask taskWithResult:nil];
        }
        NSString *token = task.result;
        return [AWSTask taskWithResult:@{self.identityProviderName : token}];
    }];

So, I'm passed...

useEnhancedFlow = NO

...to my "BYOIProvider", and it's works like in 2.3.6.

Update: Working example: https://gist.github.com/xquezme/b62eb412ca25dd3a61ded1753ed91492

nfinke commented 8 years ago

@mnolanjr98 Yes I have the same issue. Did you find the cause?

mnolanjr98 commented 8 years ago

Nope, haven't figured it out. I think I read somewhere that you need to write a custom Identity Provider to use both Authenticated and Unauthenticated accounts, which seems silly seeing they are really promoting the User Pools. No real clear guidance on how to do this - it's really weird and frustrating that the SDK doesn't tie together all of the capabilities offered in the Mobile Hub (especially seeing it is not out of Beta).

LamourBt commented 8 years ago

I am using Authenticated provider (facebook), everything such as the AWSIdentityProviderManager however something weird is going on, every time I try to register the device it kept saying that "Unauthenticated access is not supported for this identity pool". But I know that I did not check the box for unauthenticated, it seems that cognito is treating facebook as one.

behrooziAWS commented 7 years ago

@LamourBt Are you using Xcode 8 in the simulator by any chance? If so, make sure you have enabled the Keychain Sharing entitlement capability under Targets->Capabilities->Keychain Sharing. We are tracking an apple radar issue where that must be enabled to get keychains and Facebook to work. With a simple implementation of the IdentityProviderManager below and Keychain Sharing on, i'm able to get it to work:

FacebookIdentityProviderManager.h

import <Foundation/Foundation.h>
#import <AWSCore/AWSCore.h>
#import <AWSCognito/AWSCognito.h>

@interface FacebookIdentityProviderManager : NSObject <AWSIdentityProviderManager>

@end

FacebookIdentityProviderManager.m

#import "FacebookIdentityProviderManager.h"
#import <FBSDKCoreKit/FBSDKCoreKit.h>

@implementation FacebookIdentityProviderManager

- (AWSTask<NSDictionary<NSString *, NSString *> *> *)logins {

    //Use Case 1. User is authenticated and you possess a valid token
    //i.e. for Facebook
    FBSDKAccessToken* fbToken = [FBSDKAccessToken currentAccessToken];
    if(fbToken){
        NSString *token = fbToken.tokenString;
        return [AWSTask taskWithResult: @{ AWSIdentityProviderFacebook : token }];
    }else {

        return [AWSTask taskWithError:[NSError errorWithDomain:@"facebook"
                                                          code:-1
                                                      userInfo:@{@"error":@"No current Facebook access token"}]];
    }
}

@end
LamourBt commented 7 years ago

@behrooziAWS yes I have done that before ... this is not the problem

2016-09-21 21:30:16.789601 LoginWithFaceBookIOS10[27752:6534364] AWSiOSSDK v2.4.9 [Debug] AWSURLSessionManager.m line:553 | -[AWSURLSessionManager printHTTPHeadersForResponse:] | Response headers: { Connection = "keep-alive"; "Content-Length" = 111; "Content-Type" = "application/x-amz-json-1.1"; Date = "Thu, 22 Sep 2016 01:30:16 GMT"; "x-amzn-ErrorMessage" = "Unauthenticated access is not supported for this identity pool."; "x-amzn-ErrorType" = "NotAuthorizedException:"; "x-amzn-RequestId" = "1d701ba6-8064-11e6-89f1-393f422a0785"; } 2016-09-21 21:30:16.790196 LoginWithFaceBookIOS10[27752:6534364] AWSiOSSDK v2.4.9 [Debug] AWSURLResponseSerialization.m line:63 | -[AWSJSONResponseSerializer responseObjectForResponse:originalRequest:currentRequest:data:error:] | Response body: {"type":"NotAuthorizedException","message":"Unauthenticated access is not supported for this identity pool."} 2016-09-21 21:30:16.793375 LoginWithFaceBookIOS10[27752:6534364] AWSiOSSDK v2.4.9 [Error] AWSIdentityProvider.m line:304 | 52-[AWSCognitoCredentialsProviderHelper getIdentityId]_block_invoke.255 | GetId failed. Error is [Error Domain=com.amazonaws.AWSCognitoIdentityErrorDomain Code=8 "(null)" UserInfo={type=NotAuthorizedException, message=Unauthenticated access is not supported for this identity pool.}] 2016-09-21 21:30:16.793988 LoginWithFaceBookIOS10[27752:6534357] AWSiOSSDK v2.4.9 [Error] AWSCredentialsProvider.m line:605 | 46-[AWSCognitoCredentialsProvider getIdentityId]_block_invoke | In refresh, but identityId is nil. 2016-09-21 21:30:16.794154 LoginWithFaceBookIOS10[27752:6534357] AWSiOSSDK v2.4.9 [Error] AWSCredentialsProvider.m line:606 | 46-[AWSCognitoCredentialsProvider getIdentityId]_block_invoke | Result from getIdentityId is (null) 2016-09-21 21:30:16.795122 LoginWithFaceBookIOS10[27752:6534357] AWSiOSSDK v2.4.9 [Error] AWSCognitoService.m line:338 | 37-[AWSCognito registerDeviceInternal:]_block_invoke.262 | Unable to register device: Error Domain=com.amazon.cognito.AWSCognitoErrorDomain Code=-4000 "(null)" Error while registering device: Optional("The operation couldn’t be completed. (com.amazon.cognito.AWSCognitoErrorDomain error -4000.)") 2016-09-21 21:30:30.949589 LoginWithFaceBookIOS10[27752:6534292] [MC] System group container for systemgroup.com.apple.configurationprofiles path is /private/var/containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles 2016-09-21 21:30:30.958104 LoginWithFaceBookIOS10[27752:6534292] [MC] Reading from public effective user settings.

I should not get any of that issue since I am using authentication with facebook. I know that I did not check the box for unauthenticated (and if I do these credentials get saved as unauthenticated)

behrooziAWS commented 7 years ago

@LamourBt then it is likely you have not set up your credentials provider with a identityProviderManager that returns the logins map containing the Facebook token correctly. Turn on verbose logging to confirm, but if you see an empty logins map during the call to GetId, it isn't properly set up.

LamourBt commented 7 years ago

@behrooziAWS I am still having the same issue; I think the problem is with the defaultServiceConfiguration because in my appDelegate if I don't set the defaultServiceConfiguration the app would crash. I try to override that those once I have the facebook token from my singleton class but it did not override. So I upload the project on github, if you have a chance please take a look at my appDelegate and my singleton class [(https://github.com/LamourBt/Facebook-Cognito/blob/master/LoginWithFaceBookIOS10/LoginWithFacebook%2BCognito.swift)]

behrooziAWS commented 7 years ago

@LamourBt I took a look at your code. Couple of things are going on here.

  1. The reason you need to setup a defaultServiceConfiguration in your AppDelegate is that you are trying to use a Cognito Sync dataset to store your device token prior to authenticating your end user and Sync requires AWS credentials even for an unauthenticated user. Without a credentials provider to obtain those credentials your app will crash.
  2. You can only set defaultServiceConfiguration once, but you are attempting to set it again in your LoginWithFacebook class.

Some ideas for fixing it:

  1. The correct way of setting up your default service configuration is to initialize it once with your IdentityProviderManager in your AppDelegate.
  2. You should pass a handle to your IdentityProviderManager into your LoginWithFacebook class so it can set the logins map on it.
  3. If logins isn't set in IdentityProviderManager, you should just return nil instead of an error, this will allow you to get credentials against an unauthenticated identity.
  4. When you set the logins to Facebook in your IdentityProviderManager you should call clearCredentials on your credentialsProvider, this will force the credentialsProvider to get credentials against your authenticated user on your next call to an AWS service and will link your unauthenticated user to Facebook so it becomes authenticated.

Keep in mind that once a user becomes authenticated, you must always setup the logins with a valid token from Facebook or whichever provider they authenticated with in order to get AWS credentials. You cannot transition from an authenticated user back to an unauthenticated user.

Bhat123 commented 7 years ago

How can i set accessKey,secretkey by customising the provider class using these classes ? AWSCredentials, AWSCredentialsProvider

ghost commented 7 years ago

I am also trying to figure out how to unify external providers and User Pools, though I don't as of yet have any need for a custom login provider.

Is there any documentation out there or (better) sample code that shows how to do this? Getting even some chance of understanding architecture would be great, so we can start making plans on how we're going to implement all of this.