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

Add support for decoding JWT tokens #3

Merged
merged 7 commits into from
Feb 23, 2022
Merged

Add support for decoding JWT tokens #3

merged 7 commits into from
Feb 23, 2022

Conversation

moritz89
Copy link
Collaborator

@moritz89 moritz89 commented Feb 3, 2022

Why:

  • Extract microprofile from JWT instead of querying Keycloak
  • Retain KeycloakUser model
  • Enable Keycloak integration in tests
  • Add compatibility with async web servers
  • Add support for generating users with passwords
  • Fix misc. bugs

This change addresses the need by:

  • Add urls to delete users and fetch keys to decode JWTs
  • Keep KeycloakUser as first class model
  • Add TestMixin to help with cleanup of integration tests
  • Mark middleware as sync-only compatible to trigger wrapper
  • Fix GraphQL url check
  • Fix user not being added to requests
  • Add password parameter for creating users

Why:

- Extract microprofile from JWT instead of querying Keycloak
- Retain KeycloakUser model
- Enable Keycloak integration in tests
- Add compatibility with async web servers
- Add support for generating users with passwords
- Fix misc. bugs

This change addresses the need by:

- Add urls to delete users and fetch keys to decode JWTs
- Keep KeycloakUser as first class model
- Add TestMixin to help with cleanup of integration tests
- Mark middleware as sync-only compatible to trigger wrapper
- Fix GraphQL url check
- Fix user not being added to requests
- Add password parameter for creating users
@moritz89
Copy link
Collaborator Author

moritz89 commented Feb 3, 2022

I've noticed that you've focused on the KeycloakUserAutoID while I'm happy using the original KeycloakUser. Since my tests don't cover the the AutoID version, I hope my changes don't break that functionality, but I've tried to be conscientious of it.

I've run the changes through my test suite. However, since you use a different set of functionality, feedback from your internal test suite would be great.

Why:

- Use a cleaner method to check if a cache has been saved
- Better linting and code completion support

This change addresses the need by:

- Add class property in constructor as None
  - Instead of using hasattr
@moritz89
Copy link
Collaborator Author

moritz89 commented Feb 3, 2022

I've also changed the introspect call to a get user info call in the manager class. This may also be reverted, if you have specific requirements.

Introspection result
-----------
{"exp":1643806876,"iat":1643806576,"auth_time":1643805473,"jti":"d14ea562-e114-4b93-b44c-1f1b5db37ed8","iss":"http://localhost:8080/auth/realms/example","aud":["api-service","account"],"sub":"3a768e53-9a74-4504-b6e8-1570809f60b9","typ":"Bearer","azp":"web-app","session_state":"aef7605e-5a2c-42a2-9104-85aba1b24d5a","name":"Ad min","given_name":"Ad","family_name":"min","preferred_username":"admin","email":"admin@example.com","email_verified":true,"acr":"1","allowed-origins":["http://localhost:4200"],"realm_access":{"roles":["offline_access","admin","uma_authorization","default-roles-example"]},"resource_access":{"api-service":{"roles":["admin"]},"account":{"roles":["manage-account","manage-account-links","view-profile"]}},"scope":"offline_access profile email","sid":"aef7605e-5a2c-42a2-9104-85aba1b24d5a","client_id":"web-app","username":"admin","active":true}

User info result
-------------
{"sub":"3a768e53-9a74-4504-b6e8-1570809f60b9","email_verified":true,"name":"Ad min","preferred_username":"admin","given_name":"Ad","family_name":"min","email":"admin@example.com"}

Why:

- Use common request config pattern

This change addresses the need by:

- Refactoring out generation of headers and server url
Why:

- Uses the following libraries
  - rest_framework
  - dry-rest-permissions
  - jwt
  - requests

This change addresses the need by:

- Adding requirements in the setup.cfg file
@moritz89
Copy link
Collaborator Author

moritz89 commented Feb 3, 2022

I've also updated the library's library dependencies. I've not done this before, so please correct me if I'm wrong there

@moritz89
Copy link
Collaborator Author

moritz89 commented Feb 8, 2022

Have you had time to form an impression about the changeset? Do you think it has potential to be merged?

server_url = self.server_url
headers = {}
if self.internal_url:
server_url = self.internal_url
headers["HOST"] = urlparse(self.server_url).netloc
Copy link
Contributor

@ftcardoso ftcardoso Feb 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When using self.internal_url the HOST header must be urlparse(self.internal_url).netloc and not urlparse(self.server_url).netloc. This is a bug that was already here, but noticed it when testing with the DECODE_TOKEN=True, SERVER_URL="http://localhost:8080" and INTERNAL_URL=http://keycloak:8080. The subsequent requests done by public_key = self.jwks_client.get_signing_key_from_jwt(token).key used http://localhost:8080 instead of http://keycloak:8080 and since everything was inside docker, the request could not found http://localhost:8080.

Maybe we can simplify this and have only one URL that can be publicly accessible or an internal docker network reference (future improvement, I think).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. I've also just looked through the code and when internal URL is used, it overrides all uses of server_url, thereby supporting your idea to simplify the configuration to only use one url setting

Copy link
Contributor

@ftcardoso ftcardoso left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@moritz89 Could you please address the comment on django_keycloak/keycloak.py? I know that is from an already existent bug, but this way it will be already fixed with your PR. Besides that, with the tests we did, I think we can merge

Why:

- HOST header must match domain used in URL

This change addresses the need by:

- Changing the internal and server URL in _make_request_config()
@moritz89
Copy link
Collaborator Author

As a back of the envelop test, I tested the duration difference between decoding tokens vs. introspecting them against a Keycloak instance (KEYCLOAK_DECODE_TOKEN env variable). Decoding requires 0.15-0.2 ms, while the same operation required 7-8 ms while connecting to Keycloak via locahost (best case). A further optimization might be to use a ttl_cache with a timeout of 1 minute for the _decode_token function. Have you tested decoding the tokens on your system?

@ftcardoso
Copy link
Contributor

That is a great improvement! Yes, we could use ttl_cache for the decoding. Regarding the testes we weren't able to perform any specific tests (like response time). But everything seems to work and we will do more tests with the decoding option. I will merge this PR. @moritz89 it would be nice to have updates regarding the documentation and your improvements (decoding option).

@ftcardoso ftcardoso merged commit d18ec4b into urbanplatform:master Feb 23, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants