aws-amplify / amplify-swift

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

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

Open dnys1 opened 3 years ago

dnys1 commented 3 years ago

Describe the bug

Cognito Identity Tokens cannot be used with owner auth due to logic in AWSPluginsCore 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.

ID Token Payload

{
    "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

{
  "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"
}

Schema

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

Steps To Reproduce

1. Use schema above
2. Follow steps here to use Cognito as OIDC provider: https://docs.amplify.aws/lib/graphqlapi/authz/q/platform/ios/#oidc
3. Add DataStore plugin
4. Observe failing subscriptions

Expected behavior

API plugin is able to pull "cognito:username" field from ID token when OIDC is being used as the API auth provider.

Amplify Framework Version

1.15.0 (likely applies to 1.13.0+, though)

Amplify Categories

API

Dependency manager

Cocoapods

Swift version

5.5

CLI version

6.3.0

Xcode version

13.0

Relevant log output

2021-10-13 09:20:59.756887-0700 DSSyncFix[42446:260476] [StarscreamAdapter] socket.write - {"id":"2720817C-B99F-4A04-8E0E-2F74B56EA3BB","payload":{"data":"{\"query\":\"subscription OnCreateTodo {\\n  onCreateTodo {\\n    id\\n    createdAt\\n    description\\n    name\\n    updatedAt\\n    __typename\\n    _version\\n    _deleted\\n    _lastChangedAt\\n    owner\\n  }\\n}\"}","extensions":{"authorization":{"Authorization":"eyJraWQiOiJQUzdjM2lNcWJudjV1bFlqdVNwdlE5dU12alRQZStPcld3V0pvZkN6a1p3PSIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiI5MWVlZmJjYi0xMWU2LTRlNzAtOWE4OS04YjhmN2NlNjgzMTciLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiaXNzIjoiaHR0cHM6XC9cL2NvZ25pdG8taWRwLnVzLXdlc3QtMi5hbWF6b25hd3MuY29tXC91cy13ZXN0LTJfR0xsUm5qWXhVIiwiY29nbml0bzp1c2VybmFtZSI6ImRpbGxvbiIsIm9yaWdpbl9qdGkiOiI3YWIxMzY5OC0xMzgyLTQxMjQtYTNlOC03MjBiMWQ1Njk1ZGIiLCJhdWQiOiIxZHBybWZzM3R1b25wbjJ0ZWRjcGQzaGNnNyIsImV2ZW50X2lkIjoiZDFjMjMwMTktZTZlYS00ODBlLTgwNWQtYWVkYTNkZWEwN2Y4IiwidG9rZW5fdXNlIjoiaWQiLCJhdXRoX3RpbWUiOjE2MzQxNDE4MDUsImV4cCI6MTYzNDE0NTQwNSwiaWF0IjoxNjM0MTQxODA1LCJqdGkiOiJjYjFjOGI5Mi1lZWI2LTQzZDAtODdhMS0wNmFlYWE5MTczZjUiLCJlbWFpbCI6Im55ZGlsbG9uQGFtYXpvbi5jb20ifQ.b-wUBdPLfyhUo06TqapnYfFkYL5UWC4P4otZ_co4BHHajG5TzsV07VWJYCum-7_pRWk3Mnu_iMTbRA2r1AcMgFaCvLx723d_0TmEdo4lILUM4y7AnL3wcSSO1tI5kKtQRnneGEkWMZ-BdXT-SlQg3DOSgF079HP5A6dpBj7waAeamUbXjblSK5ltMa8vApnDFPsO2gS_CJHg5JnkFKRIbxaz9rK-P4oCN_Qt_qjPsxrwwAe-pONLx8kE2iH8L8ZhyEUnC2Dfqug5IGII_zbC1Lmgt7lkRkEPEeF5LbRHBKAiV5si0POTpx7vTK3nHhhK_w_XMpTUGTXvKuYOKJIdZA","host":"zm3nltvjbjhotbjwze7ibdvd7a.appsync-api.us-west-2.amazonaws.com"}}},"type":"start"}
2021-10-13 09:20:59.852139-0700 DSSyncFix[42446:260480] [StarscreamAdapter] websocketDidReceiveMessage: - {"id":"2720817C-B99F-4A04-8E0E-2F74B56EA3BB","type":"error","payload":{"errors":[{"message":"Validation error of type MissingFieldArgument: Missing field argument owner @ 'onCreateTodo'"}]}}

Is this a regression? (i.e. was this working before a version upgrade)

No response

Device

iPhone 13 Pro Max - Simulator

iOS Version

iOS 15

Specific to simulators

No response

Additional context

No response

lawmicha commented 3 years ago

From our discussion, one way to fix this is to check the provider from the authrule, if provider is oidc, do not take off the "cognito:" portion of the identity claim key. With this, we can update our integration test for OIDC: update the README.md to provision OIDC with Cognito, enable/refactor the disabled OIDC integ tests

undefobj commented 2 years ago

@lawmicha Not sure I understand this issue on a couple fronts. First, why are we using Cognito with OIDC settings? The standard Cognito User Pools auth does this behind the scenes anyway. Secondly, what is the purpose of the code which @dnys1 is referencing? We should be sending JWT tokens through to AppSync in the Authorization header unmodified so that AuthZ at the API level can take place followed by AuthZ at the Resolver level by looking at scopes can take place in the GraphQL request. Let's chat internally on this so we can come to a consistency resolution around implementation.

lawmicha commented 2 years ago

Hi @undefobj, I recall this was testing different use cases that developers could be using, and one of them being using Cognito as the OIDC provider, i'll have to defer to @dnys1 to follow up on this

dnys1 commented 2 years ago

Hi guys - yeah, sorry for the lack of context to this ticket. I had to rack my brain to remember why this was needed, but it came up again today with another user who wanted to use a custom Cognito claim for the identity claim. User Pool authorization will send the Cognito access token, which does not include these claims. By switching to OIDC, you can send the ID token, but because of the logic noted above, the Cognito ID token cannot be used even though it fits all the other criteria of an OIDC provider.

lawmicha commented 2 years ago

@undefobj the reason why the library has this code is for DataStore when establishing subscriptions, it will do two things- add the token to the request header and extract the identityClaim value from the token and add it as the owner input to the subscription operation.

@dnys1 I think you might have mentioned it back then when we originally discussed this, i thinnk the fix should be something like this:

// Current code - assumes when identityClaim from codegen is "cognito:username", then retrieves AccessToken.username
public func identityClaimOrDefault() -> String {
        guard let identityClaim = self.identityClaim else {
            return "username"
        }
        if identityClaim == "cognito:username" {
            return "username"
        }
        return identityClaim
    }

// Proposed code for the fix to work with OIDC providers
public func identityClaimOrDefault() -> String {
        guard let identityClaim = self.identityClaim else {
            return "username"
        }
        if identityClaim == "cognito:username" && provider == .userPools {
            return "username"
        }
        return identityClaim
    }

This way, when developer is using Cognito as the provider type, it will continue to return "username" to retrieve the value from the access token. When the provider is .odic then it will take the identityClaim as is which can be "cognito:username" or a custom field defined by the developer, from the ID token.

diogoepsy commented 8 months ago

Any news on this one? :)

thisisabhash commented 7 months ago

@diogoepsy Thank you - our team will investigate and post updates here. Could you share more details on the OIDC setup you're using. Also, if your schema and reproduction steps are different from the one mentioned above, it'd helpful to share it too.