Repository: lepture/authlib (language: python)

Issue:
Flask tests fail under Python 3.13
**Describe the bug**

The flask tests fail under Python 3.13

**Error Stacks**

```
11:41/0 stefanor@haydn ~/git/upstream/authlib$ tox -e py313-flask
.pkg: _optional_hooks> python /usr/lib/python3/dist-packages/pyproject_api/_backend.py True setuptools.build_meta
.pkg: get_requires_for_build_sdist> python /usr/lib/python3/dist-packages/pyproject_api/_backend.py True setuptools.build_meta
.pkg: build_sdist> python /usr/lib/python3/dist-packages/pyproject_api/_backend.py True setuptools.build_meta
py313-flask: install_package> python -I -m pip install --force-reinstall --no-deps /home/stefanor/git/upstream/authlib/.tox/.tmp/package/5/authlib-1.3.2.tar.gz
py313-flask: commands[0]> coverage run --source=authlib -p -m pytest tests/flask
/home/stefanor/git/upstream/authlib/.tox/py313-flask/lib/python3.13/site-packages/pytest_asyncio/plugin.py:208: PytestDeprecationWarning: The configuration option "asyncio_default_fixture_loop_scope" is unset.
The event loop scope for asynchronous fixtures will default to the fixture caching scope. Future versions of pytest-asyncio will default the loop scope for asynchronous fixtures to function scope. Set the default fixture loop scope explicitly in order to avoid unexpected behavior in the future. Valid fixture loop scopes are: "function", "class", "module", "package", "session"

  warnings.warn(PytestDeprecationWarning(_DEFAULT_FIXTURE_LOOP_SCOPE_UNSET))
==================================== test session starts =====================================
platform linux -- Python 3.13.0rc2, pytest-8.3.3, pluggy-1.5.0
cachedir: .tox/py313-flask/.pytest_cache
rootdir: /home/stefanor/git/upstream/authlib
configfile: tox.ini
plugins: asyncio-0.24.0
asyncio: mode=Mode.AUTO, default_loop_scope=None
collected 212 items                                                                          

tests/flask/test_oauth1/test_authorize.py ......                                       [  2%]
tests/flask/test_oauth1/test_resource_protector.py ........                            [  6%]
tests/flask/test_oauth1/test_temporary_credentials.py ................                 [ 14%]
tests/flask/test_oauth1/test_token_credentials.py ......                               [ 16%]
tests/flask/test_oauth2/test_authorization_code_grant.py .............                 [ 23%]
tests/flask/test_oauth2/test_client_configuration_endpoint.py ..................       [ 31%]
tests/flask/test_oauth2/test_client_credentials_grant.py .....                         [ 33%]
tests/flask/test_oauth2/test_client_registration_endpoint.py .........                 [ 38%]
tests/flask/test_oauth2/test_code_challenge.py .............                           [ 44%]
tests/flask/test_oauth2/test_device_code_grant.py .........                            [ 48%]
tests/flask/test_oauth2/test_implicit_grant.py ......                                  [ 51%]
tests/flask/test_oauth2/test_introspection_endpoint.py ....                            [ 53%]
tests/flask/test_oauth2/test_jwt_access_token.py ..s.FFF........FF.............        [ 67%]
tests/flask/test_oauth2/test_jwt_bearer_client_auth.py .......                         [ 70%]
tests/flask/test_oauth2/test_jwt_bearer_grant.py ......                                [ 73%]
tests/flask/test_oauth2/test_oauth2_server.py .......                                  [ 76%]
tests/flask/test_oauth2/test_openid_code_grant.py ........                             [ 80%]
tests/flask/test_oauth2/test_openid_hybrid_grant.py ..........                         [ 85%]
tests/flask/test_oauth2/test_openid_implict_grant.py ........                          [ 89%]
tests/flask/test_oauth2/test_password_grant.py ........                                [ 92%]
tests/flask/test_oauth2/test_refresh_token.py ..........                               [ 97%]
tests/flask/test_oauth2/test_revocation_endpoint.py .....                              [100%]

========================================== FAILURES ==========================================
___________________ JWTAccessTokenResourceServerTest.test_access_resource ____________________

self = <tests.flask.test_oauth2.test_jwt_access_token.JWTAccessTokenResourceServerTest testMethod=test_access_resource>

    def test_access_resource(self):
        headers = {'Authorization': f'Bearer {self.access_token}'}
    
>       rv = self.client.get('/protected', headers=headers)

tests/flask/test_oauth2/test_jwt_access_token.py:400: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.tox/py313-flask/lib/python3.13/site-packages/werkzeug/test.py:1162: in get
    return self.open(*args, **kw)
.tox/py313-flask/lib/python3.13/site-packages/flask/testing.py:235: in open
    response = super().open(
.tox/py313-flask/lib/python3.13/site-packages/werkzeug/test.py:1116: in open
    response_parts = self.run_wsgi_app(request.environ, buffered=buffered)
.tox/py313-flask/lib/python3.13/site-packages/werkzeug/test.py:988: in run_wsgi_app
    rv = run_wsgi_app(self.application, environ, buffered=buffered)
.tox/py313-flask/lib/python3.13/site-packages/werkzeug/test.py:1264: in run_wsgi_app
    app_rv = app(environ, start_response)
.tox/py313-flask/lib/python3.13/site-packages/flask/app.py:1498: in __call__
    return self.wsgi_app(environ, start_response)
.tox/py313-flask/lib/python3.13/site-packages/flask/app.py:1476: in wsgi_app
    response = self.handle_exception(e)
.tox/py313-flask/lib/python3.13/site-packages/flask/app.py:1473: in wsgi_app
    response = self.full_dispatch_request()
.tox/py313-flask/lib/python3.13/site-packages/flask/app.py:882: in full_dispatch_request
    rv = self.handle_user_exception(e)
.tox/py313-flask/lib/python3.13/site-packages/flask/app.py:880: in full_dispatch_request
    rv = self.dispatch_request()
.tox/py313-flask/lib/python3.13/site-packages/flask/app.py:865: in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)  # type: ignore[no-any-return]
authlib/integrations/flask_oauth2/resource_protector.py:105: in decorated
    return f(*args, **kwargs)
tests/flask/test_oauth2/test_jwt_access_token.py:52: in protected
    return jsonify(id=user.id, username=user.username, token=current_token)
.tox/py313-flask/lib/python3.13/site-packages/flask/json/__init__.py:170: in jsonify
    return current_app.json.response(*args, **kwargs)  # type: ignore[return-value]
.tox/py313-flask/lib/python3.13/site-packages/flask/json/provider.py:214: in response
    f"{self.dumps(obj, **dump_args)}\n", mimetype=self.mimetype
.tox/py313-flask/lib/python3.13/site-packages/flask/json/provider.py:179: in dumps
    return json.dumps(obj, **kwargs)
/usr/lib/python3.13/json/__init__.py:238: in dumps
    **kw).encode(obj)
/usr/lib/python3.13/json/encoder.py:200: in encode
    chunks = self.iterencode(o, _one_shot=True)
/usr/lib/python3.13/json/encoder.py:261: in iterencode
    return _iterencode(o, 0)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

o = <LocalProxy unbound>

    def _default(o: t.Any) -> t.Any:
        if isinstance(o, date):
            return http_date(o)
    
        if isinstance(o, (decimal.Decimal, uuid.UUID)):
            return str(o)
    
        if dataclasses and dataclasses.is_dataclass(o):
            return dataclasses.asdict(o)
    
        if hasattr(o, "__html__"):
            return str(o.__html__())
    
>       raise TypeError(f"Object of type {type(o).__name__} is not JSON serializable")
E       TypeError: Object of type LocalProxy is not JSON serializable

.tox/py313-flask/lib/python3.13/site-packages/flask/json/provider.py:121: TypeError
_______________ JWTAccessTokenResourceServerTest.test_entitlements_restriction _______________

self = <tests.flask.test_oauth2.test_jwt_access_token.JWTAccessTokenResourceServerTest testMethod=test_entitlements_restriction>

    def test_entitlements_restriction(self):
        '''
        Many authorization servers embed authorization attributes that go beyond the
        delegated scenarios described by [RFC7519] in the access tokens they issue.
        Typical examples include resource owner memberships in roles and groups that
        are relevant to the resource being accessed, entitlements assigned to the
        resource owner for the targeted resource that the authorization server knows
        about, and so on. An authorization server wanting to include such attributes
        in a JWT access token SHOULD use the 'groups', 'roles', and 'entitlements'
        attributes of the 'User' resource schema defined by Section 4.1.2 of
        [RFC7643]) as claim types.
        '''
    
        for claim in ['groups', 'roles', 'entitlements']:
            claims = create_access_token_claims(
                self.oauth_client, self.user, self.issuer
            )
            claims[claim] = ['invalid']
            access_token = create_access_token(claims, self.jwks)
    
            headers = {'Authorization': f'Bearer {access_token}'}
>           rv = self.client.get('/protected', headers=headers)

tests/flask/test_oauth2/test_jwt_access_token.py:551: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.tox/py313-flask/lib/python3.13/site-packages/werkzeug/test.py:1162: in get
    return self.open(*args, **kw)
.tox/py313-flask/lib/python3.13/site-packages/flask/testing.py:235: in open
    response = super().open(
.tox/py313-flask/lib/python3.13/site-packages/werkzeug/test.py:1116: in open
    response_parts = self.run_wsgi_app(request.environ, buffered=buffered)
.tox/py313-flask/lib/python3.13/site-packages/werkzeug/test.py:988: in run_wsgi_app
    rv = run_wsgi_app(self.application, environ, buffered=buffered)
.tox/py313-flask/lib/python3.13/site-packages/werkzeug/test.py:1264: in run_wsgi_app
    app_rv = app(environ, start_response)
.tox/py313-flask/lib/python3.13/site-packages/flask/app.py:1498: in __call__
    return self.wsgi_app(environ, start_response)
.tox/py313-flask/lib/python3.13/site-packages/flask/app.py:1476: in wsgi_app
    response = self.handle_exception(e)
.tox/py313-flask/lib/python3.13/site-packages/flask/app.py:1473: in wsgi_app
    response = self.full_dispatch_request()
.tox/py313-flask/lib/python3.13/site-packages/flask/app.py:882: in full_dispatch_request
    rv = self.handle_user_exception(e)
.tox/py313-flask/lib/python3.13/site-packages/flask/app.py:880: in full_dispatch_request
    rv = self.dispatch_request()
.tox/py313-flask/lib/python3.13/site-packages/flask/app.py:865: in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)  # type: ignore[no-any-return]
authlib/integrations/flask_oauth2/resource_protector.py:105: in decorated
    return f(*args, **kwargs)
tests/flask/test_oauth2/test_jwt_access_token.py:52: in protected
    return jsonify(id=user.id, username=user.username, token=current_token)
.tox/py313-flask/lib/python3.13/site-packages/flask/json/__init__.py:170: in jsonify
    return current_app.json.response(*args, **kwargs)  # type: ignore[return-value]
.tox/py313-flask/lib/python3.13/site-packages/flask/json/provider.py:214: in response
    f"{self.dumps(obj, **dump_args)}\n", mimetype=self.mimetype
.tox/py313-flask/lib/python3.13/site-packages/flask/json/provider.py:179: in dumps
    return json.dumps(obj, **kwargs)
/usr/lib/python3.13/json/__init__.py:238: in dumps
    **kw).encode(obj)
/usr/lib/python3.13/json/encoder.py:200: in encode
    chunks = self.iterencode(o, _one_shot=True)
/usr/lib/python3.13/json/encoder.py:261: in iterencode
    return _iterencode(o, 0)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

o = <LocalProxy unbound>

    def _default(o: t.Any) -> t.Any:
        if isinstance(o, date):
            return http_date(o)
    
        if isinstance(o, (decimal.Decimal, uuid.UUID)):
            return str(o)
    
        if dataclasses and dataclasses.is_dataclass(o):
            return dataclasses.asdict(o)
    
        if hasattr(o, "__html__"):
            return str(o.__html__())
    
>       raise TypeError(f"Object of type {type(o).__name__} is not JSON serializable")
E       TypeError: Object of type LocalProxy is not JSON serializable

.tox/py313-flask/lib/python3.13/site-packages/flask/json/provider.py:121: TypeError
___________________ JWTAccessTokenResourceServerTest.test_extra_attributes ___________________

self = <tests.flask.test_oauth2.test_jwt_access_token.JWTAccessTokenResourceServerTest testMethod=test_extra_attributes>

    def test_extra_attributes(self):
        '''
        Authorization servers MAY return arbitrary attributes not defined in any
        existing specification, as long as the corresponding claim names are collision
        resistant or the access tokens are meant to be used only within a private
        subsystem. Please refer to Sections 4.2 and 4.3 of [RFC7519] for details.
        '''
    
        self.claims['email'] = 'user@example.org'
        access_token = create_access_token(self.claims, self.jwks)
    
        headers = {'Authorization': f'Bearer {access_token}'}
>       rv = self.client.get('/protected', headers=headers)

tests/flask/test_oauth2/test_jwt_access_token.py:571: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.tox/py313-flask/lib/python3.13/site-packages/werkzeug/test.py:1162: in get
    return self.open(*args, **kw)
.tox/py313-flask/lib/python3.13/site-packages/flask/testing.py:235: in open
    response = super().open(
.tox/py313-flask/lib/python3.13/site-packages/werkzeug/test.py:1116: in open
    response_parts = self.run_wsgi_app(request.environ, buffered=buffered)
.tox/py313-flask/lib/python3.13/site-packages/werkzeug/test.py:988: in run_wsgi_app
    rv = run_wsgi_app(self.application, environ, buffered=buffered)
.tox/py313-flask/lib/python3.13/site-packages/werkzeug/test.py:1264: in run_wsgi_app
    app_rv = app(environ, start_response)
.tox/py313-flask/lib/python3.13/site-packages/flask/app.py:1498: in __call__
    return self.wsgi_app(environ, start_response)
.tox/py313-flask/lib/python3.13/site-packages/flask/app.py:1476: in wsgi_app
    response = self.handle_exception(e)
.tox/py313-flask/lib/python3.13/site-packages/flask/app.py:1473: in wsgi_app
    response = self.full_dispatch_request()
.tox/py313-flask/lib/python3.13/site-packages/flask/app.py:882: in full_dispatch_request
    rv = self.handle_user_exception(e)
.tox/py313-flask/lib/python3.13/site-packages/flask/app.py:880: in full_dispatch_request
    rv = self.dispatch_request()
.tox/py313-flask/lib/python3.13/site-packages/flask/app.py:865: in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)  # type: ignore[no-any-return]
authlib/integrations/flask_oauth2/resource_protector.py:105: in decorated
    return f(*args, **kwargs)
tests/flask/test_oauth2/test_jwt_access_token.py:52: in protected
    return jsonify(id=user.id, username=user.username, token=current_token)
.tox/py313-flask/lib/python3.13/site-packages/flask/json/__init__.py:170: in jsonify
    return current_app.json.response(*args, **kwargs)  # type: ignore[return-value]
.tox/py313-flask/lib/python3.13/site-packages/flask/json/provider.py:214: in response
    f"{self.dumps(obj, **dump_args)}\n", mimetype=self.mimetype
.tox/py313-flask/lib/python3.13/site-packages/flask/json/provider.py:179: in dumps
    return json.dumps(obj, **kwargs)
/usr/lib/python3.13/json/__init__.py:238: in dumps
    **kw).encode(obj)
/usr/lib/python3.13/json/encoder.py:200: in encode
    chunks = self.iterencode(o, _one_shot=True)
/usr/lib/python3.13/json/encoder.py:261: in iterencode
    return _iterencode(o, 0)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

o = <LocalProxy unbound>

    def _default(o: t.Any) -> t.Any:
        if isinstance(o, date):
            return http_date(o)
    
        if isinstance(o, (decimal.Decimal, uuid.UUID)):
            return str(o)
    
        if dataclasses and dataclasses.is_dataclass(o):
            return dataclasses.asdict(o)
    
        if hasattr(o, "__html__"):
            return str(o.__html__())
    
>       raise TypeError(f"Object of type {type(o).__name__} is not JSON serializable")
E       TypeError: Object of type LocalProxy is not JSON serializable

.tox/py313-flask/lib/python3.13/site-packages/flask/json/provider.py:121: TypeError
__________________ JWTAccessTokenResourceServerTest.test_scope_restriction ___________________

self = <tests.flask.test_oauth2.test_jwt_access_token.JWTAccessTokenResourceServerTest testMethod=test_scope_restriction>

    def test_scope_restriction(self):
        '''
        If an authorization request includes a scope parameter, the corresponding
        issued JWT access token SHOULD include a 'scope' claim as defined in Section
        4.2 of [RFC8693]. All the individual scope strings in the 'scope' claim MUST
        have meaning for the resources indicated in the 'aud' claim. See Section 5 for
        more considerations about the relationship between scope strings and resources
        indicated by the 'aud' claim.
        '''
    
        self.claims['scope'] = ['invalid-scope']
        access_token = create_access_token(self.claims, self.jwks)
    
        headers = {'Authorization': f'Bearer {access_token}'}
>       rv = self.client.get('/protected', headers=headers)

tests/flask/test_oauth2/test_jwt_access_token.py:522: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.tox/py313-flask/lib/python3.13/site-packages/werkzeug/test.py:1162: in get
    return self.open(*args, **kw)
.tox/py313-flask/lib/python3.13/site-packages/flask/testing.py:235: in open
    response = super().open(
.tox/py313-flask/lib/python3.13/site-packages/werkzeug/test.py:1116: in open
    response_parts = self.run_wsgi_app(request.environ, buffered=buffered)
.tox/py313-flask/lib/python3.13/site-packages/werkzeug/test.py:988: in run_wsgi_app
    rv = run_wsgi_app(self.application, environ, buffered=buffered)
.tox/py313-flask/lib/python3.13/site-packages/werkzeug/test.py:1264: in run_wsgi_app
    app_rv = app(environ, start_response)
.tox/py313-flask/lib/python3.13/site-packages/flask/app.py:1498: in __call__
    return self.wsgi_app(environ, start_response)
.tox/py313-flask/lib/python3.13/site-packages/flask/app.py:1476: in wsgi_app
    response = self.handle_exception(e)
.tox/py313-flask/lib/python3.13/site-packages/flask/app.py:1473: in wsgi_app
    response = self.full_dispatch_request()
.tox/py313-flask/lib/python3.13/site-packages/flask/app.py:882: in full_dispatch_request
    rv = self.handle_user_exception(e)
.tox/py313-flask/lib/python3.13/site-packages/flask/app.py:880: in full_dispatch_request
    rv = self.dispatch_request()
.tox/py313-flask/lib/python3.13/site-packages/flask/app.py:865: in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)  # type: ignore[no-any-return]
authlib/integrations/flask_oauth2/resource_protector.py:105: in decorated
    return f(*args, **kwargs)
tests/flask/test_oauth2/test_jwt_access_token.py:52: in protected
    return jsonify(id=user.id, username=user.username, token=current_token)
.tox/py313-flask/lib/python3.13/site-packages/flask/json/__init__.py:170: in jsonify
    return current_app.json.response(*args, **kwargs)  # type: ignore[return-value]
.tox/py313-flask/lib/python3.13/site-packages/flask/json/provider.py:214: in response
    f"{self.dumps(obj, **dump_args)}\n", mimetype=self.mimetype
.tox/py313-flask/lib/python3.13/site-packages/flask/json/provider.py:179: in dumps
    return json.dumps(obj, **kwargs)
/usr/lib/python3.13/json/__init__.py:238: in dumps
    **kw).encode(obj)
/usr/lib/python3.13/json/encoder.py:200: in encode
    chunks = self.iterencode(o, _one_shot=True)
/usr/lib/python3.13/json/encoder.py:261: in iterencode
    return _iterencode(o, 0)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

o = <LocalProxy unbound>

    def _default(o: t.Any) -> t.Any:
        if isinstance(o, date):
            return http_date(o)
    
        if isinstance(o, (decimal.Decimal, uuid.UUID)):
            return str(o)
    
        if dataclasses and dataclasses.is_dataclass(o):
            return dataclasses.asdict(o)
    
        if hasattr(o, "__html__"):
            return str(o.__html__())
    
>       raise TypeError(f"Object of type {type(o).__name__} is not JSON serializable")
E       TypeError: Object of type LocalProxy is not JSON serializable

.tox/py313-flask/lib/python3.13/site-packages/flask/json/provider.py:121: TypeError
_________________________ JWTAccessTokenResourceServerTest.test_typ __________________________

self = <tests.flask.test_oauth2.test_jwt_access_token.JWTAccessTokenResourceServerTest testMethod=test_typ>

    def test_typ(self):
        '''
        The resource server MUST verify that the 'typ' header value is 'at+jwt' or
        'application/at+jwt' and reject tokens carrying any other value.
        '''
        access_token = create_access_token(self.claims, self.jwks, typ='at+jwt')
    
        headers = {'Authorization': f'Bearer {access_token}'}
>       rv = self.client.get('/protected', headers=headers)

tests/flask/test_oauth2/test_jwt_access_token.py:432: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.tox/py313-flask/lib/python3.13/site-packages/werkzeug/test.py:1162: in get
    return self.open(*args, **kw)
.tox/py313-flask/lib/python3.13/site-packages/flask/testing.py:235: in open
    response = super().open(
.tox/py313-flask/lib/python3.13/site-packages/werkzeug/test.py:1116: in open
    response_parts = self.run_wsgi_app(request.environ, buffered=buffered)
.tox/py313-flask/lib/python3.13/site-packages/werkzeug/test.py:988: in run_wsgi_app
    rv = run_wsgi_app(self.application, environ, buffered=buffered)
.tox/py313-flask/lib/python3.13/site-packages/werkzeug/test.py:1264: in run_wsgi_app
    app_rv = app(environ, start_response)
.tox/py313-flask/lib/python3.13/site-packages/flask/app.py:1498: in __call__
    return self.wsgi_app(environ, start_response)
.tox/py313-flask/lib/python3.13/site-packages/flask/app.py:1476: in wsgi_app
    response = self.handle_exception(e)
.tox/py313-flask/lib/python3.13/site-packages/flask/app.py:1473: in wsgi_app
    response = self.full_dispatch_request()
.tox/py313-flask/lib/python3.13/site-packages/flask/app.py:882: in full_dispatch_request
    rv = self.handle_user_exception(e)
.tox/py313-flask/lib/python3.13/site-packages/flask/app.py:880: in full_dispatch_request
    rv = self.dispatch_request()
.tox/py313-flask/lib/python3.13/site-packages/flask/app.py:865: in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)  # type: ignore[no-any-return]
authlib/integrations/flask_oauth2/resource_protector.py:105: in decorated
    return f(*args, **kwargs)
tests/flask/test_oauth2/test_jwt_access_token.py:52: in protected
    return jsonify(id=user.id, username=user.username, token=current_token)
.tox/py313-flask/lib/python3.13/site-packages/flask/json/__init__.py:170: in jsonify
    return current_app.json.response(*args, **kwargs)  # type: ignore[return-value]
.tox/py313-flask/lib/python3.13/site-packages/flask/json/provider.py:214: in response
    f"{self.dumps(obj, **dump_args)}\n", mimetype=self.mimetype
.tox/py313-flask/lib/python3.13/site-packages/flask/json/provider.py:179: in dumps
    return json.dumps(obj, **kwargs)
/usr/lib/python3.13/json/__init__.py:238: in dumps
    **kw).encode(obj)
/usr/lib/python3.13/json/encoder.py:200: in encode
    chunks = self.iterencode(o, _one_shot=True)
/usr/lib/python3.13/json/encoder.py:261: in iterencode
    return _iterencode(o, 0)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

o = <LocalProxy unbound>

    def _default(o: t.Any) -> t.Any:
        if isinstance(o, date):
            return http_date(o)
    
        if isinstance(o, (decimal.Decimal, uuid.UUID)):
            return str(o)
    
        if dataclasses and dataclasses.is_dataclass(o):
            return dataclasses.asdict(o)
    
        if hasattr(o, "__html__"):
            return str(o.__html__())
    
>       raise TypeError(f"Object of type {type(o).__name__} is not JSON serializable")
E       TypeError: Object of type LocalProxy is not JSON serializable

.tox/py313-flask/lib/python3.13/site-packages/flask/json/provider.py:121: TypeError
================================== short test summary info ===================================
FAILED tests/flask/test_oauth2/test_jwt_access_token.py::JWTAccessTokenResourceServerTest::test_access_resource - TypeError: Object of type LocalProxy is not JSON serializable
FAILED tests/flask/test_oauth2/test_jwt_access_token.py::JWTAccessTokenResourceServerTest::test_entitlements_restriction - TypeError: Object of type LocalProxy is not JSON serializable
FAILED tests/flask/test_oauth2/test_jwt_access_token.py::JWTAccessTokenResourceServerTest::test_extra_attributes - TypeError: Object of type LocalProxy is not JSON serializable
FAILED tests/flask/test_oauth2/test_jwt_access_token.py::JWTAccessTokenResourceServerTest::test_scope_restriction - TypeError: Object of type LocalProxy is not JSON serializable
FAILED tests/flask/test_oauth2/test_jwt_access_token.py::JWTAccessTokenResourceServerTest::test_typ - TypeError: Object of type LocalProxy is not JSON serializable
========================== 5 failed, 206 passed, 1 skipped in 8.83s ==========================
py313-flask: exit 1 (9.48 seconds) /home/stefanor/git/upstream/authlib> coverage run --source=authlib -p -m pytest tests/flask pid=4118205
  py313-flask: FAIL code 1 (13.23=setup[3.74]+cmd[9.48] seconds)
  evaluation failed :( (13.34 seconds)
```

