starlette
starlette copied to clipboard
Cannot access POST data from AuthenticationBackend
Common use case: Retrieve POST data sent by an HTML login <form>
to validate within AuthenticationBackend
middleware.
- Passing usernames / passwords over GET with passwords visible in the URL is not acceptable.
- Passing Authorization headers along with a form requires Javascript, a poor solution.
Any common techniques or help appreciated.
One way I've worked around this temporarily is retrieving the form data upfront via middleware.
I feel this is not well documented (or intuitive?) for such a common use case and AuthenticationBackend should get the ability to access the form()
method on its own. Or some other solution perhaps, since multiple calls to form()
will hang. See https://github.com/encode/starlette/issues/495
from starlette.middleware.base import BaseHTTPMiddleware
class PostToState(BaseHTTPMiddleware):
"""
Pulls post / form data, loads into current request state.
"""
async def dispatch(self, request, call_next):
post = await request.form()
request.state.post = post
response = await call_next(request)
return response
Note to anyone using the middleware workaround: There's a sizeable performance hit for using await request.form()
on pages that do not need it. You can restrict this call to only pages which need to check POST data within the AuthenticationBackend
by including a check like this in your dispatch()
method of your PostToState middleware:
if request['path'] in ('/login', '/signup', '/password_reset'):
Again, would love a more permanent, performant solution from a core dev, but I'm not sure what the best approach would be.
Would love to hear of anyone else tackling the issue of retrieving POST data in your AuthenticationBackend.
Is this still an issue?
Yes, it's entirely related to #944 #1519
An Authentication check requires retrieving POST data.
Retrieving POST data requires consuming the stream.
You should be able to achieve the same thing with a pure ASGI middleware: https://github.com/encode/starlette/blob/master/docs/middleware.md#pure-asgi-middleware (these docs are brand new). Let me know if you need any further guidance.
Converted it over to pure ASGI awhile ago. The core issue is unfortunately unrelated.
I am beginning to feel like everyone uses Starlette for API's and not websites. :laughing: Being able to accept POST data from an HTML form for logins should be very typical.
To be fair, this is not a showstopping bug, just a poor developer experience.
Caching the stream using middleware works, its just not obvious for newcomers to do that. Other ASGI frameworks cache the stream and users will never encounter this.
You caught me red handed: I'm a backend/data engineer, I don't do much frontend. Apologies for misunderstanding your issue if I did.
The core issue is unfortunately unrelated.
Would you mind helping me understand what the core issue is? Does using the ASGI middleware not work, or is the issue now that this functionality (receiving auth info via a form) should be built in?
Being able to accept POST data from an HTML form for logins should be very typical.
Can you help me understand this a bit more? Typically, I have done this in a single handler (not middleware) which works like this:
- Load request body
- Validate authentication information
- If valid, set session
The middleware then checks session information but doesn't need to validate the form on every request? This is similar to how Django works by default, but it sounds like you want to validate this form data on every request? I'm not questioning what you're doing, just trying to understand it.
The desired UX here is:
:arrow_right: User visits page, clicks "login" modal in the page header.
:arrow_right: User POSTS using that modal <form>
page refresh (or AJAX).
:arrow_right: User is now logged in on that page.
The middleware then checks session information but doesn't need to validate the form on every request? This is similar to how Django works by default, but it sounds like you want to validate this form data on every request? I'm not questioning what you're doing, just trying to understand it
I mean yes, there could be a separate authentication page, but in an ideal world, we avoid the extra steps/redirects, and enable any page to perform Auth on POST.
Maybe this shouldn't be built in, but many other ASGI frameworks handle #944 and #1519 automatically - FastAPI and Sanic to name a few.
It's unfortunate that it's related to a longstanding issue that's been discussed to death without resolution. This probably will never be resolved until the related issues are.
Gonna go ahead and close this, but to people searching Github Issues for this DX problem, feel free to leave comments, ideas, discussion, code.
Do you mind if I keep this open?
No problem, I was just under the impression that Starlette wanted to get to 0 issues before 1.0, and this is only a DX issue that may never be resolved.
I don't like closing issues without resolution. Thanks for reopening it. 🙏
@gnat is this still an issue?
What's the proposal here? For AuthenticationMiddleware
to create a Request
|WebSocket
object instead of conn
?
After some thought, I understand the issue here, and I think it makes sense to change the AuthenticationMiddleware
in a backward compatible way to allow reading the body.
That said, it doesn't look like people are interested in this. So I'll be closing this, but if people get interested in this in the future, we can reopen it.
I'd also be happy to merge a PR to the documentation that shows how to do what is proposed here using a pure ASGI middleware.