Search code examples
swiftamazon-web-servicesgraphqlamazon-cognitoaws-amplify

Associate user information from Cognito with AWS Amplify GraphQL


I am on xcode 11.4, Swift 4. The goal is to:

  1. sign up a new user in Cognito User Pool, and then save an associated user record using Amplify GraphQL.

  2. CRUD the user's record after signing in with Cognito User Pool.

The problem is I do not know how to associate Cognito with Amplify GraphQL. For example, in Google Firebase auth and Firestore, I would get a unique user id UID after signing up, then I would create an associated user record in Firestore with the key as this UID. Then on user signin/authentication, I can get this UID from firebase auth and find the associated record in firestore.

Currently with the AWS stack, I created a user model in schema.graphql as:

type User @model @auth(rules: [{ allow: owner, ownerField: "id", operations: [create, update, delete]}]){
    id: ID!
    firstName  : String
    lastName   : String
    handle     : String
    email      : String!
}

So that only authenticated user can create, update and delete. Next somewhere in SignUpController I create a new user:

AWSMobileClient.default().signUp( username: email
                                , password: password
                                , userAttributes: ["email": email]) { (signUpResult, error) in
    if let signUpResult = signUpResult {

        switch(signUpResult.signUpConfirmationState) {
            case .confirmed:
                self.showAlert(msg: "You already have an account. Please go back and press log in")
            case .unconfirmed:
                break 
            case .unknown:
                self.showAlert(msg: "Network error")
        }
    } else if let error = error { ... }

And then confirm the user w/ code:

AWSMobileClient.default().confirmSignUp(username: email, confirmationCode: code) { (signUpResult, error) in
    if let signUpResult = signUpResult {
        switch(signUpResult.signUpConfirmationState) {
            case .confirmed:
               // This is where I need to create an associated user account
               break
            case .unconfirmed:
                self.showAlert(title: "Error", msg: "User is not confirmed and needs verification via \(signUpResult.codeDeliveryDetails!.deliveryMedium) sent at \(signUpResult.codeDeliveryDetails!.destination!)")
            case .unknown:
                self.showAlert(title: "Error", msg: "Network error")
        }
    } else { //if let error = error {
        self.showAlert(title: "Error", msg: "Network error")
    }

Right now my solution in case .confirmed is to sign in immediately, and then fetch the user's client token via:

class CognitoPoolProvider : AWSCognitoUserPoolsAuthProviderAsync {

    /// this token may not be what you want ...
    func getLatestAuthToken(_ callback: @escaping (String?, Error?) -> Void) {

        AWSMobileClient.default().getTokens { (token, error) in
            if let error = error {
                callback(nil,error)
            }
            callback(token?.accessToken?.tokenString, error)
        }
    }
}

This turns out to be the wrong solution, since the user's client token changes all the time.

Overall, this is a standard hello-world problem, and there should be a standard out of box solution provided by AWS. I search the docs and github, but cannot find a satisfactory answer.


Solution

  • The right way is DON'T TRUST CLIENT for creating associate user information from Cognito, you have to do it at server side.

    You should create a new Lambda Post Confirmation Trigger for Cognito and code it to create an associate account. You can use event.userName or create custom attribute uuid type likes custom:id to link your associate account.

    Ref: https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-post-confirmation.html