waza-ari / fastapi-keycloak-middleware

A middleware for FastAPI that allows easy authentication and authorisation tailored for Keycloak
MIT License
59 stars 15 forks source link

Issue with Extracting Authorization Claims from JSON Web Token #49

Closed AeonDave closed 4 months ago

AeonDave commented 5 months ago

Hello! i've run into a problem when trying to use the middleware with my Keycloak server setup where my JSON Web Token (JWT) format does not match the expected format for authorization claims extraction as described in the documentation.

According to the Middleware's documentation, we can specify the claim from which to extract the scopes as follows:

By default, the roles claim will be checked to build the scope. You can configure this behavior:

setup_keycloak_middleware( app, keycloak_configuration=keycloak_config, get_user=auth_get_user, authorization_method=AuthorizationMethod.CLAIM, authorization_claim="permissions" )

This setup expects that the scopes will be extracted from the permissions or roles claim (by default). However, in my JWT, the relevant authorization claims are located within the realm_access dictionary under roles, which looks like this:

{
  "exp": ***,
  "iat": ***,
  "jti": "***",
  "iss": "http://localhost:8080/realms/test",
  "aud": "account",
  "sub": "**",
  "typ": "Bearer",
  "azp": "test-cli",
  "session_state": "***",
  "acr": "1",
  "allowed-origins": [
    "/*"
  ],
  "realm_access": {
    "roles": [
      "default-roles-test",
      "offline_access",
      "admin",
      "uma_authorization"
    ]
  },
  "resource_access": {
    "account": {
      "roles": [
        "manage-account",
        "manage-account-links",
        "view-profile"
      ]
    }
  },
  "scope": "openid profile email",
  "sid": "***",
  "email_verified": false,
  "name": "Admin Admin",
  "preferred_username": "admin",
  "given_name": "Admin",
  "family_name": "Admin",
  "email": "admin@admin.com"
}

Unfortunately, I'm unable to modify this structuring on the Keycloak server side to fit the default expectation of the middleware. Also this is the default behavior of this keycloak setup.

Is there any solution to this problem? Maybe could i use a dot notation inside config? like authorization_claim="realm_access.roles" ?

also default aud is "account". is possible to include it in the default aud acceptance? otherwise i have to add decode_options={"verify_signature": True, "verify_aud": False, "exp": True}

Thank you for your attention and assistance regarding this matter. Best regards

waza-ari commented 5 months ago

Hi,

you're right, as of today this is not supported. It has been proposed in #34, however back then I felt that this is adding unnecessary complexity. Personally I really dislike having those nested claims, although I will admit it's more of a personal preference than anything I could back by scientific data. I'd be interested what stops you from configuring a client specific mapper (you can still use realm roles) as described in #34?

Regarding the audience I suggest reading this part of the documentation and/or this issue on Github: https://github.com/keycloak/keycloak/issues/12415

Hardcoding account is certainly not an option. Also, the decode option may change soon, as this library will have to migrate to python-keycloak >= 4.0 (see #30), which will change the underlying library that is used to decode the JWT. As a consequence, the behaviour (and options) exposed by this library are bound to change as well

AeonDave commented 5 months ago

Thank you for your response. I hate this nested claims, but is the default config and i need something that works "out-of-the-box" without any client put hands on keycloak installation... As per your suggestion, I have indeed configured a scope mapper to handle the nested claims. I set the authorization_claim in the configuration to "realm_access" and defined a custom scope mapper function as follows even if linter bugs me with type mismatch:

async def scope_mapper(claim_auth: dict) -> list[str]:
    permissions = []
    for role in claim_auth['roles']:
        try:
            permissions.append(role)
        except KeyError:
            logger.warning(f"Unknown role {role}")
    return permissions

Hardcoding "account" of course is bad, but is there any way i can "config" custom audience to be verified? so i can avoid to put it to false.

Best regards and thank you for your work

waza-ari commented 5 months ago

Hi @AeonDave, I understand that this is the Keycloak default config, but it's rather easy to change the client config and use a different mapper on the Keycloak side. Anyway your solution works, although I admit the linter warnings will probably be a bit annoying. However I don't see this middleware supporting nested claims any time soon, so this might be something you need to live with.

Regarding the aud claim - could you check the new version v1.0.0 (currently only available on Test PyPI. It includes the new version of python-keycloak which in turn switched from python-jose to JWCrypto for token validation. It uses different methods to validate the token, but also exposes a few more options to the user. Maybe that helps with your aud validation?

waza-ari commented 5 months ago

The new version is available within the next couple of minutes on the regular Python package index. Feel free to test this version regarding the aud claim (the rest should not have changed).

waza-ari commented 4 months ago

No feedback, closing the issue.