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

Developer authentication with Cognito Identity using Custom Provider #1774

Closed sykhan88 closed 4 years ago

sykhan88 commented 5 years ago

How can I setup developer authentication for a custom provider. The documentation here isn't sufficient and left a lot of unanswered questions. Unfortunately, there isn't much info out in the sites like stack overflow so turning to the developers on github. If there is a sample out there, I would really appreciate it I have created a Federated Identity pool with a customer provider. The Authentication mechanism I am using is Fb AccountKit. Note that this is not the same as Facebook login authentication. Its an independent provider which Fb provides to allow for passwordless authentication using phone or emails. This authentication successfully gives me an access token after authenticating a user via phone or email token. This workflow is built in with the account kit api. The documentation here says to use federersignIn as follows:

AWSMobileClient.sharedInstance().federatedSignIn(providerName: IdentityProvider.developer.rawValue,
                                                        token: "YOUR_TOKEN",
                                       federatedSignInOptions: FederatedSignInOptions(cognitoIdentityId: identityId!)) { (userState, error) in
    if let error = error as? AWSMobileClientError {
        print(error.localizedDescription)
    }
    if let userState = userState {
        print("Status: \(userState.rawValue)")
    }
}

In the above signing, what is the token it is referring to? the token from my authentication provider or the openID connect token generated by Cognito?
The developer documentation suggests to get the cognitoidentityID and token using the following method:

class DeveloperAuthenticatedIdentityProvider : AWSCognitoCredentialsProviderHelper {
    override func token() -> AWSTask<NSString> {
    // 1. Write code to call your backend:
    // 2. pass username/password to backend or some sort of token to authenticate user, if successful, 
    // 3. from backend call getOpenIdTokenForDeveloperIdentity with logins map containing "your.provider.name":"enduser.username"
    // 4. return the identity id and token to client
    // 5. You can use AWSTaskCompletionSource to do this asynchronously

    // 6. Set the identity id and return the token
    self.identityId = resultFromAbove.identityId
    return AWSTask(result: resultFromAbove.token)
}

From the above, I am doing 1. and 2. outside of this method and essentially getting an accesstoken from AccountKit confirming user login. In the token() method, I am just retrieving the identityID and token using getOpenIdTokenForDeveloperIdentity. Is that correct? And then using this Identity provider by passing it into AWSCognitoCredentialsProvider as follows

let devAuth = DeveloperAuthenticatedIdentityProvider(regionType: .YOUR_IDENTITY_POOL_REGION, identityPoolId: "YOUR_IDENTITY_POOL_ID", useEnhancedFlow: true, identityProviderManager:nil)
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: .YOUR_IDENTITY_POOL_REGION, identityProvider:devAuth)
let configuration = AWSServiceConfiguration(region: .YOUR_IDENTITY_POOL_REGION, credentialsProvider:credentialsProvider)
AWSServiceManager.default().defaultServiceConfiguration = configuration

However it is unclear to me how to provide login map mentioned in step 3. of the override function token() anywhere?
Additionally, from the above implementation, how am I supposed to extract the token and identityID to be used in my federatedsignin?

My implementation of the above override func token() is as follows: I am providing a dummy enduser.username by the name of shery_test.

class DeveloperAuthenticatedIdentityProvider : AWSCognitoCredentialsProviderHelper {    
    override func token() -> AWSTask<NSString> {
       var cognitoToken:String = ""
        AWSCognitoIdentityGetOpenIdTokenForDeveloperIdentityInput()?.identityPoolId = cognitoIdentityPoolID
        AWSCognitoIdentityGetOpenIdTokenForDeveloperIdentityInput()?.logins = [IdentityProvider.developer.rawValue: "shery_test"]
        AWSCognitoIdentity().getOpenIdToken(forDeveloperIdentity:AWSCognitoIdentityGetOpenIdTokenForDeveloperIdentityInput()) { (data, error) in
            if let error = error {
                print("Failed to retrieve Cognito IdentityId and Token: \(error.localizedDescription)")
                print("error: \(error)")
            }else{
                print("Successfully retrieved Cognito IdentityId and Token: \(error?.localizedDescription ?? "no error")");
                self.identityId = data?.identityId
                if let token = data?.token{
                    cognitoToken = token
                }
            }
        }
        return AWSTask(result: cognitoToken as NSString)
}

However, when I put a breakpoint within the token() func it does not get used at all. Am I supposed to call the token() separately somewhere in my code? Additionally, after initialising credentials provider, I run the following code:

