-
Notifications
You must be signed in to change notification settings - Fork 15
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
v2: Complete app refactor #39
Conversation
The general direction looks good. I have a few points, but they should all be addressable. Good points:
Some behaviors that I would change or at least make configurable:
My main concern is the behavior of the As a reference I've added the JWTs of an admin and normal user: Admin token{
"exp": 1666537275,
"iat": 1666536975,
"auth_time": 1666536314,
"jti": "60c114dd-f0da-4639-b345-ac17f3adac1d",
"iss": "http://localhost:8080/auth/realms/my-realm",
"aud": [
"core-service",
"account"
],
"sub": "3a768e53-9a74-4504-b6e8-1570809f60b9",
"typ": "Bearer",
"azp": "web-app",
"nonce": "58e0218d-75c0-43d2-804a-6340a3aa7227",
"session_state": "19cd8ecd-f183-4738-861e-d559665f604c",
"acr": "0",
"allowed-origins": [
"http://localhost:4200"
],
"realm_access": {
"roles": [
"offline_access",
"admin",
"uma_authorization",
"default-roles-my-realm"
]
},
"resource_access": {
"core-service": {
"roles": [
"admin"
]
},
"account": {
"roles": [
"manage-account",
"manage-account-links",
"view-profile"
]
}
},
"scope": "openid profile email",
"sid": "19cd8ecd-f183-4738-861e-d559665f604c",
"email_verified": true,
"name": "My Realm Admin",
"preferred_username": "my-realm-admin",
"given_name": "My Realm",
"family_name": "Admin",
"email": "admin@example.com"
} Normal user token{
"exp": 1666537366,
"iat": 1666537066,
"auth_time": 1666537065,
"jti": "bb3864a5-9985-456b-9709-12a6c6faf6e8",
"iss": "http://localhost:8080/auth/realms/my-realm",
"aud": "account",
"sub": "be148f39-ae1b-4f80-8ee1-5d6a9fdc749c",
"typ": "Bearer",
"azp": "web-app",
"nonce": "3b4f1bc8-cbc1-4293-8149-86873b5c58a5",
"session_state": "7a1efb7d-849f-4351-be22-2038050dad76",
"acr": "1",
"allowed-origins": [
"http://localhost:4200"
],
"realm_access": {
"roles": [
"offline_access",
"uma_authorization",
"default-roles-my-realm"
]
},
"resource_access": {
"account": {
"roles": [
"manage-account",
"manage-account-links",
"view-profile"
]
}
},
"scope": "openid profile email",
"sid": "7a1efb7d-849f-4351-be22-2038050dad76",
"email_verified": true,
"name": "Normal User",
"preferred_username": "my-realm-user",
"given_name": "Normal",
"family_name": "User",
"email": "user@example.com"
} |
Thanks for feedback @moritz89 . Deprecated codeWhy this block is no longer used? Is it safe to delete it? def pass_auth(self, request):
"""
Check if the current URI path needs to skip authorization
"""
logging.warning("Not used by middleware", DeprecationWarning, 2)
path = request.path_info.lstrip("/")
return any(re.match(m, path) for m in self.keycloak.exempt_uris) If we push this as a major new release "2.0", what do you think of removing deprecated stuff, such as the above code and graphene middleware? Async middlewareCheck commit b56ee03 and f806de7 regarding the async middleware. Role/scope checks only using introspectionI didn't notice the decode token method, since it was not documented. Please check the commit 00946e3. I believe Authentication without having to call Keycloak server (introspection depending on whether data is available in token)Not sure how to implement this. Are you referring to
|
@diogosilva30 Regarding the active token check. I'd recap the OAuth flow to make sure we're on the same page.
The refresh token has a long lifetime (several weeks) while the access token only has a short lifetime (~3 to 120 minutes). Therefore, as the expiration time is part of the token, it is signed and short lived, it should not be necessary to check with Keycloak whether the access token is still active. By decoding the token (cryptographically signed) and checking the system time, it can be checked if the access token is valid. The only use cases for querying Keycloak whether the access token is active is if it has been configured with a long lifetime (not recommended) or if a short (< 30 min) time to block access is required. In the second case, setting the access token life time to a few minutes is the easiest option. Otherwise it would require handling backchannel logout callbacks from Keycloak (Backchannel Logout URL option) to populate a local dict to invalidate sessions which is checked when decoding JWTs. |
@moritz89 I have implemented your suggestions. Please see 2bef36d. I was not aware of I have made a few additional changes that I would like to get feedback on (@moritz89 @uw-rvitorino ). Basic auth supportIn the Why this change?Use case: When working with If you find that this behaviour should be optional, we could create a setting to enable/disable it. |
Just a note regarding the token timeout. In the previous implementation I only used a token timeout check to verify if the Django client's access token should be renewed to avoid access errors when performing admin calls to Keycloak. This was more of a hack as the proper method would have been to check the error codes, as done by python-keycloak with Therefore, in this aspect the token timeout functionality is not needed any more. In your MR you've added it for a different purpose, to check if the access token has expired. To check if a user's access token is valid (not expired, not tampered with) calling Therefore, we can remove the explicit has expired and is active check from the MR since it is handled in |
In a different point, the audience check, this should not be disabled by default in
For advanced use cases (using roles and client scopes), the audience resolve mapper type can be used to add the audience to the token. An idea would be to add it as an option in settings if this should be verified. |
Regarding basic auth support. I have nothing against it. We're all adults here and if someone were to use it in prod (not recommended) that is their own prerogative 😄 It should also not interfere with the session / default auth middleware as it uses the cookie header. |
@moritz89 Can you work on the mentioned changes? I'm kinda busy. |
Why: - Simplify how settings class is constructed - Use decoding token for active check This change addresses the need by: - Adding comments to settins - Simplifying creating settings dataclass from Django settings - Adding caching to decoding/introspecting token - Simplifying is_active token check
19a94d3
to
1da3ee8
Compare
The 403 comes from my permission checking. Probably sees that the anonymous user has no permissions for the GraphQL query. Anyhow, Django itself will never raise 401 errors, only 403s. Reason is this: https://code.djangoproject.com/ticket/4354 |
The reason was to debug why old versions of Keycloak were failing when the realm configuration file was added. We can remove that now. |
Rest framework returns 401 if you set |
Just to make sure we're on the same page, if the middleware can't authenticate the user, it does not do anything to the request (only does a debug log on errors). The 4XX error is solely dependent on the Django view handling the request, so a 401 will be sent if you're using DRF and a 403 if you raise a PermissionError using Django's permission framework. So both responses are valid. |
Wasn't implying your response was invalid. Sorry for misunderstand. I was just wondering if 401 was returned when using restframework, as their docs state. I have no problem with Django's 403. EDIT: |
@diogosilva30 Jupp, lazy loading is possible, but one side-effect of the current implementation is that it'll create quite a bit of overhead since a new admin connector has to be created with each KeycloakUser object instantiation. AFAIK, the KeycloakAdmin setup is somewhat expensive with the get_token() setup. Would an alternative be a lazy loaded global object? A bit like here: https://stackoverflow.com/a/52359211/6783666 |
For now I would ship v2 as it is. We know it is working. But we should explore your option for optimization in further patches. |
Wasn't straight forward, but I added an implementation with lazy loading. Test with |
Hi. I was exploring the repository, and I very much agree with @moritz89 regarding #12 . If there is a library python-keycloak that is better maintained we should delegate the keycloak API interactions to it.
This move heavily simplifies our code, leads to fewer possible debugs such as #38 ( which I believe it has been fixed in this version), and mainly allows us to focus our short time in integrating the package with django. From what I have seen, python-keycloak seems far more stable in regards to API interaction.
Our team was facing a
jwks
error, this should be fixed aswell.I have divided the code into 2 core and separate concepts:
Token
--> this class is the core object of this app. If serves as a container foraccess
andrefresh
tokens. Additionally, it contains several properties to retrieve information about a specific token, as well as validation mechanisms.This class is what should be used to transfer data between the different parts of the app.
KeycloakAdminConnector
--> this object replaces the oldConnector
, and is used to interface with Keycloak in ADMIN operations: "list users", "get user by id", etc...I also found a lot of code that was not being used/called anywhere in the codebase, so I deleted it. A new
config.py
module with asettings
attribute was also created to provide better parsing and single-source-of-truth settings.Looking forward for a detailed review and opinions.