Authlib 1.7.2__tar.gz → 1.8.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (226) hide show
  1. {authlib-1.7.2 → authlib-1.8.0}/Authlib.egg-info/PKG-INFO +3 -3
  2. {authlib-1.7.2 → authlib-1.8.0}/Authlib.egg-info/SOURCES.txt +1 -0
  3. authlib-1.8.0/Authlib.egg-info/requires.txt +2 -0
  4. {authlib-1.7.2 → authlib-1.8.0}/PKG-INFO +3 -3
  5. authlib-1.8.0/authlib/common/security.py +48 -0
  6. {authlib-1.7.2 → authlib-1.8.0}/authlib/common/urls.py +11 -2
  7. {authlib-1.7.2 → authlib-1.8.0}/authlib/consts.py +1 -1
  8. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/base_client/async_openid.py +3 -2
  9. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/base_client/sync_openid.py +3 -2
  10. authlib-1.8.0/authlib/integrations/httpx_client/_compat.py +18 -0
  11. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/httpx_client/assertion_client.py +12 -13
  12. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/httpx_client/oauth1_client.py +10 -15
  13. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/httpx_client/oauth2_client.py +16 -17
  14. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/httpx_client/utils.py +7 -2
  15. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/requests_client/assertion_session.py +2 -0
  16. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth1/rfc5849/errors.py +1 -1
  17. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc7521/client.py +4 -0
  18. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc7523/assertion.py +6 -0
  19. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc7523/client.py +18 -1
  20. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc7523/jwt_bearer.py +26 -3
  21. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc7523/token.py +1 -1
  22. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc7523/validator.py +3 -2
  23. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc8628/endpoint.py +1 -1
  24. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc9068/token.py +1 -1
  25. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc9068/token_validator.py +1 -2
  26. {authlib-1.7.2 → authlib-1.8.0}/authlib/oidc/core/grants/_legacy.py +12 -8
  27. {authlib-1.7.2 → authlib-1.8.0}/authlib/oidc/core/grants/code.py +6 -4
  28. {authlib-1.7.2 → authlib-1.8.0}/pyproject.toml +21 -22
  29. authlib-1.7.2/Authlib.egg-info/requires.txt +0 -2
  30. authlib-1.7.2/authlib/common/security.py +0 -21
  31. {authlib-1.7.2 → authlib-1.8.0}/Authlib.egg-info/dependency_links.txt +0 -0
  32. {authlib-1.7.2 → authlib-1.8.0}/Authlib.egg-info/top_level.txt +0 -0
  33. {authlib-1.7.2 → authlib-1.8.0}/LICENSE +0 -0
  34. {authlib-1.7.2 → authlib-1.8.0}/MANIFEST.in +0 -0
  35. {authlib-1.7.2 → authlib-1.8.0}/README.md +0 -0
  36. {authlib-1.7.2 → authlib-1.8.0}/authlib/__init__.py +0 -0
  37. {authlib-1.7.2 → authlib-1.8.0}/authlib/_joserfc_helpers.py +0 -0
  38. {authlib-1.7.2 → authlib-1.8.0}/authlib/common/__init__.py +0 -0
  39. {authlib-1.7.2 → authlib-1.8.0}/authlib/common/encoding.py +0 -0
  40. {authlib-1.7.2 → authlib-1.8.0}/authlib/common/errors.py +0 -0
  41. {authlib-1.7.2 → authlib-1.8.0}/authlib/common/language.py +0 -0
  42. {authlib-1.7.2 → authlib-1.8.0}/authlib/deprecate.py +0 -0
  43. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/__init__.py +0 -0
  44. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/base_client/__init__.py +0 -0
  45. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/base_client/async_app.py +0 -0
  46. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/base_client/errors.py +0 -0
  47. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/base_client/framework_integration.py +0 -0
  48. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/base_client/registry.py +0 -0
  49. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/base_client/sync_app.py +0 -0
  50. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/django_client/__init__.py +0 -0
  51. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/django_client/apps.py +0 -0
  52. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/django_client/integration.py +0 -0
  53. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/django_oauth1/__init__.py +0 -0
  54. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/django_oauth1/authorization_server.py +0 -0
  55. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/django_oauth1/nonce.py +0 -0
  56. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/django_oauth1/resource_protector.py +0 -0
  57. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/django_oauth2/__init__.py +0 -0
  58. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/django_oauth2/authorization_server.py +0 -0
  59. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/django_oauth2/endpoints.py +0 -0
  60. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/django_oauth2/requests.py +0 -0
  61. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/django_oauth2/resource_protector.py +0 -0
  62. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/django_oauth2/signals.py +0 -0
  63. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/flask_client/__init__.py +0 -0
  64. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/flask_client/apps.py +0 -0
  65. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/flask_client/integration.py +0 -0
  66. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/flask_oauth1/__init__.py +0 -0
  67. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/flask_oauth1/authorization_server.py +0 -0
  68. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/flask_oauth1/cache.py +0 -0
  69. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/flask_oauth1/resource_protector.py +0 -0
  70. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/flask_oauth2/__init__.py +0 -0
  71. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/flask_oauth2/authorization_server.py +0 -0
  72. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/flask_oauth2/errors.py +0 -0
  73. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/flask_oauth2/requests.py +0 -0
  74. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/flask_oauth2/resource_protector.py +0 -0
  75. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/flask_oauth2/signals.py +0 -0
  76. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/httpx_client/__init__.py +0 -0
  77. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/requests_client/__init__.py +0 -0
  78. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/requests_client/oauth1_session.py +0 -0
  79. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/requests_client/oauth2_session.py +0 -0
  80. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/requests_client/utils.py +0 -0
  81. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/sqla_oauth2/__init__.py +0 -0
  82. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/sqla_oauth2/client_mixin.py +0 -0
  83. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/sqla_oauth2/functions.py +0 -0
  84. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/sqla_oauth2/tokens_mixins.py +0 -0
  85. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/starlette_client/__init__.py +0 -0
  86. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/starlette_client/apps.py +0 -0
  87. {authlib-1.7.2 → authlib-1.8.0}/authlib/integrations/starlette_client/integration.py +0 -0
  88. {authlib-1.7.2 → authlib-1.8.0}/authlib/jose/__init__.py +0 -0
  89. {authlib-1.7.2 → authlib-1.8.0}/authlib/jose/drafts/__init__.py +0 -0
  90. {authlib-1.7.2 → authlib-1.8.0}/authlib/jose/drafts/_jwe_algorithms.py +0 -0
  91. {authlib-1.7.2 → authlib-1.8.0}/authlib/jose/drafts/_jwe_enc_cryptodome.py +0 -0
  92. {authlib-1.7.2 → authlib-1.8.0}/authlib/jose/drafts/_jwe_enc_cryptography.py +0 -0
  93. {authlib-1.7.2 → authlib-1.8.0}/authlib/jose/errors.py +0 -0
  94. {authlib-1.7.2 → authlib-1.8.0}/authlib/jose/jwk.py +0 -0
  95. {authlib-1.7.2 → authlib-1.8.0}/authlib/jose/rfc7515/__init__.py +0 -0
  96. {authlib-1.7.2 → authlib-1.8.0}/authlib/jose/rfc7515/jws.py +0 -0
  97. {authlib-1.7.2 → authlib-1.8.0}/authlib/jose/rfc7515/models.py +0 -0
  98. {authlib-1.7.2 → authlib-1.8.0}/authlib/jose/rfc7516/__init__.py +0 -0
  99. {authlib-1.7.2 → authlib-1.8.0}/authlib/jose/rfc7516/jwe.py +0 -0
  100. {authlib-1.7.2 → authlib-1.8.0}/authlib/jose/rfc7516/models.py +0 -0
  101. {authlib-1.7.2 → authlib-1.8.0}/authlib/jose/rfc7517/__init__.py +0 -0
  102. {authlib-1.7.2 → authlib-1.8.0}/authlib/jose/rfc7517/_cryptography_key.py +0 -0
  103. {authlib-1.7.2 → authlib-1.8.0}/authlib/jose/rfc7517/asymmetric_key.py +0 -0
  104. {authlib-1.7.2 → authlib-1.8.0}/authlib/jose/rfc7517/base_key.py +0 -0
  105. {authlib-1.7.2 → authlib-1.8.0}/authlib/jose/rfc7517/jwk.py +0 -0
  106. {authlib-1.7.2 → authlib-1.8.0}/authlib/jose/rfc7517/key_set.py +0 -0
  107. {authlib-1.7.2 → authlib-1.8.0}/authlib/jose/rfc7518/__init__.py +0 -0
  108. {authlib-1.7.2 → authlib-1.8.0}/authlib/jose/rfc7518/ec_key.py +0 -0
  109. {authlib-1.7.2 → authlib-1.8.0}/authlib/jose/rfc7518/jwe_algs.py +0 -0
  110. {authlib-1.7.2 → authlib-1.8.0}/authlib/jose/rfc7518/jwe_encs.py +0 -0
  111. {authlib-1.7.2 → authlib-1.8.0}/authlib/jose/rfc7518/jwe_zips.py +0 -0
  112. {authlib-1.7.2 → authlib-1.8.0}/authlib/jose/rfc7518/jws_algs.py +0 -0
  113. {authlib-1.7.2 → authlib-1.8.0}/authlib/jose/rfc7518/oct_key.py +0 -0
  114. {authlib-1.7.2 → authlib-1.8.0}/authlib/jose/rfc7518/rsa_key.py +0 -0
  115. {authlib-1.7.2 → authlib-1.8.0}/authlib/jose/rfc7518/util.py +0 -0
  116. {authlib-1.7.2 → authlib-1.8.0}/authlib/jose/rfc7519/__init__.py +0 -0
  117. {authlib-1.7.2 → authlib-1.8.0}/authlib/jose/rfc7519/claims.py +0 -0
  118. {authlib-1.7.2 → authlib-1.8.0}/authlib/jose/rfc7519/jwt.py +0 -0
  119. {authlib-1.7.2 → authlib-1.8.0}/authlib/jose/rfc8037/__init__.py +0 -0
  120. {authlib-1.7.2 → authlib-1.8.0}/authlib/jose/rfc8037/jws_eddsa.py +0 -0
  121. {authlib-1.7.2 → authlib-1.8.0}/authlib/jose/rfc8037/okp_key.py +0 -0
  122. {authlib-1.7.2 → authlib-1.8.0}/authlib/jose/util.py +0 -0
  123. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth1/__init__.py +0 -0
  124. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth1/client.py +0 -0
  125. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth1/errors.py +0 -0
  126. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth1/rfc5849/__init__.py +0 -0
  127. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth1/rfc5849/authorization_server.py +0 -0
  128. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth1/rfc5849/base_server.py +0 -0
  129. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth1/rfc5849/client_auth.py +0 -0
  130. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth1/rfc5849/models.py +0 -0
  131. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth1/rfc5849/parameters.py +0 -0
  132. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth1/rfc5849/resource_protector.py +0 -0
  133. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth1/rfc5849/rsa.py +0 -0
  134. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth1/rfc5849/signature.py +0 -0
  135. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth1/rfc5849/util.py +0 -0
  136. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth1/rfc5849/wrapper.py +0 -0
  137. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/__init__.py +0 -0
  138. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/auth.py +0 -0
  139. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/base.py +0 -0
  140. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/claims.py +0 -0
  141. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/client.py +0 -0
  142. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc6749/__init__.py +0 -0
  143. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc6749/authenticate_client.py +0 -0
  144. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc6749/authorization_server.py +0 -0
  145. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc6749/endpoint.py +0 -0
  146. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc6749/errors.py +0 -0
  147. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc6749/grants/__init__.py +0 -0
  148. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc6749/grants/authorization_code.py +0 -0
  149. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc6749/grants/base.py +0 -0
  150. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc6749/grants/client_credentials.py +0 -0
  151. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc6749/grants/implicit.py +0 -0
  152. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc6749/grants/refresh_token.py +0 -0
  153. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc6749/grants/resource_owner_password_credentials.py +0 -0
  154. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc6749/hooks.py +0 -0
  155. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc6749/models.py +0 -0
  156. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc6749/parameters.py +0 -0
  157. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc6749/requests.py +0 -0
  158. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc6749/resource_protector.py +0 -0
  159. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc6749/token_endpoint.py +0 -0
  160. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc6749/util.py +0 -0
  161. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc6749/wrappers.py +0 -0
  162. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc6750/__init__.py +0 -0
  163. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc6750/errors.py +0 -0
  164. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc6750/parameters.py +0 -0
  165. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc6750/token.py +0 -0
  166. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc6750/validator.py +0 -0
  167. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc7009/__init__.py +0 -0
  168. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc7009/parameters.py +0 -0
  169. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc7009/revocation.py +0 -0
  170. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc7521/__init__.py +0 -0
  171. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc7523/__init__.py +0 -0
  172. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc7523/auth.py +0 -0
  173. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc7591/__init__.py +0 -0
  174. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc7591/claims.py +0 -0
  175. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc7591/endpoint.py +0 -0
  176. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc7591/errors.py +0 -0
  177. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc7592/__init__.py +0 -0
  178. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc7592/endpoint.py +0 -0
  179. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc7636/__init__.py +0 -0
  180. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc7636/challenge.py +0 -0
  181. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc7662/__init__.py +0 -0
  182. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc7662/introspection.py +0 -0
  183. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc7662/models.py +0 -0
  184. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc7662/token_validator.py +0 -0
  185. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc8414/__init__.py +0 -0
  186. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc8414/models.py +0 -0
  187. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc8414/well_known.py +0 -0
  188. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc8628/__init__.py +0 -0
  189. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc8628/device_code.py +0 -0
  190. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc8628/errors.py +0 -0
  191. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc8628/models.py +0 -0
  192. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc8693/__init__.py +0 -0
  193. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc9068/__init__.py +0 -0
  194. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc9068/claims.py +0 -0
  195. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc9068/introspection.py +0 -0
  196. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc9068/revocation.py +0 -0
  197. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc9101/__init__.py +0 -0
  198. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc9101/authorization_server.py +0 -0
  199. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc9101/discovery.py +0 -0
  200. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc9101/errors.py +0 -0
  201. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc9101/registration.py +0 -0
  202. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc9207/__init__.py +0 -0
  203. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc9207/discovery.py +0 -0
  204. {authlib-1.7.2 → authlib-1.8.0}/authlib/oauth2/rfc9207/parameter.py +0 -0
  205. {authlib-1.7.2 → authlib-1.8.0}/authlib/oidc/__init__.py +0 -0
  206. {authlib-1.7.2 → authlib-1.8.0}/authlib/oidc/core/__init__.py +0 -0
  207. {authlib-1.7.2 → authlib-1.8.0}/authlib/oidc/core/claims.py +0 -0
  208. {authlib-1.7.2 → authlib-1.8.0}/authlib/oidc/core/errors.py +0 -0
  209. {authlib-1.7.2 → authlib-1.8.0}/authlib/oidc/core/grants/__init__.py +0 -0
  210. {authlib-1.7.2 → authlib-1.8.0}/authlib/oidc/core/grants/hybrid.py +0 -0
  211. {authlib-1.7.2 → authlib-1.8.0}/authlib/oidc/core/grants/implicit.py +0 -0
  212. {authlib-1.7.2 → authlib-1.8.0}/authlib/oidc/core/grants/util.py +0 -0
  213. {authlib-1.7.2 → authlib-1.8.0}/authlib/oidc/core/models.py +0 -0
  214. {authlib-1.7.2 → authlib-1.8.0}/authlib/oidc/core/userinfo.py +0 -0
  215. {authlib-1.7.2 → authlib-1.8.0}/authlib/oidc/core/util.py +0 -0
  216. {authlib-1.7.2 → authlib-1.8.0}/authlib/oidc/discovery/__init__.py +0 -0
  217. {authlib-1.7.2 → authlib-1.8.0}/authlib/oidc/discovery/models.py +0 -0
  218. {authlib-1.7.2 → authlib-1.8.0}/authlib/oidc/discovery/well_known.py +0 -0
  219. {authlib-1.7.2 → authlib-1.8.0}/authlib/oidc/registration/__init__.py +0 -0
  220. {authlib-1.7.2 → authlib-1.8.0}/authlib/oidc/registration/claims.py +0 -0
  221. {authlib-1.7.2 → authlib-1.8.0}/authlib/oidc/rpinitiated/__init__.py +0 -0
  222. {authlib-1.7.2 → authlib-1.8.0}/authlib/oidc/rpinitiated/discovery.py +0 -0
  223. {authlib-1.7.2 → authlib-1.8.0}/authlib/oidc/rpinitiated/end_session.py +0 -0
  224. {authlib-1.7.2 → authlib-1.8.0}/authlib/oidc/rpinitiated/registration.py +0 -0
  225. {authlib-1.7.2 → authlib-1.8.0}/setup.cfg +0 -0
  226. {authlib-1.7.2 → authlib-1.8.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Authlib
3
- Version: 1.7.2
3
+ Version: 1.8.0
4
4
  Summary: The ultimate Python library in building OAuth and OpenID Connect servers and clients.
5
5
  Author-email: Hsiaoming Yang <me@lepture.com>
6
6
  License: BSD-3-Clause
@@ -32,8 +32,8 @@ Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
32
32
  Requires-Python: >=3.10
33
33
  Description-Content-Type: text/markdown
34
34
  License-File: LICENSE
35
- Requires-Dist: cryptography
36
- Requires-Dist: joserfc>=1.6.0
35
+ Requires-Dist: cryptography>=45.0.1
36
+ Requires-Dist: joserfc>=1.6.1
37
37
  Dynamic: license-file
38
38
 
39
39
  <div align="center">
@@ -54,6 +54,7 @@ authlib/integrations/flask_oauth2/requests.py
54
54
  authlib/integrations/flask_oauth2/resource_protector.py
55
55
  authlib/integrations/flask_oauth2/signals.py
56
56
  authlib/integrations/httpx_client/__init__.py
57
+ authlib/integrations/httpx_client/_compat.py
57
58
  authlib/integrations/httpx_client/assertion_client.py
58
59
  authlib/integrations/httpx_client/oauth1_client.py
59
60
  authlib/integrations/httpx_client/oauth2_client.py
@@ -0,0 +1,2 @@
1
+ cryptography>=45.0.1
2
+ joserfc>=1.6.1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Authlib
3
- Version: 1.7.2
3
+ Version: 1.8.0
4
4
  Summary: The ultimate Python library in building OAuth and OpenID Connect servers and clients.
5
5
  Author-email: Hsiaoming Yang <me@lepture.com>
6
6
  License: BSD-3-Clause
@@ -32,8 +32,8 @@ Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
32
32
  Requires-Python: >=3.10
33
33
  Description-Content-Type: text/markdown
34
34
  License-File: LICENSE
35
- Requires-Dist: cryptography
36
- Requires-Dist: joserfc>=1.6.0
35
+ Requires-Dist: cryptography>=45.0.1
36
+ Requires-Dist: joserfc>=1.6.1
37
37
  Dynamic: license-file
38
38
 
39
39
  <div align="center">
@@ -0,0 +1,48 @@
1
+ import ipaddress
2
+ import os
3
+ import random
4
+ import string
5
+ from urllib.parse import urlsplit
6
+
7
+ UNICODE_ASCII_CHARACTER_SET = string.ascii_letters + string.digits
8
+
9
+
10
+ def generate_token(length=30, chars=UNICODE_ASCII_CHARACTER_SET):
11
+ rand = random.SystemRandom()
12
+ return "".join(rand.choice(chars) for _ in range(length))
13
+
14
+
15
+ def is_secure_transport(uri):
16
+ """Check if the uri is over ssl."""
17
+ if os.getenv("AUTHLIB_INSECURE_TRANSPORT"):
18
+ return True
19
+
20
+ try:
21
+ parts = urlsplit(uri)
22
+ except ValueError:
23
+ return False
24
+
25
+ if not parts.hostname:
26
+ return False
27
+
28
+ if parts.scheme == "https":
29
+ return True
30
+
31
+ if parts.scheme != "http":
32
+ return False
33
+
34
+ # rfc8252 §7.3: native apps may use http for loopback redirection URIs.
35
+ if parts.hostname == "localhost":
36
+ return True
37
+
38
+ try:
39
+ address = ipaddress.ip_address(parts.hostname)
40
+ except ValueError:
41
+ return False
42
+
43
+ # IPv6Address.is_loopback only accounts for the mapped IPv4 address since
44
+ # CPython 3.10.16, 3.11.11 and 3.12.4.
45
+ if isinstance(address, ipaddress.IPv6Address) and address.ipv4_mapped:
46
+ address = address.ipv4_mapped
47
+
48
+ return address.is_loopback
@@ -140,7 +140,16 @@ def extract_params(raw):
140
140
 
141
141
 
142
142
  def is_valid_url(url: str, fragments_allowed=True):
143
- parsed = urlparse.urlparse(url)
144
- return (
143
+ try:
144
+ parsed = urlparse.urlparse(url)
145
+ except ValueError:
146
+ return False
147
+
148
+ # The userinfo component can disguise the actual host,
149
+ # as in "https://provider.test@attacker.test/".
150
+ if "@" in parsed.netloc:
151
+ return False
152
+
153
+ return bool(
145
154
  parsed.scheme and parsed.hostname and (fragments_allowed or not parsed.fragment)
146
155
  )
@@ -1,5 +1,5 @@
1
1
  name = "Authlib"
2
- version = "1.7.2"
2
+ version = "1.8.0"
3
3
  author = "Hsiaoming Yang <me@lepture.com>"
4
4
  homepage = "https://authlib.org"
5
5
  default_user_agent = f"{name}/{version} (+{homepage})"
@@ -1,6 +1,7 @@
1
1
  from joserfc import jwt
2
2
  from joserfc.errors import InvalidKeyIdError
3
3
  from joserfc.jwk import KeySet
4
+ from joserfc.jws import JWSRegistry
4
5
 
5
6
  from authlib.common.security import generate_token
6
7
  from authlib.common.urls import add_params_to_uri
@@ -67,7 +68,7 @@ class AsyncOpenIDMixin:
67
68
  token = jwt.decode(
68
69
  token["id_token"],
69
70
  key=key_set,
70
- algorithms=alg_values,
71
+ registry=JWSRegistry(algorithms=alg_values, strict_check_header=False),
71
72
  )
72
73
  except InvalidKeyIdError:
73
74
  jwks = await self.fetch_jwk_set(force=True)
@@ -75,7 +76,7 @@ class AsyncOpenIDMixin:
75
76
  token = jwt.decode(
76
77
  token["id_token"],
77
78
  key=key_set,
78
- algorithms=alg_values,
79
+ registry=JWSRegistry(algorithms=alg_values, strict_check_header=False),
79
80
  )
80
81
 
81
82
  claims = claims_cls(token.claims, token.header, claims_options, claims_params)
@@ -1,6 +1,7 @@
1
1
  from joserfc import jwt
2
2
  from joserfc.errors import InvalidKeyIdError
3
3
  from joserfc.jwk import KeySet
4
+ from joserfc.jws import JWSRegistry
4
5
 
5
6
  from authlib.common.security import generate_token
6
7
  from authlib.common.urls import add_params_to_uri
@@ -66,14 +67,14 @@ class OpenIDMixin:
66
67
  token = jwt.decode(
67
68
  token["id_token"],
68
69
  key=key_set,
69
- algorithms=alg_values,
70
+ registry=JWSRegistry(algorithms=alg_values, strict_check_header=False),
70
71
  )
71
72
  except InvalidKeyIdError:
72
73
  key_set = KeySet.import_key_set(self.fetch_jwk_set(force=True))
73
74
  token = jwt.decode(
74
75
  token["id_token"],
75
76
  key=key_set,
76
- algorithms=alg_values,
77
+ registry=JWSRegistry(algorithms=alg_values, strict_check_header=False),
77
78
  )
78
79
 
79
80
  claims = claims_cls(token.claims, token.header, claims_options, claims_params)
@@ -0,0 +1,18 @@
1
+ """Compatibility shim for httpx2 with legacy httpx fallback.
2
+
3
+ Falls back to httpx when httpx2 is unavailable. The fallback is deprecated
4
+ and will be removed in a future release.
5
+ """
6
+
7
+ from authlib.deprecate import deprecate
8
+
9
+ try:
10
+ import httpx2
11
+ except ImportError:
12
+ import httpx as httpx2
13
+
14
+ deprecate(
15
+ "The httpx module is deprecated; please use httpx2 instead."
16
+ )
17
+
18
+ __all__ = ["httpx2"]
@@ -1,18 +1,18 @@
1
- import httpx
2
- from httpx import USE_CLIENT_DEFAULT
3
- from httpx import Response
4
-
5
1
  from authlib.oauth2.rfc7521 import AssertionClient as _AssertionClient
6
2
  from authlib.oauth2.rfc7523 import JWTBearerGrant
7
3
 
8
4
  from ..base_client import OAuthError
5
+ from ._compat import httpx2
9
6
  from .oauth2_client import OAuth2Auth
10
7
  from .utils import extract_client_kwargs
11
8
 
9
+ USE_CLIENT_DEFAULT = httpx2.USE_CLIENT_DEFAULT
10
+ Response = httpx2.Response
11
+
12
12
  __all__ = ["AsyncAssertionClient"]
13
13
 
14
14
 
15
- class AsyncAssertionClient(_AssertionClient, httpx.AsyncClient):
15
+ class AsyncAssertionClient(_AssertionClient, httpx2.AsyncClient):
16
16
  token_auth_class = OAuth2Auth
17
17
  oauth_error_class = OAuthError
18
18
  JWT_BEARER_GRANT_TYPE = JWTBearerGrant.GRANT_TYPE
@@ -31,10 +31,11 @@ class AsyncAssertionClient(_AssertionClient, httpx.AsyncClient):
31
31
  claims=None,
32
32
  token_placement="header",
33
33
  scope=None,
34
+ client_id=None,
34
35
  **kwargs,
35
36
  ):
36
37
  client_kwargs = extract_client_kwargs(kwargs)
37
- httpx.AsyncClient.__init__(self, **client_kwargs)
38
+ httpx2.AsyncClient.__init__(self, **client_kwargs)
38
39
 
39
40
  _AssertionClient.__init__(
40
41
  self,
@@ -47,6 +48,7 @@ class AsyncAssertionClient(_AssertionClient, httpx.AsyncClient):
47
48
  claims=claims,
48
49
  token_placement=token_placement,
49
50
  scope=scope,
51
+ client_id=client_id,
50
52
  **kwargs,
51
53
  )
52
54
 
@@ -69,7 +71,7 @@ class AsyncAssertionClient(_AssertionClient, httpx.AsyncClient):
69
71
  return self.parse_response_token(resp)
70
72
 
71
73
 
72
- class AssertionClient(_AssertionClient, httpx.Client):
74
+ class AssertionClient(_AssertionClient, httpx2.Client):
73
75
  token_auth_class = OAuth2Auth
74
76
  oauth_error_class = OAuthError
75
77
  JWT_BEARER_GRANT_TYPE = JWTBearerGrant.GRANT_TYPE
@@ -88,15 +90,11 @@ class AssertionClient(_AssertionClient, httpx.Client):
88
90
  claims=None,
89
91
  token_placement="header",
90
92
  scope=None,
93
+ client_id=None,
91
94
  **kwargs,
92
95
  ):
