paulgessinger / swift-paperless

Native iOS app for interacting with a Paperless-ngx installation to manage documents
https://swift-paperless.gessinger.dev/
MIT License
318 stars 15 forks source link

Support OIDC login #50

Open paulgessinger opened 8 months ago

paulgessinger commented 8 months ago

It's currently not possible to log in to a Paperless-ngx server behind a reverse-proxy login mechanism like Authelia or Authentik.

Possible solutions could be:

  1. One possible workaround is disabling the login checks for the /api and allow the app to retrieve and use a token bypassing the login service.
  2. The Less Paper app uses a method that detects redirects, presents a web view, waits for a login and subsequent login back to Paperless-ngx, and captures cookies in the process. The stored cookies then allow the app to communicate with the /api endpoint. This has the possible caveat of requiring the app to throw up a web view where the app has full access to the web content. I'm looking to support this in an opt-in rather than on-by-default way.

Please chime in if I missed anything!

Whipbread commented 7 months ago

I for one really like the Less Paper route. Please add this!

landmass-deftly-reptile-budget commented 7 months ago

Just for info, if you have this not on the radar: Version 2.5.0 was released and is now capable to do OIDC https://github.com/paperless-ngx/paperless-ngx/pull/5190

TheRealGramdalf commented 7 months ago

OIDC is by far the preferred method compared to proxy login. I would highly recommend supporting it instead of forward auth, since the latter is more of a hack and has several limitations.

leepfrog-ger commented 7 months ago

I've just set up SSO via Keycloak using paperless-ngx 2.5.0, works flawless. Would love to see that supported in the app!

paulgessinger commented 7 months ago

@TheRealGramdalf @leepfrog-ger, so as far as I can tell, this still only allows OIDC into paperless, not OAuth2 against Paperless, i.e. Paperless being a provider permitting login via OAuth2 to it.

