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

Migrating to Connexion 3: Issues with current_app and g for Authentication and Request Lifecycle #1880

Closed
oyeamit opened this issue Feb 13, 2024 · 4 comments · Fixed by #1883

Comments

@oyeamit
Copy link

oyeamit commented Feb 13, 2024

Description

I am currently in the process of migrating a Flask application to Connexion 3 and have encountered an issue related to the use of current_app and the g object, which I previously utilized extensively in my Connexion 2 FlaskApp setup. In the older version, I used to set several variables in g during the @before_request hook, and these variables were accessible throughout the lifespan of a request, including in my authentication methods and in methods decorated with @after_request.

This setup was crucial for maintaining request-scoped data, such as user authentication details and other contextual information needed across different parts of the application during the processing of a single request.

However, with the upgrade to Connexion 3, I am finding it challenging to replicate this behavior. The documentation and migration guides I've consulted so far do not provide clear instructions on how to achieve similar functionality with the updated framework version. Specifically, I am looking for guidance on:

  1. How to properly set and access request-scoped variables using g or an equivalent mechanism in Connexion 3.
  2. Ensuring that these variables are accessible in the authentication methods and throughout the request lifecycle, including @before_request and @after_request hooks.
  3. Any changes or updates in Connexion 3 that affect the use of current_app in the context of request handling and authentication.

Could you provide examples or guidance on how to adapt my existing FlaskApp code to work with Connexion 3, particularly focusing on the use of current_app and g for request-scoped data handling and authentication?

Output of the commands:

  • Python 3.10.9
  • Connexion 3.0.5
  • Flask 2.2.5
  • Werkzeug 2.3.8
@RobbeSneyders
Copy link
Member

Thanks @gaurav-triverus.

I submitted a PR which makes the request available to the security handlers. In combination with a custom before/after-request middleware, you should be able to implement this flow.

class CustomMiddleware:

    def __init__(self, next_app: ASGIApp):
        self.next_app = next_app

    def before_request(scope: Scope):
        # Code here. State can be stored on the scope.

    def after_request(scope: Scope):
        # Code here. State can be stored on the scope.

    async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
        self.before_request(scope)
        await self.app(scope, receive, send)

app = connexion.FlaskApp(__name__)
app.add_middleware(CustomMiddleware, position=connexion.middleware.MiddlewarePosition.BEFORE_SECURITY)

You can then access the state via request.scope in your security handler.

@gaurav-triverus
Copy link

Thanks @RobbeSneyders
Just to confirm, even after those changes flask g object is not accessible in security layer.
we will have to create a custom middleware to set variables to be used in throughout the lifespan?

@RobbeSneyders
Copy link
Member

Yes indeed @gaurav-triverus

g is only available within the context of the Flask application. Since Connexion 3's security functionality is performed by middleware wrapped around the Flask application, it's not possible to have access there.

@JoelPelaezP
Copy link

Hi, I have some issues implementing this custom Middleware.

this is my code:

`class CustomMiddleware:
def init(self, next_app):
self.next_app = next_app

def before_request(self, scope):
    print('test app  -> CustomMiddleware before_request')
    print("scope => " + str(scope))

def after_request(scope):
    print('test app  -> CustomMiddleware after_request')

async def __call__(self, scope, receive, send) -> None:
    print('test app -> CustomMiddleware __call__')
    self.before_request(scope)
    await self.next_app(scope, receive, send)

def get_project_root_dir() -> str:
return os.path.dirname(os.path.dirname(file))

def decode_jwt(token):
print('test app--> in decode_jwt')

decoded_token = jwt.decode(
    token,
    "secret",
    algorithms=["HS256"],
    options=dict(require_exp=True, require_sub=True),
)

return decoded_token

def hellow_world():
print('test app -->', 'in hellow_world ')
stores = [{"name": "local flask app", "description": "local flask app to test connexion "}]
return {"stores": stores}

def create_app():
print('test app --> in create_app')
options = SwaggerUIOptions(swagger_ui_path="/docs")
app = connexion.FlaskApp(
name, specification_dir=get_project_root_dir(), swagger_ui_options=options
)

app.add_api(
    'openapi.yaml',
    swagger_ui_options=options,
    strict_validation=True,
    validate_responses=True
)

app.add_middleware(CustomMiddleware, position=connexion.middleware.MiddlewarePosition.BEFORE_SECURITY)

flask_app = app.app

@flask_app.before_request
def push_db():
    print('test app --> in @flask_app.before_request')

@flask_app.teardown_request
def close_db(exception=None):
    print('test app --> in @flask_app.teardown_request')

@flask_app.after_request
def access_log_end(response):
    print('test app --> in @flask_app.after_request')
    return response

app.run(host = "0.0.0.0",
        port=5000,
        )

if name == "main":
create_app()`

