Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CognitoCurrentUser() produces "Validation Error for Claims" #27

Open
mjvdvlugt opened this issue Feb 23, 2021 · 5 comments
Open

CognitoCurrentUser() produces "Validation Error for Claims" #27

mjvdvlugt opened this issue Feb 23, 2021 · 5 comments
Labels
enhancement New feature or request help wanted Extra attention is needed

Comments

@mjvdvlugt
Copy link

When using CognitoCurrentUser(region=settings.aws_region, userPoolId=settings.aws_cognito_user_pool_id) in my endpoint depencies, it consistently errors a 403: "Validation Error for Claims". Further investigation indicated this has to do with an issue mapping the Cognito reply to the CognitoClaims Pydantic model here:

current_user = self.user_info.parse_obj(claims)

The ValidationError is:

ValidationError(model='CognitoClaims', errors=[{'loc': ('cognito:username',), 'msg': 'field required', 'type': 'value_error.missing'}])

and indeed, the 'cognito:username' field is not available in the claims:

{'sub': '5a8b7ac6-5cd3-4279-b89b-5f59ca6c4144', 'cognito:groups': ['Users'], 'iss': '[Redacted]', 'version': 2, 'client_id': '[Redacted]', 'event_id': 'b0fcb01a-971b-40fe-942c-550dbf0915d2', 'token_use': 'access', 'scope': 'openid email', 'auth_time': 1614092323, 'exp': 1614095923, 'iat': 1614092323, 'jti': '48884493-8fad-47e5-8d8b-a4f44aa41900', 'username': '5a8b7ac6-5cd3-4279-b89b-5f59ca6c4144'}

but 'username' is!

I wasn't able to find why Cognito returns the claims in this different way.

Proposed compatibility fix

class CognitoClaims(BaseModel):
    username: str = Field(alias="cognito:username")
    email: str = Field(None, alias="email")

    class Config:
        allow_population_by_field_name = True

Adding the allow_population_by_field_name = True in the CognitoClaims model config makes it compatible with this other Cognito output.

@mjvdvlugt
Copy link
Author

An alternative solution could also parse the "sub" uuid and scopes('cognito:groups') from the Access Token Payload, and make username/email optional. I found some claims contents documentation: https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-with-identity-providers.html#amazon-cognito-user-pools-using-the-access-token

@mjvdvlugt
Copy link
Author

By the way: I've managed to work around this using a custom CognitoClaim object. 💪

@mjvdvlugt
Copy link
Author

mjvdvlugt commented Feb 24, 2021

I thought of another possibility that might help:

class CognitoClaims(BaseModel):
    username: str = Field(alias="cognito:username")
    email: str = Field(None, alias="email")

    class Config:
        extra = Extra.allow

The extra = Extra.allow will make Pydantic accept all fields from the auth provider. With this also any added custom fields are automatically accessible through the user_info object.

A combination of this with allow_population_by_field_name = True is also possible of course.

@tokusumi
Copy link
Owner

@mjvdvlugt Always thank you for your feedback and proposals. Very helpful !!!

I would like to investigate a little further whether to make it the default configuration.

@tokusumi tokusumi added enhancement New feature or request help wanted Extra attention is needed labels Jun 13, 2021
@DarioPanada
Copy link

I thought of another possibility that might help:

class CognitoClaims(BaseModel):
    username: str = Field(alias="cognito:username")
    email: str = Field(None, alias="email")

    class Config:
        extra = Extra.allow

The extra = Extra.allow will make Pydantic accept all fields from the auth provider. With this also any added custom fields are automatically accessible through the user_info object.

A combination of this with allow_population_by_field_name = True is also possible of course.

A small note - if you want to avoid modifying the package code you'll also need to create a custom CognitoCurrentUser class, something like:

class CognitoClaimsCustom(BaseModel):
    username: str = Field(alias="cognito:username")
    email: str = Field(None, alias="email")
    sub: str = Field(None, alias="cognito:sub")

    class Config:
        extra = Extra.allow


class CognitoCurrentUserCustom(CognitoCurrentUser):

    user_info = CognitoClaimsCustom

    def __init__(
            self, *args: Any, **kwargs: Any,
    ):
        super().__init__(*args, **kwargs)

Otherwise the CognitoCurrentUser instance will by default use the original version of CognitoClaims which only provide the username and string. With this approach instead you get all claims.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

3 participants