aws-amplify / amplify-swift

A declarative library for application development using cloud services.
Apache License 2.0
456 stars 198 forks source link

iOS: Create AWS API Plugin Amplify Models when programmatically configuring Amplify #1130

Closed paluka closed 3 years ago

paluka commented 3 years ago

Hi, in my iOS Swift app, I am configuring amplify manually using AWSCognitoAuthPlugin + AWSAPIPlugin plugins and their respective configuration variables. I want to be able to use GraphQL queries to grab data and the documentation states that I need to generate models for example ToDo model. Since I am configuring Amplify manually (inline code) to use existing AWS resources, how do I generate these models manually? amplify codegen models CLI command states that:

You are not working inside a valid Amplify project. Use 'amplify init' in the root of your app directory to initialize your project, or 'amplify pull' to pull down an existing project.

and amplify pull states:

No Amplify apps found. Please ensure your local profile matches the AWS account or region in which the Amplify app exists. Failed to pull the backend.

I am able to get Amplify.Auth.signIn working with a proper username + password, so the authConfig is correct.

   let authConfig = AuthCategoryConfiguration(plugins: [
        "awsCognitoAuthPlugin": [
            "CognitoUserPool": [
                "Default": [
                    "PoolId": ...,
                    "Region": ...,
                    "AppClientId": ...,
          ]]])

    let apiConfig = APICategoryConfiguration(plugins: [
        "awsAPIPlugin": [
            "api": [
                "endpointType": "GraphQL",
                "endpoint": "[APPSYNC ENDPOINT]",
                "region": ...,
                "authorizationType": "AMAZON_COGNITO_USER_POOLS",
      ]]])

    try Amplify.add(plugin: AWSCognitoAuthPlugin())
    try Amplify.add(plugin: AWSAPIPlugin(), modelRegistration: AmplifyModels()))
    try Amplify.configure(AmplifyConfiguration(api: apiConfig, auth: authConfig))

   Amplify.API.query(request: .list(ToDo.self)) { event in ...
paluka commented 3 years ago

Do I need to create a model or can I make a query like the below:

query: `query QueryAccount{
              account{
                id
              name
              email {
                  address
              }
              phone {
                number
              }
                partners{
                  edges{
                    cursor
                    node{
                      tenants{
                        id
                        name
                      }
                    }
                  }
                }
              }
            }`
ruiguoamz commented 3 years ago

Hi, @paluka Thanks for reaching out.

What you can do is:

  1. Navigate to your AWS Amplify Console
  2. Click on the Amplify App in the console that you want to pull down
  3. There is a Edit backend button. You can see the provided command line something like "amplify pull --appId ***** --envName dev"
  4. Copy and paste that line to the root of your project and execute.
  5. Once finish, run amplify pull. Upon completion, the amplify environment is in your local computer including the schema defined previously.
  6. Run amplify codegen models and it should generate the correct AmplifyModels files for you to use.

Hope this helps you. Feel free to reach out if it doesn't work.

paluka commented 3 years ago

Hi @ruiguoamz, there are no Amplify apps in my AWS. I am using the Amplify iOS libraries to use "existing Cognito and other AWS resources. Connect to them from your app with the Amplify Libraries." See the attached image:

Screen Shot 2021-03-30 at 2 13 43 PM
palpatim commented 3 years ago

As @ruiguoamz investigates the right way to get codegen of your existing API into your project...

Do I need to create a model

You can use the Amplify API category without generating models by writing custom GraphQL queries. See the advanced workflows documentation for examples of using custom documents to query your backend.

Code-generated models provide a convenient way of working with API data, since Amplify understands how to query, mutate, and subscribe to models without having to write custom documents. Behind the scenes, Amplify is just converting those model-based calls to a GraphQL document using metadata stored in the model schema, saving you the work of writing the document and maintaining it as you evolve your GraphQL schema.

paluka commented 3 years ago

Thank you @palpatim. We are using Amazon API Gateway which goes to a Lambda function that acts as proxy for GraphQL

paluka commented 3 years ago

@palpatim @ruiguoamz Since I am using Amazon API Gateway, I've switched the AWS Amplify API plugin from APPSync GraphQL to Amazon API Gateway REST endpoint type, but I am getting http 401 response unauthorized.

   let apiConfig = APICategoryConfiguration(plugins: [
        "awsAPIPlugin": [
            "<name-of-api>": [
                "endpointType": "REST",
                "endpoint": "https://<...>.execute-api.us-west-2.amazonaws.com/dev/graphql",
                "region": amplifyAuthConfig.region,
                "authorizationType": "AMAZON_COGNITO_USER_POOLS", 
                // AMAZON_COGNITO_USER_POOLS, AWS_IAM, OPENID_CONNECT, or API_KEY
   ]]])

The endpoint requires an Authentication Bearer token header value, so I grab it from:

   var idToken = ""
   Amplify.Auth.fetchAuthSession { result in
        do {
            let session = try result.get()

            // Get cognito user pool token
            if let cognitoTokenProvider = session as? AuthCognitoTokensProvider {
                let tokens = try cognitoTokenProvider.getCognitoTokens().get()
                idToken = tokens.idToken
   }   }   }

then make the AWS API Gateway post http call to the lambda GraphQL proxy

   Amplify.API.post(request: .getAccount(token: idToken)) { result in ...

with getAccount being

   extension RESTRequest {
      static func getAccount(token: String) -> RESTRequest {
          let graphQLQuery = ...
          return RESTRequest(headers: ["Authorization": "Bearer \(token)"], body: body.data(using: .utf8))
   }  }

The idToken that I am grabbing works in Postman Post request. Am I doing it correctly in iOS Swift?

palpatim commented 3 years ago

If your API Gateway endpoint is set up with Cognito User Pools authorization, you should just be able to configure your plugin to use AMAZON_COGNITO_USER_POOLS authorization and have it 'just work', as described here.

It sounds like your custom specification of the Authorization header is being overwritten by the standard User Pools behavior that injects an appropriate Authorization header when it detects that your API has an authorizationType of AMAZON_COGNITO_USER_POOLS.

The authorizationType key also supports a value of NONE, which will cause Amplify to not attempt to inject any authorization tokens into the request. That should allow your custom header to work as expected.

Finally: I see that we don't have the NONE value documented in our API Authorization docs. That seems to be a miss that we'll need to rectify. I'll open a docs issue for that.

github-actions[bot] commented 3 years ago

This issue is stale because it has been open for 14 days with no activity. Please, provide an update or it will be automatically closed in 7 days.

github-actions[bot] commented 3 years ago

This issue is being automatically closed due to inactivity. If you believe it was closed by mistake, provide an update and re-open it.

lawmicha commented 3 years ago

draft PR https://github.com/aws-amplify/docs/pull/3210