when running the code and testing the endpoint hellowworld I have 1 issue:

  1. Printing steps in the code shows that before_request in customMiddleware is called 2 times with different value for scope param.

Results:

test app --> in create_app
test app -> CustomMiddleware call
test app -> CustomMiddleware before_request
scope => {'type': 'lifespan', 'asgi': {'version': '3.0', 'spec_version': '2.0'}, 'state': {}, 'app': <connexion.middleware.main.ConnexionMiddleware object at 0x7fd76318a100>}
test app -> CustomMiddleware call
test app -> CustomMiddleware before_request
scope => {'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.3'}, 'http_version': '1.1', 'server': ('172.22.0.2', 5000), 'client': ('172.22.0.1', 44230), 'scheme': 'http', 'root_path': '', 'headers': [(b'host', b'localhost:5000'), (b'connection', b'keep-alive'), (b'sec-ch-ua', b'"Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"'), (b'accept', b'application/json'), (b'sec-ch-ua-mobile', b'?0'), (b'authorization', b'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4ifQ.LmuLCvLRfzvDVYK_iUBmwL3-5K9N0QLFrHwhXVb5TPU'), (b'user-agent', b'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36'), (b'sec-ch-ua-platform', b'"Windows"'), (b'sec-fetch-site', b'same-origin'), (b'sec-fetch-mode', b'cors'), (b'sec-fetch-dest', b'empty'), (b'referer', b'http://localhost:5000/docs/'), (b'accept-encoding', b'gzip, deflate, br, zstd'), (b'accept-language', b'en-US,en;q=0.9,es-US;q=0.8,es;q=0.7'), (b'cookie', b'_ga=GA1.1.1667569712.1706132845; _ff={%22enableLocalizeVietnameseLanguage%22:false}; _ga_SW2TVH2WBY=GS1.1.1708531929.9.1.1708540204.0.0.0')], 'state': {}, 'method': 'GET', 'path': '/hellowworld', 'raw_path': b'/hellowworld', 'query_string': b'', 'app': <connexion.middleware.main.ConnexionMiddleware object at 0x7fd76318a100>, 'starlette.exception_handlers': ({<class 'starlette.exceptions.HTTPException'>: <function ExceptionMiddleware.http_exception at 0x7fd7634a0e50>, <class 'starlette.exceptions.WebSocketException'>: <bound method ExceptionMiddleware.websocket_exception of <connexion.middleware.exceptions.ExceptionMiddleware object at 0x7fd762d2c5b0>>, <class 'connexion.exceptions.ProblemException'>: <function ExceptionMiddleware.problem_handler at 0x7fd762d3ab80>, <class 'Exception'>: <function ExceptionMiddleware.common_error_handler at 0x7fd762d3ac10>}, {}), 'path_params': {}, 'extensions': {'connexion_routing': {'api_base_path': '', 'operation_id': 'app.hellow_world'}}}
test app--> in decode_jwt
test app --> in @flask_app.before_request
test app --> in hellow_world
test app --> in @flask_app.after_request
test app --> in @flask_app.teardown_request
INFO: 172.22.0.1:44230 - "GET /hellowworld HTTP/1.1" 200 OK

RobbeSneyders added a commit that referenced this issue Mar 20, 2024
Fixes #1881
Fixes #1880
Fixes #1876

Alternative to #1750

This PR makes the current request available to the security handlers by
injecting it as a keyword. I think this is a proper alternative to
#1750, since this is the only place in the default middleware stack
where I expect this to be needed.
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 a pull request may close this issue.

4 participants