aws-amplify / aws-amplify/amplify-android

API Auth (OIDC): Cognito cannot be used as issuer

Open
#1,535 1 comment 0 reactions 0 assignees View on GitHub
api bug GraphQL API
Dominant language
Java
Stars
287
Forks
132
Avg merge
2d 2h
Merged PRs (30d)
45

Description

### Before opening, please confirm:

- [X] I have [searched for duplicate or closed issues](https://github.com/aws-amplify/amplify-android/issues?q=is%3Aissue+) and [discussions](https://github.com/aws-amplify/amplify-android/discussions).

### Language and Async Model

Not applicable

### Amplify Categories

GraphQL API, DataStore

### Gradle script dependencies

```groovy
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.amplifyframework:core:1.28.0'
implementation "com.amplifyframework:aws-api:1.28.0"
implementation "com.amplifyframework:aws-api-appsync:1.28.0"
```

### Environment information

```
# Put output below this line

------------------------------------------------------------
Gradle 6.7
------------------------------------------------------------

Build time: 2020-10-14 16:13:12 UTC
Revision: 312ba9e0f4f8a02d01854d1ed743b79ed996dfd3

Kotlin: 1.3.72
Groovy: 2.5.12
Ant: Apache Ant(TM) version 1.10.8 compiled on May 10 2020
JVM: 11.0.12 (Amazon.com Inc. 11.0.12+7-LTS)
OS: Mac OS X 11.6 x86_64
```

### Please include any relevant guides or documentation you're referencing

https://docs.amplify.aws/lib/graphqlapi/authz/q/platform/android/#oidc

### Describe the bug

Cognito Identity Tokens cannot be used with owner auth due to logic in [core](https://github.com/aws-amplify/amplify-android/blob/main/core/src/main/java/com/amplifyframework/core/model/AuthRule.java#L136) switching the "cognito:username" identity claim for "username".

Cognito ID tokens and Access Tokens have different structures. This logic in core seems to be accommodating changes to the latter; however, as a result, it seems to have also broken the former.

From Cognito [docs](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-with-identity-providers.html):

**ID Token Payload**

```json
{
"sub": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
"aud": "xxxxxxxxxxxxexample",
"email_verified": true,
"token_use": "id",
"auth_time": 1500009400,
"iss": "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_example",
"cognito:username": "janedoe",
"exp": 1500013000,
"given_name": "Jane",
"iat": 1500009400,
"email": "janedoe@example.com",
"jti": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
"origin_jti": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
}
```

**Access Token Payload**

```json
{
"sub": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
"device_key": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
"cognito:groups": [
"admin"
],
"token_use": "access",
"scope": "aws.cognito.signin.user.admin",
"auth_time": 1562190524,
"iss": "https://cognito-idp.us-west-2.amazonaws.com/us-west-2_example",
"exp": 1562194124,
"iat": 1562190524,
"origin_jti": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
"jti": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
"client_id": "57cbishk4j24pabc1234567890",
"username": "janedoe@example.com"
}
```

### Reproduction steps (if applicable)

1. Use schema below
2. Set primary auth as OIDC

```sh
$ amplify update api
? Please select from one of the below mentioned services: GraphQL
? Select from the options below Update auth settings
? Choose the default authorization type for the API OpenID Connect
? Enter a name for the OpenID Connect provider: Cognito
? Enter the OpenID Connect provider domain (Issuer URL): https://cognito-idp..amazonaws.com//
? Enter the Client Id from your OpenID Client Connect application (optional):
? Enter the number of milliseconds a token is valid after being issued to a user: 3600000
? Enter the number of milliseconds a token is valid after being authenticated: 3600000
```

3. Return Cognito ID token using OIDC auth provider in API category

### Code Snippet

```java
// Put your code below this line.

```

### Log output

```
E/amplify:aws-datastore: Failure encountered while attempting to start API sync.
DataStoreException{message=Error during subscription., cause=ApiAuthException{message=Attempted to subscribe to a model with owner-based authorization without username which was specified (or defaulted to) as the identity claim., cause=null, recoverySuggestion=If you did not specify a custom identityClaim in your schema, make sure you are logged in. If you did, check that the value you specified in your schema is present in the access key.}, recoverySuggestion=Evaluate details.}
at com.amplifyframework.datastore.appsync.AppSyncClient.lambda$subscription$3(AppSyncClient.java:328)
at com.amplifyframework.datastore.appsync.-$$Lambda$AppSyncClient$797ziDK0io-qXODzROLOA77stS8.accept(Unknown Source:4)
at com.amplifyframework.api.aws.AWSApiPlugin.subscribe(AWSApiPlugin.java:317)
at com.amplifyframework.api.aws.AWSApiPlugin.subscribe(AWSApiPlugin.java:288)
at com.amplifyframework.api.ApiCategory.subscribe(ApiCategory.java:91)
at com.amplifyframework.datastore.appsync.AppSyncClient.subscription(AppSyncClient.java:332)
at com.amplifyframework.datastore.appsync.AppSyncClient.onUpdate(AppSyncClient.java:272)
at com.amplifyframework.datastore.syncengine.-$$Lambda$r7L8lscweM53-6nW0zECJRGgjT0.subscribe(Unknown Source:7)
at com.amplifyframework.datastore.syncengine.SubscriptionProcessor.lambda$subscriptionObservable$6$SubscriptionProcessor(SubscriptionProcessor.java:187)
at com.amplifyframework.datastore.syncengine.-$$Lambda$SubscriptionProcessor$w6tohapLGUGmW4mOmsvNOno7GVE.subscribe(Unknown Source:11)
at io.reactivex.rxjava3.internal.operators.observable.ObservableCreate.subscribeActual(ObservableCreate.java:40)
at io.reactivex.rxjava3.core.Observable.subscribe(Observable.java:13099)
at io.reactivex.rxjava3.internal.operators.observable.ObservableDoOnEach.subscribeActual(ObservableDoOnEach.java:42)
at io.reactivex.rxjava3.core.Observable.subscribe(Observable.java:13099)
at io.reactivex.rxjava3.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96)
at io.reactivex.rxjava3.core.Scheduler$DisposeTask.run(Scheduler.java:614)
at io.reactivex.rxjava3.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:65)
at io.reactivex.rxjava3.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:56)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:923)
Caused by: ApiAuthException{message=Attempted to subscribe to a model with owner-based authorization without username which was specified (or defaulted to) as the identity claim., cause=null, recoverySuggestion=If you did not specify a custom identityClaim in your schema, make sure you are logged in. If you did, check that the value you specified in your schema is present in the access key.}
at com.amplifyframework.api.aws.auth.AuthRuleRequestDecorator.getIdentityValue(AuthRuleRequestDecorator.java:157)
at com.amplifyframework.api.aws.auth.AuthRuleRequestDecorator.decorate(AuthRuleRequestDecorator.java:123)
at com.amplifyframework.api.aws.AWSApiPlugin.buildSubscriptionOperation(AWSApiPlugin.java:628)
at com.amplifyframework.api.aws.AWSApiPlugin.subscribe(AWSApiPlugin.java:308)
at com.amplifyframework.api.aws.AWSApiPlugin.subscribe(AWSApiPlugin.java:288) 
at com.amplifyframework.api.ApiCategory.subscribe(ApiCategory.java:91) 
at com.amplifyframework.datastore.appsync.AppSyncClient.subscription(AppSyncClient.java:332) 
at com.amplifyframework.datastore.appsync.AppSyncClient.onUpdate(AppSyncClient.java:272) 
at com.amplifyframework.datastore.syncengine.-$$Lambda$r7L8lscweM53-6nW0zECJRGgjT0.subscribe(Unknown Source:7) 
at com.amplifyframework.datastore.syncengine.SubscriptionProcessor.lambda$subscriptionObservable$6$SubscriptionProcessor(SubscriptionProcessor.java:187) 
at com.amplifyframework.datastore.syncengine.-$$Lambda$SubscriptionProcessor$w6tohapLGUGmW4mOmsvNOno7GVE.subscribe(Unknown Source:11) 
at io.reactivex.rxjava3.internal.operators.observable.ObservableCreate.subscribeActual(ObservableCreate.java:40) 
at io.reactivex.rxjava3.core.Observable.subscribe(Observable.java:13099) 
at io.reactivex.rxjava3.internal.operators.observable.ObservableDoOnEach.subscribeActual(ObservableDoOnEach.java:42) 
at io.reactivex.rxjava3.core.Observable.subscribe(Observable.java:13099) 
at io.reactivex.rxjava3.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96) 
at io.reactivex.rxjava3.core.Scheduler$DisposeTask.run(Scheduler.java:614) 
at io.reactivex.rxjava3.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:65) 
at io.reactivex.rxjava3.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:56) 
at java.util.concurrent.FutureTask.run(FutureTask.java:266) 
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301) 
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) 
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) 
at java.lang.Thread.run(Thread.java:923) 
W/amplify:aws-datastore: API sync failed - transitioning to LOCAL_ONLY.
```

### amplifyconfiguration.json

```dart
const amplifyconfig = ''' {
"UserAgent": "aws-amplify-cli/2.0",
"Version": "1.0",
"api": {
"plugins": {
"awsAPIPlugin": {
"dsregression": {
"endpointType": "GraphQL",
"endpoint": "https://zm3nltvjbjhotbjwze7ibdvd7a.appsync-api.us-west-2.amazonaws.com/graphql",
"region": "us-west-2",
"authorizationType": "OPENID_CONNECT"
}
}
}
},
"auth": {
"plugins": {
"awsCognitoAuthPlugin": {
"UserAgent": "aws-amplify-cli/0.1.0",
"Version": "0.1.0",
"IdentityManager": {
"Default": {}
},
"AppSync": {
"Default": {
"ApiUrl": "https://zm3nltvjbjhotbjwze7ibdvd7a.appsync-api.us-west-2.amazonaws.com/graphql",
"Region": "us-west-2",
"AuthMode": "OPENID_CONNECT",
"ClientDatabasePrefix": "dsregression_OPENID_CONNECT"
}
},
"CredentialsProvider": {
"CognitoIdentity": {
"Default": {
"PoolId": "us-west-2:83f3a331-1419-40e2-a673-86dccd15f34f",
"Region": "us-west-2"
}
}
},
"CognitoUserPool": {
"Default": {
"PoolId": "us-west-2_GLlRnjYxU",
"AppClientId": "1dprmfs3tuonpn2tedcpd3hcg7",
"Region": "us-west-2"
}
},
"Auth": {
"Default": {
"authenticationFlowType": "USER_SRP_AUTH",
"loginMechanisms": [
"PREFERRED_USERNAME"
],
"signupAttributes": [
"EMAIL"
],
"passwordProtectionSettings": {
"passwordPolicyMinLength": 8,
"passwordPolicyCharacters": []
},
"mfaConfiguration": "OFF",
"mfaTypes": [
"SMS"
],
"verificationMechanisms": [
"EMAIL"
]
}
}
}
}
}
}''';
```

### GraphQL Schema

```graphql
type Todo
@model
@auth(rules: [
{ allow: owner, provider: oidc, identityClaim: "cognito:username" }
]) {
id: ID!
name: String!
description: String
}
```

### Additional information and screenshots

_No response_

Contributor guide

Open the contributing guide

Research direction

Start with core/src/main/java/com/amplifyframework/core/model/AuthRule.java around line 136, then follow AuthRuleRequestDecorator.getIdentityValue and decorate at the stack-trace lines. Reproduce the OIDC Cognito ID-token flow with owner authorization and verify that subscriptions no longer fail because the identity claim is missing.

Written by the indexing model from the issue text.

Assessment

Tech stack
android, graphql, java
Domain
api, authentication
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.