93
96
  client_kwargs = extract_client_kwargs(kwargs)
94
- # app keyword was dropped!
95
- app_value = client_kwargs.pop("app", None)
96
- if app_value is not None:
97
- client_kwargs["transport"] = httpx.WSGITransport(app=app_value)
98
-
99
- httpx.Client.__init__(self, **client_kwargs)
97
+ httpx2.Client.__init__(self, **client_kwargs)
100
98
 
101
99
  _AssertionClient.__init__(
102
100
  self,
@@ -109,6 +107,7 @@ class AssertionClient(_AssertionClient, httpx.Client):
109
107
  claims=claims,
110
108
  token_placement=token_placement,
111
109
  scope=scope,
110
+ client_id=client_id,
112
111
  **kwargs,
113
112
  )
114
113
 
@@ -1,10 +1,5 @@
1
1
  import typing
2
2
 
3
- import httpx
4
- from httpx import Auth
5
- from httpx import Request
6
- from httpx import Response
7
-
8
3
  from authlib.common.encoding import to_unicode
9
4
  from authlib.oauth1 import SIGNATURE_HMAC_SHA1
10
5
  from authlib.oauth1 import SIGNATURE_TYPE_HEADER
@@ -12,12 +7,17 @@ from authlib.oauth1 import ClientAuth
12
7
  from authlib.oauth1.client import OAuth1Client as _OAuth1Client