**To Reproduce**

A minimal example to reproduce the behavior:

```
tox -e py313-flask
```

**Expected behavior**

A clear and concise description of what you expected to happen.

**Environment:**

 - OS: Debian unstable
 - Python Version: 3.1.13rc2
 - Authlib Version: 904d66bebd79bf39fb8814353a22bab7d3e092c4

**Additional context**

Add any other context about the problem here.



Discussion / hints:
This seems Flask's issue.
> This seems Flask's issue.

I don't think flask agrees. See https://github.com/pallets/flask/issues/4609

https://github.com/lepture/authlib/blob/904d66bebd79bf39fb8814353a22bab7d3e092c4/authlib/integrations/flask_oauth2/resource_protector.py#L114

`current_token` is the werkzeug `LocalProxy` that flask is failing to JSON encode. It looks like it should be derefenced before returning it in:

https://github.com/lepture/authlib/blob/904d66bebd79bf39fb8814353a22bab7d3e092c4/tests/flask/test_oauth2/test_jwt_access_token.py#L76
@stefanor can you fix it? A PR is welcome.

Fix patch:
diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml
index 69e51671..24e91550 100644
--- a/.github/workflows/python.yml
+++ b/.github/workflows/python.yml
@@ -24,12 +24,11 @@ jobs:
       max-parallel: 3
       matrix:
         python:
-        - version: "3.8"
         - version: "3.9"
         - version: "3.10"
         - version: "3.11"
         - version: "3.12"
-        - version: "pypy3.8"
+        - version: "3.13"
         - version: "pypy3.9"
         - version: "pypy3.10"
 
diff --git a/tox.ini b/tox.ini
index fee918fa..957abcd5 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,8 +1,8 @@
 [tox]
 isolated_build = True
 envlist =
-    py{38,39,310,311,312,py38,py39,py310}
-    py{38,39,310,311,312,py38,py39,py310}-{clients,flask,django,jose}
+    py{39,310,311,312,313,py39,py310}
+    py{39,310,311,312,313,py39,py310}-{clients,flask,django,jose}
     coverage
 
 [testenv]


Test patch:
diff --git a/tests/flask/test_oauth2/test_jwt_access_token.py b/tests/flask/test_oauth2/test_jwt_access_token.py
index f4b8cf99..20feb1bb 100644
--- a/tests/flask/test_oauth2/test_jwt_access_token.py
+++ b/tests/flask/test_oauth2/test_jwt_access_token.py
@@ -49,31 +49,51 @@ def create_resource_protector(app, validator):
     @require_oauth()
     def protected():
         user = db.session.get(User, current_token['sub'])
-        return jsonify(id=user.id, username=user.username, token=current_token)
+        return jsonify(
+            id=user.id,
+            username=user.username,
+            token=current_token._get_current_object(),
+        )
 
     @app.route('/protected-by-scope')
     @require_oauth('profile')
     def protected_by_scope():
         user = db.session.get(User, current_token['sub'])
-        return jsonify(id=user.id, username=user.username, token=current_token)
+        return jsonify(
+            id=user.id,
+            username=user.username,
+            token=current_token._get_current_object(),
+        )
 
     @app.route('/protected-by-groups')
     @require_oauth(groups=['admins'])
     def protected_by_groups():
         user = db.session.get(User, current_token['sub'])
-        return jsonify(id=user.id, username=user.username, token=current_token)
+        return jsonify(
+            id=user.id,
+            username=user.username,
+            token=current_token._get_current_object(),
+        )
 
     @app.route('/protected-by-roles')
     @require_oauth(roles=['student'])
     def protected_by_roles():
         user = db.session.get(User, current_token['sub'])
-        return jsonify(id=user.id, username=user.username, token=current_token)
+        return jsonify(
+            id=user.id,
+            username=user.username,
+            token=current_token._get_current_object(),
+        )
 
     @app.route('/protected-by-entitlements')
     @require_oauth(entitlements=['captain'])
     def protected_by_entitlements():
         user = db.session.get(User, current_token['sub'])
-        return jsonify(id=user.id, username=user.username, token=current_token)
+        return jsonify(
+            id=user.id,
+            username=user.username,
+            token=current_token._get_current_object(),
+        )
 
     return require_oauth