-
-
Notifications
You must be signed in to change notification settings - Fork 328
-
Hi,
I hope this is the right place to ask this. Else I can also open an issue.
I am currently evaluating IDOM for a personal project and I like it a lot. You already have some nice examples helping me understand how to implement things with IDOM. One important aspect of my project is missing though:
What is the recommended way of doing authentication with IDOM?
The central problem to solve is how to establish a secure session over the websockets connection that is persistent across reloads.
A secondary objective is to achieve that with as little Javascript as possible.
I already scanned all discussions for solutions and what I found was either doing auth the "traditional" way and then redirecting to a page with embedded IDOM or doing auth completely inside IDOM and losing the session on reload. Both is not great. Maybe I missed something but I could not find a definitive recommendation.
I think I might have found a pattern how to do auth in IDOM via server-side sessions:
Implement a request middleware that sets a session cookie on the first response serving the initial HTML. That ensures the websockets connection always gets a valid session ID via Cookie header. Then with use_request we can extract the session ID inside IDOM, retrieve the session data, check authentication info and either display a login form or the restricted page.
I propose that pattern as recommended way of doing auth with IDOM since it is secure and easy to implement.
I already implemented a full example for Sanic (session and user persistence not included). In the README there is also a more in-depth explanation of my reasoning.
I am looking forward to get feedback from you people on this approach 🙂
Beta Was this translation helpful? Give feedback.
All reactions
Replies: 4 comments 16 replies
-
I do agree we could benefit from some pre-baked authentication helpers support for our supported frameworks. Would make embedding IDOM easier, especially when used in conjunction with #653
Beta Was this translation helpful? Give feedback.
All reactions
-
sqlite is standard lib, so I'd think it's an okay backend for frameworks without an ORM.
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 1
-
Definitely something we can provide out of the box, but sqlite has similar limitations if you deploy multiple instances.
Beta Was this translation helpful? Give feedback.
All reactions
-
Your suggestion of storing session keys in-memory is unfortunately not feasible though. Most people will use IDOM alongside multiprocessing ASGI webservers. And unfortunately there is currently no multiprocessing safe way of sharing memory within Python. I recall seeing a PEP open for this but it hasn't made significant progress in quite some time.
I think there is a misunderstanding. I do not suggest to put sessions in memory in the real world. I just did not want to complicate the example even more. I point out in the code and also in the README that it should be put in DB or a KV store.
About the middleware: It is important that the session ID is always set. Even when the session data is empty. I do not know about Django's built-in SessionMiddleware, but I had to trick sanic_session into always setting the cookie by adding some meaningless data to the session data.
We can certainly add some sort of session middleware for any frameworks with native ORM support though. Or perhaps, build the session middleware around popular third-party ORMs. But no matter the case, we'd need some form of persistent storage.
Maybe this is more a documentation issue. You could document how to integrate each framework's most popular server-side session framework with IDOM. For Sanic there is the already mentioned sanic_session. For Flask there is flask-session(Flask's built-in session works with signed client-side cookies). For FastAPI I could only find fastapi-session, but it seems abandoned. And for Tornado I only could find tornado-sessions, which is also abandoned.
So both of you are fine with the "always set session ID on initial response and use server-side session" approach"?
There is also other approaches like JWT and and signed client-side cookies, but I find them to be somewhat incompatible with IDOM.
Beta Was this translation helpful? Give feedback.
All reactions
-
So both of you are fine with the "always set session ID on initial response and use server-side session" approach?
The fact that everything is managed server-side does seem to fit well with IDOM. With that said, it's been a bit since I've had to implement lower level auth machinery so I'd have to do some research to be sure.
Beta Was this translation helpful? Give feedback.
All reactions
-
The fact that everything is managed server-side does seem to fit well with IDOM.
I thought that too.
With that said, it's been a bit since I've had to implement lower level auth machinery so I'd have to do some research to be sure.
Yes, me too. I did some research for the last two days after work. And because I found out that not all common approaches are equally secure or compatible with IDOM I was motivated to write up an example that might help others.
Should you come to the same conclusion as I did, then a quick-win might be to add a high-level "how-to do auth" to the docs so others do not have to re-do the research.
Adding your own session middleware (where necessary) might be good next step in my opinion. I would deliberately carve out persistence and define an interface via abc or protocol. Maybe implement some reference adapters for the most popular solutions like Sqlalchemy, Django ORM and Redis, but that really depends on your time and willingness to maintain these.
Beta Was this translation helpful? Give feedback.
All reactions
-
I've created a very rough draft of what session management might look like in IDOM: #771
I don't think I'm satisfied with the interface, but it should get the idea across and could be used as a starting point for anyone interested in contributing this feature.
Beta Was this translation helpful? Give feedback.
All reactions
-
Given that I'm unlikely to find the time to implement this any time soon (we should about adding more to the 1.0 roadmap) the best option is to, as @phihos has said, write up documentation on how to do this for each builtin backend. I'm unsure whether we should have the documentation written for a 1.0 release though. I'm learning towards thinking we should since this seems like something that would come up pretty quickly for any new adopters.
Beta Was this translation helpful? Give feedback.
All reactions
-
@rmorshea, @phihos I really appreciate your efforts to show session / auth management.
I am trying to put together a similar example using OAuth but am struggling to cover cases for an implicit grant where the token is given via a hash-param. eg. https://example.com/oauth_callback?state=foo#access_token=1234.
My current approach was to have the redirect endpoint (the endpoint you return to from the oauth service), return JS script which would set a cookie and redirect the client to an endpoint serving the app. eg.
GET -> /
(no cookie found from use_context)
<- 302 oauthservice redirect = /oauth
GET -> /oauth#access_token=1234
<- 200
return html.script('''
() => {
var hash = window.location.hash;
var params = hash.substring(1);
params.split("&").forEach((item) => {
const [key, value] = item.split("=");
if (key === "access_token") {
document.cookie = "access_token=" + value;
}
});
window.location = "http://localhost:8080/app";
}
''')
GET -> /app (cookie included in request context)
However this is not working. I never see the cookie on the /app request.
The most likely issue is with the developer as I lack experience with cookies or browser interactive work :P
I saw a potential alt option to return JS which will make a request to the service and using your above session example set the values, but I worried a redirect would then drop the session if it were tied to the websocket session.
Im not sure which direction is best and was hoping you could provide some direction.
Options:
-
Maybe someway to get the hash param in the request to the IDOM endpoint?
-
Set a cookie on the client and see that cookie when initiating a websocket request?
-
Setup a secondary api call to set the token in some session state?
Im also struggling to split the boundary between what the server should do and what the browser should do.
I constantly have a case where I want to create small utility apps for work and need to provide a browser UI and often with authentication mechanisms. I typically create the UI in js-react and serve the service behavior via a RESTful API with either java or python, because I have a lot of existing libraries built in those languages.
I was hopeful to use this IDOM library to not have to manage two languages and two package managers. I could keep everything in python. But im not sure if I should be infusing the API calls with the document model and thus the 'API' part, or if I should be treating IDOM as a standalone SPA (single page application) and serve the API behavior via python but on a different port. Thus splitting the UI and backend behaviors. (which sounds like the logical choice, and still keeps a single language, but it add more 'things' to maintain, and the app is usually small enough to not worry about splitting or long term maintenance friendly abstraction of the UI from the API behavior.)
🙏🏻 I would appreciate your insights and direction for how to best use IDOM for this sort of case. (small SPa + API, and avoiding creating two projects to do it)
Beta Was this translation helpful? Give feedback.
All reactions
-
@tylerhjones, at a glance this looks like Option 3 from #828 which, as the issue explains, has potential security vulnerabilities. I think our present recommendation is Option 2, where you render and handle auth using a traditional login page before redirecting to your IDOM app. In the long-run we want to implement Option 4, however we don't have a timeline for that yet. Let me know if you have any questions about how to make Option 2 work for you. I know @Archmonger has used it before in his project Conreq.
Beta Was this translation helpful? Give feedback.
All reactions
-
Thanks, ill take another look at the Conreq project to see what im missing when setting a cookie client side and not via a set cookie resp header, else ill have to do the hash to query and call and endpoint just to give a response with a set cookie header of the oauth token.
Beta Was this translation helpful? Give feedback.
All reactions
-
Which is also a bit weird as API calls via oauth are supposed to be a bearer token in the authorization header, not a cookie.
Beta Was this translation helpful? Give feedback.
All reactions
-
To help out, I believe the version of Conreq using IDOM is on this branch: https://github.com/Archmonger/Conreq/tree/app_store
Beta Was this translation helpful? Give feedback.
All reactions
-
For Conreq, I use Django's traditional HTTP views for my login page. If the user attempts to access an IDOM component that requires authentication, then a html.script will redirect them to the login page.
So IDOM doesn't handle any auth, only redirection to traditional auth views.
Beta Was this translation helpful? Give feedback.