        AWSMobileClient.sharedInstance().getIdentityId().continueWith { task in
            if let error = task.error as? AWSMobileClientError {
                print("error: \(error)")
                print("error: \(error.localizedDescription) \((error as NSError).userInfo)")
            }
            if let result = task.result {
                print("identity id: \(result)")
            }
            return nil
        }
       return true

I get the following error:

error: cognitoIdentityPoolNotConfigured(message: "Cannot get identityId since cognito credentials configuration is not available.")
error: The operation couldn’t be completed. (AWSMobileClient.AWSMobileClientError error 30.) [:]

Why can't I retrieve get a identityID which I am supposed to get using the DeveloperAuthenticatedIdentityProvider class?

Which AWS Services are you utilizing? AWSMobileClient, AWSCore

Environment(please complete the following information):

sykhan88 commented 5 years ago

Hello guys. Would really appreciate a response on this. Essentially still stuck and would really like to understand if i am on the right track or way off..

royjit commented 5 years ago

Hi @sykhan88 Please follow the guide given here. I think you are mixing two ways of doing the same thing.

Basically you just need to initialize AWSMobileClient and then pass the token that you get from authentication to AWSMobileClient.sharedInstance().federatedSignIn. After this just call AWSMobileClient.sharedInstance().getIdentityId() to get the identityId.

Let us know if this worked for you.

sykhan88 commented 5 years ago

What two ways are you referring to? I have followed the mentioned guide, however it doesn't lend enough detail. I have tried for a few days without success. Does the federatedSignIn method use the openID connect token generated by Cognito? Can you please provide further detail on how to retrieve the token? My above trial of retrieving the token has failed.

behrooziAWS commented 5 years ago

For these steps:

// 1. Write code to call your backend:
// 2. pass username/password to backend or some sort of token to authenticate user, if successful, 
// 3. from backend call getOpenIdTokenForDeveloperIdentity with logins map containing "your.provider.name":"enduser.username"

You need to actually have a backend you are calling. GetOpenIdTokenForDeveloperCredentials requires AWS credentials to make the call and you don't have any yet on your device (which is why it is failing with error: cognitoIdentityPoolNotConfigured(message: "Cannot get identityId since cognito credentials configuration is not available.")) . This backend can be an unauthenticated API Gateway API, but it needs to be able to validate the token you provide and call getOpenIdForDeveloperIdentity for that users' login using AWS credentials. It will return an identity id and Cognito token which you then return.

sykhan88 commented 5 years ago

How do you mean, I don't have a backend? I am using FB AccountKit as my backend for user passwordless authentication. The user is successfully able to login using either email address or phone number. This is quite straightforward and i have followed the approach here. User can login using viewcontroller provided by Account kit such as:

private let accountKitManager = AccountKit(responseType: .accessToken)
accountKitManager.viewControllerForPhoneLogin()
accountKitManager.viewControllerForEmailLogin()

This should cover point 1 and 2, you have mentioned. I guess what I am really struggling with is how to call getOpenIdTokenForDeveloperIdentity. The developer documentation mentions using DeveloperAuthenticatedIdentityProvider class to call getOpenIdTokenForDeveloperIdentity and, I have tried to do it here as follows:

class DeveloperAuthenticatedIdentityProvider : AWSCognitoCredentialsProviderHelper {    
    override func token() -> AWSTask<NSString> {
       var cognitoToken:String = ""
        AWSCognitoIdentityGetOpenIdTokenForDeveloperIdentityInput()?.identityPoolId = cognitoIdentityPoolID
        AWSCognitoIdentityGetOpenIdTokenForDeveloperIdentityInput()?.logins = [IdentityProvider.developer.rawValue: "shery_test"]
        AWSCognitoIdentity().getOpenIdToken(forDeveloperIdentity:AWSCognitoIdentityGetOpenIdTokenForDeveloperIdentityInput()) { (data, error) in
            if let error = error {
                print("Failed to retrieve Cognito IdentityId and Token: \(error.localizedDescription)")
                print("error: \(error)")
            }else{
                print("Successfully retrieved Cognito IdentityId and Token: \(error?.localizedDescription ?? "no error")");
                self.identityId = data?.identityId
                if let token = data?.token{
                    cognitoToken = token
                }
            }
        }
        return AWSTask(result: cognitoToken as NSString)
}

Is the above implementation correct? This class is supposed to return cognitoToken and IdentityID. however, I am not sure how and where in my code am i supposed to call this class and retrieve the token and identityID.

timwhunt commented 5 years ago

Hi sykhan88, behrooziAWS was explaining that you need to have your own "backend" to call GetOpenIdTokenForDeveloperCredentials. That is, you need to have an environment (e.g., on Amazon EC2 or Lambda) from which you can call GetOpenIdTokenForDeveloperCredentials using AWS credentials (i.e., for an AWS IAM user). That call to GetOpenIdTokenForDeveloperCredentials must be authenticated with your AWS credentials, which should not be used from a web or mobile app/client. FB AccountKit cannot be your backend, but is a 3rd party service you are using for authenticating users. I hope that helps.

sykhan88 commented 4 years ago

Thanks timwhunt. I think I have missed a major trick here. I am new to backend development so didnt really appreciate the intricacies. The DeveloperAuthenticatedIdentityProvider class which contains the call getOpenIdTokenForDeveloperIdentity is in Swift so I assumed I call it from my mobile client. The documentation on AWS doesn't go into enough detail about building a backend to enable this. If there is any guide or an example out there which I could follow, i would appreciate it.

palpatim commented 4 years ago

@sykhan88 You can find more information about getting your server-side tokens here.

If you have other questions on the server-side setup, please check out the (AWS Forums)[https://forums.aws.amazon.com/forum.jspa?forumID=173], which is where conversations around the Cognito service happen.

Hope this helps.

sykhan88 commented 4 years ago

@palpatim Many thanks. Ill give this a go.