13
8
 
14
9
  from ..base_client import OAuthError
10
+ from ._compat import httpx2
15
11
  from .utils import build_request
16
12
  from .utils import extract_client_kwargs
17
13
 
14
+ Auth = httpx2.Auth
15
+ Request = httpx2.Request
16
+ Response = httpx2.Response
17
+
18
18
 
19
19
  class OAuth1Auth(Auth, ClientAuth):
20
- """Signs the httpx request using OAuth 1 (RFC5849)."""
20
+ """Signs the httpx2 request using OAuth 1 (RFC5849)."""
21
21
 
22
22
  requires_request_body = True
23
23
 
@@ -31,7 +31,7 @@ class OAuth1Auth(Auth, ClientAuth):
31
31
  )
32
32
 
33
33
 
34
- class AsyncOAuth1Client(_OAuth1Client, httpx.AsyncClient):
34
+ class AsyncOAuth1Client(_OAuth1Client, httpx2.AsyncClient):
35
35
  auth_class = OAuth1Auth
36
36
 
37
37
  def __init__(
@@ -49,7 +49,7 @@ class AsyncOAuth1Client(_OAuth1Client, httpx.AsyncClient):
49
49
  **kwargs,
50
50
  ):
51
51
  _client_kwargs = extract_client_kwargs(kwargs)
