aws-amplify / aws-amplify/amplify-android
API Auth (OIDC): Cognito cannot be used as issuer
- 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
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