twonote / solid-file-python

solid-file-python is a Python library for creating and managing files and folders in Solid pods. https://www.w3.org/community/solid/
https://www.w3.org/community/solid/
MIT License
22 stars 11 forks source link

[WIP] feat: Support OIDC login using solid-oidc-client #38

Open renyuneyun opened 11 months ago

renyuneyun commented 11 months ago

This PR aims to support Solid-OIDC authentication, using the solid-oidc-client library as mentioned in #33. Essentially, it provides a new class OidcAuth to handle that. The rest of the library should be used in the same way as before.

Discussion

Usage

from solid.auth import OidcAuth
from solid.solid_api import SolidAPI

auth = OidcAuth()
# This will print the URL, and listen to callback, and generate session, etc
auth.login(idp)

solid_api = SolidAPI(auth)

# Any request will be assigned correct headers
solid_api.fetch(...)

Dependency

The dependency is not added, as I'm not entirely sure which place should the modification be placed. I tested against NSS and CSS. The added dependencies are:

Login URL handling

At the moment, it will print out a URL in the terminal, and the user should separately visit that URL and perform auth on the browser. The library will receive the callback automatically, and perform further steps.

This is not flexible enough for library users. In particular, the URL (login_url) should be emitted to the caller of the login function, and the caller should determine how this URL is used, e.g. printing to console (for CLI app), or automatically redirecting to the URL (for browser app). I can think of two ways, and am not sure which is preferred:

  1. use yield to send out the URL, and the caller does for login_url in auth.login(idp): ....
  2. split the login function as two parts, login() and waitLogin, and require the caller to sequentially call them.

I can modify the code accordingly, after a decision is made.

Otto-AA commented 11 months ago

Hi, congrats for getting it to work together! :ok_hand:

Regarding the dependency / library question: I think it would make sense to not include flask and solid-oidc-client as dependencies, but rather let the library user create the auth class (or function) and let them pass it as a parameter to the SolidAPI constructor. This would be similar to how I implemented it for the solid-file-client in javascript, where the fetch method is passed to the constructor.

So the usage would be (pseudocode):

from solid_file_python import SolidAPI
import solid_oidc_client

# do all the login stuff (including opening the url, the oauth redirect, etc)
solid_oidc_client.login(...)
session = solid_oidc_client...

# create fetch/object to pass as parameter
def get_auth_headers(url, method):
    return session.get_auth_headers(url, method)

# create SolidAPI with the authentication method
solid_api = SolidAPI(get_auth_headers)

# now this internally uses the get_auth_headers method
solid_api.fetch(...)

The benefits of the separation would be:

The disadvantages:

An open question would be, how it should be passed in. As a method that creates headers, a class instance, etc.

renyuneyun commented 11 months ago

Hi @Otto-AA , thanks for the comments.

to not include flask and solid-oidc-client as dependencies

That makes sense. The README and code documentation should be updated accordingly to prompt the developer for that.

So the usage would be (pseudocode):

The current usage is similar to what you described, just in a different organization. See the updated main body for the code.

One possibility in being more flexible is to extend the parameters for the login() function (or the OidcAuth constructor):

def login(self, idp, external_callback=False):
    ... # prepare
    if external_callback:
        return login_url, self.on_login_finished  # Return the log-in URL, and a callback for storing the session after log-in finished
    ... # Start flask server and listen to callback

def on_login_finished(self, session):
    ... # store log-in finished state

Essentially, this provides a branch when caller wants to handle it separately. Otherwise, use the original/built-in mechanism.

peter0083 commented 11 months ago

thank you both @Otto-AA and @renyuneyun for the input. I'm tagging @hrchu here for a review.

hrchu commented 11 months ago

sorry for late reply, just come back from PyCon APAC'23. I will check it later.

hrchu commented 11 months ago

Nice work! I agree that this PR is not flexable enough for all scenarios, but at least we can address a few scenarios like let Python developers access Solid Pod with this PR. Here are some points I can think of to make it even better:

  1. Move related code to a new Python module, e.g., OidcAuth.py so users can choose not install flask and solid-oidc-client and also let test cases pass.
  2. Could you add some example code or documentation to provide further clarity this feature? (https://github.com/twonote/solid-file-python/blob/master/solid-file-python-getting-start.ipynb, https://github.com/twonote/solid-file-python/blob/master/README.md, or even a saperate doc.)
  3. How about add an option to allow users to manually pass the callback URL (including the code and state from the IDP) to the Auth class? This would make the Flask dependency optional and allow it to work in environments that cannot accept requests from the IDP. Be like:
    > auth = OidcAuth(callback_server=False)
    > auth.login(idp)
    Plz login via URL: ..... 
    Paste callback url here: ...
    > solid_api = SolidAPI(auth)
hrchu commented 11 months ago

Further thoughts about supporting OIDC login:

  1. Based on the currently Solid-OIDC spec, I believe client library implementations are hard to be flexible enough to cover all use cases out of box, especially for those scenarios without human or browser interaction. We need something like the OpenID Connect Client-Initiated Backchannel Authentication Flow - Core 1.0 (https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0.html) in the spec.
  2. Based on the above observation, I agree that solid-file-python should allow users to create their own authentication class for their specific use cases, as suggested by @Otto-AA.
  3. In my opinion, we still need some sort of convenient and out-of-the-box solution for develpers to enable Python users to smoothly adopt the evolution of Solid (which is why I contribute to this project). The work done by @renyuneyun in this PR is a nice step towards achieving that.