52
- httpx.AsyncClient.__init__(self, **_client_kwargs)
52
+ httpx2.AsyncClient.__init__(self, **_client_kwargs)
53
53
 
54
54
  _OAuth1Client.__init__(
55
55
  self,
@@ -99,7 +99,7 @@ class AsyncOAuth1Client(_OAuth1Client, httpx.AsyncClient):
99
99
  raise OAuthError(error_type, error_description)
100
100
 
101
101
 
102
- class OAuth1Client(_OAuth1Client, httpx.Client):
102
+ class OAuth1Client(_OAuth1Client, httpx2.Client):
103
103
  auth_class = OAuth1Auth
104
104
 
105
105
  def __init__(
@@ -117,12 +117,7 @@ class OAuth1Client(_OAuth1Client, httpx.Client):
117
117
  **kwargs,
118
118
  ):
119
119
  _client_kwargs = extract_client_kwargs(kwargs)
120
- # app keyword was dropped!
121
- app_value = _client_kwargs.pop("app", None)
122
- if app_value is not None:
123
- _client_kwargs["transport"] = httpx.WSGITransport(app=app_value)
124
-
125
- httpx.Client.__init__(self, **_client_kwargs)
120
+ httpx2.Client.__init__(self, **_client_kwargs)
126
121
 
127
122
  _OAuth1Client.__init__(
128
123
  self,
@@ -1,12 +1,11 @@
1
+ # ruff: noqa: I001
2
+ # The import order below is intentional: httpx compatibility must be loaded
3
+ # before anyio so import errors refer to httpx.
1
4
  import typing
2
5
  from contextlib import asynccontextmanager
3
6
 
4
- import httpx
5
- from anyio import Lock # Import after httpx so import errors refer to httpx
6
- from httpx import USE_CLIENT_DEFAULT
7
- from httpx import Auth
8
- from httpx import Request
9
- from httpx import Response
7
+ from ._compat import httpx2
8
+ from anyio import Lock # Import after httpx so import errors refer to httpx
10
9
 
11
10
  from authlib.common.urls import url_decode
12
11
  from authlib.oauth2.auth import ClientAuth
@@ -20,6 +19,11 @@ from ..base_client import UnsupportedTokenTypeError
20
19
  from .utils import HTTPX_CLIENT_KWARGS
21
20
  from .utils import build_request
22
21
 
22
+ USE_CLIENT_DEFAULT = httpx2.USE_CLIENT_DEFAULT
23
+ Auth = httpx2.Auth
24
+ Request = httpx2.Request
25
+ Response = httpx2.Response
26
+
23
27
  __all__ = [
24
28
  "OAuth2Auth",
25
29
  "OAuth2ClientAuth",
@@ -60,7 +64,7 @@ class OAuth2ClientAuth(Auth, ClientAuth):
60
64
  )
61
65
 
62
66
 
63
- class AsyncOAuth2Client(_OAuth2Client, httpx.AsyncClient):
67
+ class AsyncOAuth2Client(_OAuth2Client, httpx2.AsyncClient):
64
68
  SESSION_REQUEST_PARAMS = HTTPX_CLIENT_KWARGS
65
69
 
66
70
  client_auth_class = OAuth2ClientAuth
@@ -81,9 +85,9 @@ class AsyncOAuth2Client(_OAuth2Client, httpx.AsyncClient):
81
85
  leeway=60,
82
86
  **kwargs,
83
87
  ):
84
- # extract httpx.Client kwargs
88
+ # extract httpx2.Client kwargs
85
89
  client_kwargs = self._extract_session_request_params(kwargs)
86
- httpx.AsyncClient.__init__(self, **client_kwargs)
90
+ httpx2.AsyncClient.__init__(self, **client_kwargs)
87
91
 
88
92
  # We use a Lock to synchronize coroutines to prevent
89
93
  # multiple concurrent attempts to refresh the same token
@@ -208,7 +212,7 @@ class AsyncOAuth2Client(_OAuth2Client, httpx.AsyncClient):
208
212
  )