This means that in this setup, the app still either would have to be able to supply a custom redirect URL (like swift-paperless://) to which the access token gets sent, or allow OAuth2 login into paperless and then permit access to /api via the OAuth2 token rather than the access token.

I might have missed something though.

TheRealGramdalf commented 7 months ago

The latter is what typically happens. OIDC (which is not the same as Oauth2 - oauth2 is the protocol (e.g. tcp), OIDC is a standard for transmitting data over that protocol (e.g http) I'm not sure how django-allauth implements tokens exactly, but i do know this much:

paulgessinger commented 7 months ago

I think what you're describing is correct, but the API token that the identity provider returns to the DA (paperless instance) is the auth token of the provider itself, ie google or authelia or whatever.

Paperless acts as a client here, and the app is not part of the flow at all, does not receive the upstream auth token, or get any access to the paperless API token at all.

Now if paperless worked as an IDP and the app would log into that, it could maybe use the returned token for further auth, but I don't think that's possible here.

paulgessinger commented 7 months ago

Also, if I want to intercept this in the clean way in the app, I would have to supply the redirect URL and initiate the redirect to the IDP.

But in the current OIDC flow, paperless initiates the redirect, and supplies some url as a callback redirect.

beposec commented 7 months ago

The URL recognition for paperless apps with OIDC login could be inspired by the Matrix protocol. With Matrix, the path of the OIDC provider (e.g. https://paperless.example.com/.well-known/paperless/oidc) is located on the domain in a .well-known directory.

https://areweoidcyet.com/client-implementation-guide/

paulgessinger commented 7 months ago

Ok I see, that's interesting.

In this case, the matrix home server delegates to some OIDC provider via well-known, and the app authenticates directly with that OIDC provider, and the home server then (not exactly clear to me how) recognizes that auth as valid.

I don't think this is implemented in paperless at this time, paperless acts as the client in the OIDC flow.

On top of this, the OIDC provider has to support dynamic client creation, which I believe neither Authelia nor Authentik currently do.

EDIT: I'm guessing the matrix home server just has some way to trust the scopes and claims from the OIDC provider's JWT, so the OIDC JWT can be used with the Matrix API. As far as I understand it, in paperless the web ui just completes the OIDC flow itself and issues cookies, discarding the OIDC JWT completely.

beposec commented 7 months ago

I'm not that deep into the subject. Maybe you don't have to implement the full matrix variant. But maybe you could use the well-known to recognise whether OIDC is active or not and then open the browser with the paperless URL?

leepfrog-ger commented 7 months ago

Another app that neatly integrates this and may serve as an example for an own implementation would be Immich with its mobile apps. It does make use of an app-specific redirect URI with the OIDC provider, see here: https://immich.app/docs/administration/oauth/

I however assume that it would would necessary to modify the web application (in this case paperless-ngx) to support this.

paulgessinger commented 7 months ago

I didn't consider the possibility of directly adding the app specific redirect uri to the upstream OIDC provider. In that case, I suppose the Immich API also trusts the OIDC token to authenticate the user.

That's the bit that's missing for Paperless-nix, if I got it right.

This could definitely work, but I'm not an expert in the backend development.

LucaTheHacker commented 6 months ago

I'd still consider the webview method, because that would also allow people using Proxy providers with Authentik to log in.

For an app like Paperless that may contain sensitive data, I'd definitely prefer to have "two step authentication" rather than hoping that the OAuth2 implementation isn't flawed.

xcojonny commented 6 months ago

The Immich app has this kind of feature and it works nicely.

https://immich.app/docs/overview/quick-start/

valentinp72 commented 3 months ago

Any news on this subject? For security reasons, I would really like to keep Paperless behind Authelia for 2FA.

ocean-haiyang commented 3 months ago

Any updates on this?

deviantintegral commented 3 months ago

Paperless now supports OpenID Connect which is even better than forward auth. I've been using it with Authentik since it was released and it's worked well.

I think this can probably be closed.

https://github.com/paperless-ngx/paperless-ngx/discussions/4006

paulgessinger commented 3 months ago

I think I'm not going to implement forward auto support, but I'm planning to allow you to sign in via OIDC. However, as far as I can tell, that requires some work in the backend, that I haven't had time get into so far.

Basically, my plan is to add a way for the backend to supply an auth token to the app via redirect, which will then allow me to use a web view to log in.

Whipbread commented 1 month ago

I think I'm not going to implement forward auto support, but I'm planning to allow you to sign in via OIDC. However, as far as I can tell, that requires some work in the backend, that I haven't had time get into so far.

Basically, my plan is to add a way for the backend to supply an auth token to the app via redirect, which will then allow me to use a web view to log in.

Do you have a timeline for this?

paulgessinger commented 1 month ago

@Whipbread not really. Realistically, maybe I can get something going before the end of the year.

However, in the upcoming version I'm reworking the login screen, and I'll add the ability to log in with a token directly (by logging in on the web and then pasting the token). That way, it will at least be possible to bypass the OIDC flow and log in anyway.

Whipbread commented 1 month ago

@Whipbread not really. Realistically, maybe I can get something going before the end of the year.

However, in the upcoming version I'm reworking the login screen, and I'll add the ability to log in with a token directly (by logging in on the web and then pasting the token). That way, it will at least be possible to bypass the OIDC flow and log in anyway.

@paulgessinger

Thank you for your reply. This is maybe enough for my needs for now. I'll be beta testing 👍

paulgessinger commented 3 weeks ago

@hendrik1120 points out that https://docs.allauth.org/en/latest/headless/index.html exists, which could potentially alleviate the need for a custom token transport mechanism in Paperless-ngx.

paulgessinger commented 3 weeks ago

@hendrik1120 I think the provider token workflow in allauth headless could be what I've been looking for. I haven't fully understood if that would require registering the app as a separate client in the OIDC provider next to the actual Paperless-ngx backend.

hendrik1120 commented 3 weeks ago

@paulgessinger Yes, I was also looking at the Provider Token Endpoint. Since MFA is currently one of the most requested features for paperless using the complete headless api might be also be an option to future-proof the app.

If I remember correctly, the current auth situation in paperless is a complete mess as the Angular frontend still relies on the standard django login with allauth mixed in and authenticates against the drf using the session cookie.

Django-allauth headless would be imo the best solution moving forward for both the SPA and all apps. No, it's currently not enabled by default, but can be very easily by adding it to INSTALLED_APPS which is exposed using an ENV and by adding the urls in urls.py. As the app will handle the oidc flow itself, it would require as all other apps a registered oidc public client from the oidc provider. I will try to dig up some examples.

paulgessinger commented 3 weeks ago

@hendrik1120 I talked to the Paperless-ngx folks, and they don't seem overly interested in implementing this (not enough perceived benefit over existing solution).

Unfortunately, I'm not experienced with Django development, so not sure how effective I'd be at implementing this myself.

Do you have Django experience? I'd be open to try to collaborate on this.

hendrik1120 commented 3 weeks ago

@paulgessinger Sure, on their side it's a two line change. See https://github.com/hendrik1120/paperless-ngx/tree/allauth_headless which enables the _allauth urls. I am currently building you a docker image, but cross building for amd64 takes forever. If you already have a paperless dev containter up and running, it might be easier to just change the two files and restart the container. I will update this post with the image once its ready... Edit: Multiplatform image available here: hendrikunraid/paperless-ngx_headless:latest

hendrik1120 commented 3 weeks ago

Bad news, after a few hours of trying to use the _allauth/{client}/v1/auth/provider/tokenendpoint I ended up with this error message: Provider does not support token authentication. Which in my opinion means, that the openid_connect provider as currently implemented in django allauth does not support token authentication. When querying the _allauth/app/v1/config there are also no available providers listed. Changing the client to browser shows the provider.

Confirmed by: "OpenID support at headless is still a TODO." from here. The changes in Paperless required to make this work are still very minimal. The two line change I mentioned before is not quite enough, since this will only provide you with a session_token. It is however fully supported to override this behavior to return an api token instead.

I also have a full openid connect flow written in python with PKCE for testing. I bet that there are much better libraries for swift which will work out of the box.