amplify-swift icon indicating copy to clipboard operation
amplify-swift copied to clipboard

federatedSignIn Works for REST But Not GraphQL API

Open Etep15 opened this issue 3 years ago • 3 comments

Describe the bug

Using AWSMobileClient.federatedSignIn works when using a REST API (API Gateway / Lambdas) but not when using a GraphQL API (AppSync / Datastore). For some reason when using GraphQL it doesn't send the authorization properly or is that AppSync does not recognize federated identities?

I've tried the "web sign in" but not only does it visually not look proper for a native iOS app. But it is causing no end in grief to implement in our existing system. For instance the cognitoAuthenticationProvider values are so different than when using federated identities.

Steps To Reproduce

Sign in using a native federated sign in (in our example we use Sign in With Apple) using the following code... 

	@objc func signIn (withToken token: String, andGivenName givenName: String, andFamilyName familyName: String, success: @escaping ()->(), failure: @escaping (_ error: NSError?)->())
	{
		print ("Signing in to Cognito...")

		guard
			let plugin = try? Amplify.Auth.getPlugin(for: AWSCognitoAuthPlugin().key),
			let authPlugin = plugin as? AWSCognitoAuthPlugin,
			case .awsMobileClient (let client) = authPlugin.getEscapeHatch()
		else
		{
			failure (nil); // error
			return
		}
		
		client.federatedSignIn(providerName: IdentityProvider.apple.rawValue, token: token) { (state, error) in

			if let unwrappedError = error
			{
				print (unwrappedError)
				failure (nil)
			}
			else if let unwrappedState = state
			{
				print ("Successful federated sign in:", unwrappedState)
					
				success ();
			}
		}
	}

Then make a call to the REST API using the following code : 

        @objc func doGet (path:String, success: @escaping (_ result: String)->(), failure: @escaping (_ error: NSError)->())
	{
		let request = RESTRequest (path: path)
		
		Amplify.API.get (request:request)
		{
			result in switch result
			{
				case .success(let data):
					self.handleSuccess(data: data, success: success)
					
				case .failure(let apiError):
					self.handleFailure(apiError: apiError, failure: failure)
			}
		}
	}

This will work perfectly.

Then test using the GraphQL call with the following code :

	@objc func getAccount (id:String, success: @escaping (_ result : OCAccount)->(), failure: @escaping (_ error: NSError)->()) {
		let account = Account.keys		
		let predicate = account.id == id
		
		Amplify.API.query(request: .paginatedList(Account.self, where: predicate, limit: 1000)) { event in
			switch event {
				case .success(let result):
					switch result {
					case .success(let account):
						
						let theAccount = account[0];
						print("Successfully retrieved account : \(theAccount)")
						
						success (self.ocAccountFrom(account: theAccount));
					case .failure(let error):
						print("Got failed result with \(error.errorDescription)")
						let objcError : NSError = NSError.init(domain: kESErrorDomain, code: -1, userInfo: ["errorDescription": error.errorDescription])
						
						failure (objcError);
					}
			case .failure(let error):
				print("Got failed event with error \(error)")
				let objcError : NSError = NSError.init(domain: kESErrorDomain, code: -1, userInfo: ["errorDescription": error.errorDescription])

				print ("Underlying Error : \(error.underlyingError)")
				
				failure (objcError);
			}
		}
	}

This will result in an error. See below...



### Expected behavior

Using the same authentication procedure sure work for REST and GraphQL APIs. 

For some reason a GraphQL has it's own Auth setup when you create the API with `amplify add api` and select `GraphQL`.  Whereas REST uses what is setup when you call `amplify add auth`. Is this the issue?

### Amplify Framework Version

1.16.1

### Amplify Categories

API, Auth

### Dependency manager

Cocoapods

### Swift version

5.0

### CLI version

7.6.26

### Xcode version

13.2.1

### Relevant log output

```shell
[API] Starting query AA2649AE-33DA-4BD2-B231-9FAFC3329904
[API] {
  "variables" : {
    "limit" : 1000,
    "filter" : {
      "id" : {
        "eq" : "4bc2b6cf-815b-42fb-95f4-cb0730aced0e"
      }
    }
  },
  "query" : "query ListAccounts($filter: ModelAccountFilterInput, $limit: Int) {\n  listAccounts(filter: $filter, limit: $limit) {\n    items {\n      id\n      createdAt\n      email\n      familyName\n      givenName\n      owner\n      updatedAt\n      __typename\n    }\n    nextToken\n  }\n}"
}
[API] Starting network task for query AA2649AE-33DA-4BD2-B231-9FAFC3329904
Got failed event with error APIError: The HTTP response status code is [401].
Recovery suggestion: The metadata associated with the response is contained in the HTTPURLResponse.
For more information on HTTP status codes, take a look at
https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
Underlying Error : nil

Is this a regression?

No

Regression additional context

No response

Device

iPhone 12 Pro

iOS Version

iOS 15

Specific to simulators

No response

Additional context

For clarity, here is the version of all the PODS according to the Podfile.lock file...

PODS:

  • Amplify (1.16.1):
    • Amplify/Default (= 1.16.1)
  • Amplify/Default (1.16.1)
  • AmplifyPlugins/AWSAPIPlugin (1.16.1):
    • AppSyncRealTimeClient (~> 1.4)
    • AWSCore (~> 2.26.5)
    • AWSPluginsCore (= 1.16.1)
  • AmplifyPlugins/AWSCognitoAuthPlugin (1.16.1):
    • AWSAuthCore (~> 2.26.5)
    • AWSCognitoIdentityProvider (~> 2.26.5)
    • AWSCognitoIdentityProviderASF (~> 2.26.5)
    • AWSCore (~> 2.26.5)
    • AWSMobileClient (~> 2.26.5)
    • AWSPluginsCore (= 1.16.1)
  • AmplifyPlugins/AWSDataStorePlugin (1.16.1):
    • AWSCore (~> 2.26.5)
    • AWSPluginsCore (= 1.16.1)
    • SQLite.swift (= 0.12.2)
  • AmplifyPlugins/AWSPinpointAnalyticsPlugin (1.16.1):
    • AWSCore (~> 2.26.5)
    • AWSPinpoint (~> 2.26.5)
    • AWSPluginsCore (= 1.16.1)
  • AppSyncRealTimeClient (1.6.0):
    • Starscream (~> 3.1.1)
  • AWSAuthCore (2.26.5):
    • AWSCore (= 2.26.5)
  • AWSCognitoIdentityProvider (2.26.5):
    • AWSCognitoIdentityProviderASF (= 2.26.5)
    • AWSCore (= 2.26.5)
  • AWSCognitoIdentityProviderASF (2.26.5)
  • AWSCore (2.26.5)
  • AWSMobileClient (2.26.5):
    • AWSAuthCore (= 2.26.5)
    • AWSCognitoIdentityProvider (= 2.26.5)
    • AWSCognitoIdentityProviderASF (= 2.26.5)
    • AWSCore (= 2.26.5)
  • AWSPinpoint (2.26.5):
    • AWSCore (= 2.26.5)
  • AWSPluginsCore (1.16.1):
    • Amplify (= 1.16.1)
    • AWSCore (~> 2.26.5)
  • CocoaAsyncSocket (7.6.5)
  • DTFoundation/Core (1.7.18)
  • DTFoundation/DTASN1 (1.7.18):
    • DTFoundation/Core
  • SAMKeychain (1.5.3)
  • SQLite.swift (0.12.2):
    • SQLite.swift/standard (= 0.12.2)
  • SQLite.swift/standard (0.12.2)
  • Starscream (3.1.1)
  • TCMobileProvision (1.0.0):
    • DTFoundation/DTASN1 (~> 1.6)

DEPENDENCIES:

  • Amplify
  • AmplifyPlugins/AWSAPIPlugin
  • AmplifyPlugins/AWSCognitoAuthPlugin
  • AmplifyPlugins/AWSDataStorePlugin
  • AmplifyPlugins/AWSPinpointAnalyticsPlugin
  • CocoaAsyncSocket
  • SAMKeychain
  • TCMobileProvision

SPEC REPOS: trunk: - Amplify - AmplifyPlugins - AppSyncRealTimeClient - AWSAuthCore - AWSCognitoIdentityProvider

Etep15 avatar Apr 06 '22 05:04 Etep15

Hi @Etep15 , how did you provision the AppSync endpoint with IAM auth? Can you provide us some steps such as the selections you took after amplify add api? What's the resulting schema before amplify push (simplified/redacted if possible)? What version of the CLI are you using? We'll have to reproduce to see why the authenticated user is not able to perform that operation

lawmicha avatar Apr 07 '22 18:04 lawmicha

Thanks for the reply @lawmicha ... Hopefully this helps below...

1. AppSync provisioning with amplify CLI

I've tried both methods, Cognito User Pools and IAM. But both result in the same. I've also set it up so both are available for use... The last time I updated the API I had Cognito User Pools as the default. So I've gone through and setup IAM as the default and tried again and had the same result on my iOS app... Here is that setup with IAM being set as the default and Cognito User Pools as secondary...

amplify api update
? Select from one of the below mentioned services: GraphQL

General information
- Name: MyProject
- API endpoint: https://[REDACTED].amazonaws.com/graphql

Authorization modes
- Default: Amazon Cognito User Pool

Conflict detection (required for DataStore)
- Conflict resolution strategy: Auto Merge

? Select a setting to edit Authorization modes
? Choose the default authorization type for the API IAM
? Configure additional auth types? Yes
? Choose the additional authorization types you want to configure for the API Amazon Cognito User Pool
Cognito UserPool configuration
Use a Cognito user pool configured as a part of this project.
GraphQL schema compiled successfully.

Edit your schema at /Users/peter/Development/xCode/MyProject/amplify/backend/api/myproject/schema.graphql or place .graphql files in a directory at /Users/peter/Development/xCode/MyProject/amplify/backend/api/myproject/schema
✅ Successfully updated resource

2. Amplify Push

I then did an amplify push which showed an "Update" for my API and I answered the following questions as follows...

? Are you sure you want to continue? Yes
GraphQL schema compiled successfully.

Edit your schema at /Users/peter/Development/xCode/MyProject/amplify/backend/api/myproject/schema.graphql or place .graphql files in a directory at /Users/peter/Development/xCode/MyProject/amplify/backend/api/myproject/schema
⠧ Building resource api/myproject schema compiled successfully.

Edit your schema at /Users/peter/Development/xCode/MyProject/amplify/backend/api/myproject/schema.graphql or place .graphql files in a directory at /Users/peter/Development/xCode/MyProject/amplify/backend/api/myproject/schema
? Do you want to update code for your updated GraphQL API Yes
? Do you want to generate GraphQL statements (queries, mutations and subscription) based on your schema types?
This will overwrite your current graphql queries, mutations and subscriptions Yes
⠦ Updating resources in the cloud. This may take a few minutes...

Once the push was completed I then tested again with that setup. Same log result as originally posted.

3. GraphQL Schema

Here is the schema. I grabbed this during the push but to be honest, it didn't change at all. as I know it well.

type Account @model @auth(rules: [{allow: owner}, {allow: private, operations: [read]}]) {
  id: ID!
  email: String
  givenName: String
  familyName: String
  owner: String
#  identifyingPerson: Person @hasOne # Had to remove bidirectional relationship due to Amplify Bug.
  identifyingPerson: [Person] @hasMany
  managingPersons: [Person] @manyToMany(relationName:"accountHasPerson")
  devices: [Device] @hasMany
}

type Person @model @auth(rules: [{allow: owner}, {allow: private, operations: [read]}]) {
  id: ID!
  email: String @auth(rules: [{allow: owner}])
  givenName: String
  familyName: String
  identifyingAccount: Account @belongsTo
  managingAccounts: [Account] @manyToMany(relationName:"accountHasPerson")
  tempForCompetition: Competition @belongsTo
  tempInviteEmail: String
}

type Device @model @auth(rules: [{allow: owner}]) {
  id: ID!
  name: String
  pushToken: String
  voipToken: String
  production: Boolean!
  appVersion: Float
  appBuild: Int
  account: Account @belongsTo
}

type Competition @model @auth(rules: [{allow: owner}]) {
  id: ID!
  tempPersons: [Person] @hasMany
}

4. Amplify CLI Version

Finally, the Amplify CLI version is 7.6.26 which I noted in my original post so I'm hoping I'm grabbing the right number? (from amplify version)

Let me know if there is anything else needed. Much appreciate the help here!

Etep15 avatar Apr 07 '22 23:04 Etep15

Latest Schema (From Discord)

type Account @model @auth(rules: [{allow: owner}, {allow: private, operations: [read]}]) {
  id: ID!
  email: String
  givenName: String
  familyName: String
  owner: String
#  identifyingPerson: Person @hasOne # Had to remove bidirectional relationship due to Amplify Bug.
  identifyingPerson: [Person] @hasMany @index (name:"byIdentifyingPerson", queryField:"accountByIdentifyingPerson")
  managingPersons: [Person] @manyToMany(relationName:"accountHasPerson")
  devices: [Device] @hasMany
}

type Person @model @auth(rules: [{allow: owner}, {allow: private, operations: [read]}]) {
  id: ID!
  email: String @auth(rules: [{allow: owner}])
  givenName: String
  familyName: String
  identifyingAccount: Account @belongsTo @index (name:"byIdentifyingAccount", queryField:"personByIdentifyingAccount")
  managingAccounts: [Account] @manyToMany(relationName:"accountHasPerson")
  tempForCompetition: Competition @belongsTo
  tempInviteEmail: String
}

type Device @model @auth(rules: [{allow: owner}]) {
  id: ID!
  name: String
  pushToken: String
  voipToken: String
  production: Boolean!
  appVersion: Float
  appBuild: Int
  account: Account @belongsTo #This may be unnecessary (and a security risk) as it is already owned by "Owner"
}

type Competition @model @auth(rules: [{allow: owner}]) {
  id: ID!
  tempPersons: [Person] @hasMany
}

lawmicha avatar Apr 15 '22 13:04 lawmicha

Apologize for losing track on this issue. @Etep15 are you still facing this or did you find a work around?

royjit avatar Feb 24 '23 20:02 royjit