209
213
 
210
214
 
211
- class OAuth2Client(_OAuth2Client, httpx.Client):
215
+ class OAuth2Client(_OAuth2Client, httpx2.Client):
212
216
  SESSION_REQUEST_PARAMS = HTTPX_CLIENT_KWARGS
213
217
 
214
218
  client_auth_class = OAuth2ClientAuth
@@ -228,14 +232,9 @@ class OAuth2Client(_OAuth2Client, httpx.Client):
228
232
  update_token=None,
229
233
  **kwargs,
230
234
  ):
231
- # extract httpx.Client kwargs
235
+ # extract httpx2.Client kwargs
232
236
  client_kwargs = self._extract_session_request_params(kwargs)
233
- # app keyword was dropped!
234
- app_value = client_kwargs.pop("app", None)
235
- if app_value is not None:
236
- client_kwargs["transport"] = httpx.WSGITransport(app=app_value)
237
-
238
- httpx.Client.__init__(self, **client_kwargs)
237
+ httpx2.Client.__init__(self, **client_kwargs)
239
238
 
240
239
  _OAuth2Client.__init__(
241
240
  self,
@@ -1,4 +1,6 @@
1
- from httpx import Request
1
+ from ._compat import httpx2
2
+
3
+ Request = httpx2.Request
2
4
 
3
5
  HTTPX_CLIENT_KWARGS = [
4
6
  "headers",
@@ -32,7 +34,10 @@ def extract_client_kwargs(kwargs):
32
34
  def build_request(url, headers, body, initial_request: Request) -> Request:
33
35
  """Make sure that all the data from initial request is passed to the updated object."""
34
36
  updated_request = Request(
35
- method=initial_request.method, url=url, headers=headers, content=body
37
+ method=initial_request.method,
38
+ url=url,
39
+ headers=headers,
40
+ content=body,
36
41
  )
37
42
 
38
43
  if hasattr(initial_request, "extensions"):
@@ -39,6 +39,7 @@ class AssertionSession(AssertionClient, Session):
39
39
  claims=None,
40
40
  token_placement="header",
41
41
  scope=None,
42
+ client_id=None,
42
43
  default_timeout=None,
43
44
  leeway=60,
44
45
  **kwargs,
@@ -57,6 +58,7 @@ class AssertionSession(AssertionClient, Session):
57
58
  claims=claims,
58
59
  token_placement=token_placement,
59
60
  scope=scope,
61
+ client_id=client_id,
60
62
  leeway=leeway,
61
63
  **kwargs,
62
64
  )
@@ -26,7 +26,7 @@ class OAuth1Error(AuthlibHTTPError):
26
26
 
27
27
  class InsecureTransportError(OAuth1Error):
28
28
  error = "insecure_transport"
29
- description = "OAuth 2 MUST utilize https."
29
+ description = "OAuth 1 MUST utilize https."
30
30
 
31
31
  @classmethod
32
32
  def check(cls, uri):
@@ -25,6 +25,7 @@ class AssertionClient:
25
25
  claims=None,
26
26
  token_placement="header",
27
27
  scope=None,
28
+ client_id=None,
28
29
  leeway=60,
29
30
  **kwargs,
30
31
  ):
@@ -46,6 +47,7 @@ class AssertionClient:
46
47
  self.audience = audience
47
48
  self.claims = claims
48
49
  self.scope = scope
50
+ self.client_id = client_id
49
51
  if self.token_auth_class is not None:
50
52
  self.token_auth = self.token_auth_class(None, token_placement, self)
51
53
  self._kwargs = kwargs
@@ -79,6 +81,8 @@ class AssertionClient:
79
81
  }
80
82
  if self.scope:
81
83
  data["scope"] = self.scope
84
+ if self.client_id:
85
+ data["client_id"] = self.client_id
82
86
 
83
87
  return self._refresh_token(data)
84
88
 
@@ -41,6 +41,12 @@ def sign_jwt_bearer_assertion(
41
41
  payload["iat"] = issued_at
42
42
  payload["exp"] = expires_at
43
43
 
44
+ if claims is None:
45
+ claims = {}
46
+ # jti uniquely identifies the JWT and prevents replay attacks (RFC 7523 §3)
47
+ if "jti" not in claims:
48
+ claims["jti"] = generate_token(36)
49
+
44
50
  if claims:
45
51
  payload.update(claims)
46
52
 
@@ -46,7 +46,15 @@ class JWTBearerClientAssertion:
46
46
  assertion = data.get("client_assertion")
47
47
  if assertion_type == ASSERTION_TYPE and assertion:
48
48
  headers, claims = self.extract_assertion(assertion)
49
+ # "sub" is read before the signature and the claims are verified,
50
+ # so it cannot be assumed to be present nor to be a string.
51
+ if "sub" not in claims:
52
+ raise InvalidClientError(description="Missing claim: 'sub'")
53
+
49
54
  client_id = claims["sub"]
55
+ if not isinstance(client_id, str):
56
+ raise InvalidClientError(description="Invalid claim: 'sub'")
57
+
50
58
  client = query_client(client_id)
51
59
  if not client:
52
60
  raise InvalidClientError(
@@ -137,11 +145,20 @@ class JWTBearerClientAssertion:
137
145
  )
138
146
 
139
147
  def extract_assertion(self, assertion: str):
140
- obj = jws.extract_compact(to_bytes(assertion))
148
+ try:
149
+ obj = jws.extract_compact(to_bytes(assertion))
150
+ except JoseError as e:
151
+ log.debug("Assertion Error: %r", e)
152
+ raise InvalidClientError(description="Invalid JWT assertion.") from e
153
+
141
154
  try:
142
155
  claims = json_loads(obj.payload)
143
156
  except ValueError:
144
157
  raise InvalidClientError(description="Invalid JWT payload.") from None
158
+
159
+ if not isinstance(claims, dict):
160
+ raise InvalidClientError(description="Invalid JWT payload.")
161
+
145
162
  return obj.headers(), claims
146
163
 
147
164
  def validate_jti(self, claims, jti):
@@ -82,7 +82,20 @@ class JWTBearerGrant(BaseGrant, TokenEndpointMixin):
82
82
  .. _`Section 3.1`: https://tools.ietf.org/html/rfc7523#section-3.1
83
83
  """
84
84
  headers, claims = self.extract_assertion(assertion)
85
- client = self.resolve_issuer_client(claims["iss"])
85
+ # "iss" is read before the signature and the claims are verified, so it
86
+ # cannot be assumed to be present nor to be a string.
87
+ if "iss" not in claims:
88
+ raise InvalidGrantError(description="Missing claim: 'iss'")
89
+
90
+ issuer = claims["iss"]
91
+ if not isinstance(issuer, str):
92
+ raise InvalidGrantError(description="Invalid claim: 'iss'")
93
+
94
+ client = self.resolve_issuer_client(issuer)
95
+ if not client:
96
+ raise InvalidGrantError(
97
+ description="The client does not exist on this server."
98
+ )
86
99
 
87
100
  if hasattr(self, "resolve_client_key"): # pragma: no cover
88
101
  key = import_any_key(self.resolve_client_key(client, headers, claims))
@@ -106,11 +119,20 @@ class JWTBearerGrant(BaseGrant, TokenEndpointMixin):
106
119
  return token.claims
107
120
 
108
121
  def extract_assertion(self, assertion: str):
109
- obj = jws.extract_compact(to_bytes(assertion))
122
+ try:
123
+ obj = jws.extract_compact(to_bytes(assertion))
124
+ except JoseError as e:
125
+ log.debug("Assertion Error: %r", e)
126
+ raise InvalidGrantError(description="Invalid JWT assertion.") from e
127
+
110
128
  try:
111
129
  claims = json_loads(obj.payload)
112
130
  except ValueError:
113
131
  raise InvalidGrantError(description="Invalid JWT payload.") from None
132
+
133
+ if not isinstance(claims, dict):
134
+ raise InvalidGrantError(description="Invalid JWT payload.")
135
+
114
136
  return obj.headers(), claims
115
137
 
116
138
  def validate_token_request(self):
@@ -194,7 +216,8 @@ class JWTBearerGrant(BaseGrant, TokenEndpointMixin):
194
216
  return Client.query_by_iss(issuer)
195
217
 
196
218
  :param issuer: "iss" value in assertion
197
- :return: Client instance
219
+ :return: Client instance, or ``None`` when no client matches the
220
+ issuer. In that case an ``invalid_grant`` error is returned.
198
221
  """
199
222
  raise NotImplementedError()
200
223
 
@@ -48,7 +48,7 @@ class JWTBearerTokenGenerator:
48
48
  def get_sub_value(user):
49
49
  return str(user.id)
50
50
  """
51
- return user.get_user_id()
51
+ return str(user.get_user_id())
52
52
 
53
53
  def get_token_data(self, grant_type, client, expires_in, user=None, scope=None):
54
54
  scope = self.get_allowed_scope(client, scope)
@@ -33,9 +33,10 @@ class JWTBearerTokenValidator(BearerTokenValidator):
33
33
  TOKEN_TYPE = "bearer"
34
34
  token_cls = JWTBearerToken
35
35
 
36
- def __init__(self, public_key, issuer=None, realm=None, **extra_attributes):
36
+ def __init__(self, public_key, issuer=None, realm=None, leeway=60, **extra_attributes):
37
37
  super().__init__(realm, **extra_attributes)
38
38
  self.public_key = import_any_key(public_key)
39
+ self.leeway = leeway
39
40
  claims_options = {
40
41
  "exp": {"essential": True},
41
42
  "client_id": {"essential": True},
@@ -52,7 +53,7 @@ class JWTBearerTokenValidator(BearerTokenValidator):
52
53
  logger.debug("Authenticate token failed. %r", error)
53
54
  return None
54
55
 
55
- claims_requests = jwt.JWTClaimsRegistry(leeway=60, **self.claims_options)
56
+ claims_requests = jwt.JWTClaimsRegistry(leeway=self.leeway, **self.claims_options)
56
57
  try:
57
58
  claims_requests.validate(token.claims)
58
59
  except JoseError as error:
@@ -115,7 +115,7 @@ class DeviceAuthorizationEndpoint:
115
115
  }
116
116
 
117
117
  self.save_device_credential(
118
- request.payload.client_id, request.payload.scope, data
118
+ request.client.get_client_id(), request.payload.scope, data
119
119
  )
120
120
  return 200, data, default_json_headers
121
121
 
@@ -144,7 +144,7 @@ class JWTBearerTokenGenerator(BearerTokenGenerator):
144
144
  # correspond to the subject identifier of the resource owner.
145
145
 
146
146
  if user:
147
- token_data["sub"] = user.get_user_id()
147
+ token_data["sub"] = str(user.get_user_id())
148
148
 
149
149
  # In cases of access tokens obtained through grants where no resource owner is
150
150
  # involved, such as the client credentials grant, the value of 'sub' SHOULD
@@ -7,7 +7,6 @@ Implementation of Validating JWT Access Tokens per `Section 4`_.
7
7
  """
8
8
 
9
9
  from joserfc import jwt
10
- from joserfc.errors import DecodeError
11
10
  from joserfc.errors import JoseError
12
11
 
13
12
  from authlib._joserfc_helpers import import_any_key
@@ -115,7 +114,7 @@ class JWTBearerTokenValidator(BearerTokenValidator):
115
114
  try:
116
115
  token = jwt.decode(token_string, key=key)
117
116
  return JWTAccessTokenClaims(token.claims, token.header, claims_options)
118
- except DecodeError as exc:
117
+ except JoseError as exc:
119
118
  raise InvalidTokenError(
120
119
  realm=self.realm, extra_attributes=self.extra_attributes
121
120
  ) from exc