jellyfin / jellyfin-android

Android Client for Jellyfin
https://jellyfin.org
GNU General Public License v2.0
1.38k stars 230 forks source link

Support HTTP basic auth for reverse proxies #123

Open obbardc opened 3 years ago

obbardc commented 3 years ago

Describe the bug Using Traefik 2 setup as a HTTPS proxy with a http basic auth for all connections, Jellyfin for Android can't connect. This is the same with the Play store version and the latest RC released in this repository. The web interface works fine when using a browser.

To Reproduce

  1. Setup Traefik proxy with http basic auth
  2. Set hostname in Jellyfin app to the address
  3. Press connect
  4. See error "connection couldn't be established"

Expected behavior

The app to connect.

Logs

Screenshots

System (please complete the following information):

Additional context

nielsvanvelzen commented 3 years ago

We can't use the Authorization header for basic authentication because it's used for Jellyfin credentials. A solution I can think of is to use the Proxy-Authenticate header for this but I don't know if Traefik supports it and it will probably not work from within the webui.

obbardc commented 3 years ago

Right - the Traefik http basic auth is just another layer of added security. I expect to have to login twice. The Jellyfin webui works perfectly with separate logins, so in the first instance I'd like to replicate that on mobile app.

I'm currently on vacation, but Ill do some further digging once I am back at the desk ;-).

obbardc commented 3 years ago

For my reference later, here's some info about Android webview handling http basic auth: https://stackoverflow.com/questions/4220832/how-to-handle-basic-authentication-in-webview

obbardc commented 3 years ago

Here's the backtrace, it looks like the issue is with the Jellyfin java API rather than this app.

D/TimberLogger: Adding request to queue: https://jellyfin.<my_host>//System/Info/Public?format=json
E/Volley: [45297] BasicNetwork.performRequest: Unexpected response code 401 for https://jellyfin.<my_host>//System/Info/Public?format=json
E/TimberLogger: VolleyError com.android.volley.AuthFailureError: null
    com.android.volley.AuthFailureError
        at com.android.volley.toolbox.BasicNetwork.performRequest(BasicNetwork.java:195)
        at com.android.volley.NetworkDispatcher.processRequest(NetworkDispatcher.java:131)
        at com.android.volley.NetworkDispatcher.processRequest(NetworkDispatcher.java:111)
        at com.android.volley.NetworkDispatcher.run(NetworkDispatcher.java:90)
E/ContinuationResponse: org.jellyfin.apiclient.model.net.HttpException: VolleyError com.android.volley.AuthFailureError: 
        at org.jellyfin.apiclient.interaction.VolleyErrorListener.onErrorResponse(VolleyErrorListener.java:25)
        at com.android.volley.Request.deliverError(Request.java:617)
        at com.android.volley.ExecutorDelivery$ResponseDeliveryRunnable.run(ExecutorDelivery.java:104)
        at android.os.Handler.handleCallback(Handler.java:883)
        at android.os.Handler.dispatchMessage(Handler.java:100)
        at android.os.Looper.loop(Looper.java:214)
        at android.app.ActivityThread.main(ActivityThread.java:7682)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:516)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)
     Caused by: com.android.volley.AuthFailureError
        at com.android.volley.toolbox.BasicNetwork.performRequest(BasicNetwork.java:195)
        at com.android.volley.NetworkDispatcher.processRequest(NetworkDispatcher.java:131)
        at com.android.volley.NetworkDispatcher.processRequest(NetworkDispatcher.java:111)
        at com.android.volley.NetworkDispatcher.run(NetworkDispatcher.java:90)
mxvin commented 3 years ago

I experience similar issue on version 2.0.0-rc.5 - latest beta playstore, but I'm not using any HTTP Basics. Just Jelly behind nginx. Cannot connect to HTTPS (nginx) with app saying 'Connection cannot be established.....'. Turning off h2 have no effect, no log from Jelly and nginx so I suspect this may a problem on HTTPS implementation in the app. No problem if directly connect to Jellyfin via HTTP

nielsvanvelzen commented 3 years ago

@mxvin Please provide app logs

@obbardc the apiclient is currently ongoing a big refactor and it's behavior might change. The error from your logs basically says that no credentials were given when retrieving server info (which is caused by your proxy since the public endpoint doesn't require authentication at all and thus the apiclient won't provide them).

alexandre-abrioux commented 3 years ago

It would be great to be able to input the basic auth directly in the URL, like this: https://user:password@www.example.com/. I don't have any knowledge with Kotlin but looking at the code we would probably need to parse the URL before loading the webview: https://github.com/jellyfin/jellyfin-android/blob/2e6983c731139aad7458581fdb69e888e3ef6998/app/src/main/java/org/jellyfin/mobile/fragment/WebViewFragment.kt#L202 ; and handle auth with the onReceivedHttpAuthRequest listener.

nielsvanvelzen commented 3 years ago

Like I said before, The HTTP Basic Authentication Scheme uses the Authorization header in HTTP requests which is already used for Jellyfin account authorization. We can't add basic auth on top of that.

alexandre-abrioux commented 3 years ago

Thanks. I tried to get more info from my setup where it's actually working behind basic auth.

Jellyfin apparently allows the use of the X-Emby-Authorization header to pass the authentication token, see: https://github.com/jellyfin/jellyfin/blob/4a3411cad17c95f3afe0870a1ff3a9afc10286fa/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs#L211

The web-ui uses this header instead of the Authorization, see the Javascript client code: https://github.com/jellyfin/jellyfin-apiclient-javascript/blob/e2c51b2c67e73dfb1e736cbbfc84d73165f5968a/src/apiClient.js#L177

The Kodi plugin also started to use this header since this PR: https://github.com/jellyfin/jellyfin-kodi/pull/56/files

Is it something we could replicate in the Java client?

nielsvanvelzen commented 3 years ago

We have decided to use the Authorization header because it is a common standard. We will not change it to use the X-Emby-Authorization header.

alexandre-abrioux commented 3 years ago

I agree with that, but we could switch to it if we detect basic auth in the input URL, that could be a great compromise, what do you think?

KillerKelvUK commented 3 years ago

Sorry for the noob question but is this issue where the android app fails to connect because of traefik http basic auth being used or is there a general compatibility issue with traefik and the mobile app?

I ask as I'm just dipping my toe into traefik with jelly and am successfully reverse proxying to a public domain but the app on my OnePlus6 just won't connect. I'm not using http basic auth but I confuss I'm not very experienced so some of the details in this thread are a little beyond me at the moment.

obbardc commented 3 years ago

Sorry for the noob question but is this issue where the android app fails to connect because of traefik http basic auth being used or is there a general compatibility issue with traefik and the mobile app?

I ask as I'm just dipping my toe into traefik with jelly and am successfully reverse proxying to a public domain but the app on my OnePlus6 just won't connect. I'm not using http basic auth but I confuss I'm not very experienced so some of the details in this thread are a little beyond me at the moment.

the jellyfin app works fine with traefik. Suggest you should post your configuration files in full detail in their forum for more help - here isn't really the place to discuss that :-).

caillou commented 3 years ago

Would it be conceivable to use the same username and password for both, the reverse proxy and Jellyfin, and tell the reverse proxy to forward the Authorization header?

nielsvanvelzen commented 3 years ago

The Authorization header uses a custom scheme that is specific to Jellyfin so I don't think it's going to work in a proxy.

Maxr1998 commented 3 years ago

Specifically, Jellyfin uses Authorization: MediaBrowser <fields> (he fields contain info like the app id), whereas the proxy uses basic auth with Authorization: Basic <username:password encoded as base64>.

caillou commented 3 years ago

After some more digging on this, I have the impression the Proxy-Authorization header could be used to authenticate on the reverse-proxy, be it Nginx or Traefik. Would this be a possible route?

nielsvanvelzen commented 3 years ago

I think that might be an option. I proposed it earlier (https://github.com/jellyfin/jellyfin-android/issues/123#issuecomment-691633388). We need to add support for it in the SDK first and then expose the option in the UI.

caillou commented 3 years ago

One more question: would it be feasible to use Jellyfin as an authentication backend for Nginx or Traefik?

Could the reverse-proxy use Jellyfin's Authorization header scheme, and validate it against the Jellyfin server? Ideally, this would be transparent to the client.

nielsvanvelzen commented 3 years ago

It might be possible to do with a plugin for nginx/traefik by calling the authenticateByName endpoint with a username+password and if a token is returned the authentication was successful. A bit out of the scope for this issue though. Feel free to join our Matrix channels if you need help with the api.

3c7 commented 3 years ago

Would love to see Basic Auth compatibility with the Android app. I'm using an Nginx reverse proxy with Basic Auth in order to prevent direct exposition of JellyFin to the internet.

Is the use of Authorization header instead of X-Emby-Authorization a general concept which will also be implemented in the WebUI?

jellyfin-bot commented 2 years ago

This issue has gone 120 days without comment. To avoid abandoned issues, it will be closed in 21 days if there are no new comments.

If you're the original submitter of this issue, please comment confirming if this issue still affects you in the latest release or master branch, or close the issue if it has been fixed. If you're another user also affected by this bug, please comment confirming so. Either action will remove the stale label.

This bot exists to prevent issues from becoming stale and forgotten. Jellyfin is always moving forward, and bugs are often fixed as side effects of other changes. We therefore ask that bug report authors remain vigilant about their issues to ensure they are closed if fixed, or re-confirmed - perhaps with fresh logs or reproduction examples - regularly. If you have any questions you can reach us on Matrix or Social Media.

anderspitman commented 10 months ago

FWIW, I believe the best solution for this would be for Jellyfin to implement header authentication, and for the clients to implement standard auth flows like OIDC. Most reverse proxies support this (search for "forward auth"). This gives the most flexibility in allowing users to choose an auth system that works for them, and frees Jellyfin from a lot of auth concerns.

That said, I think implementing X-Emby-Authorization is the most reasonable short-term solution. I totally agree with @nielsvanvelzen that standard headers should be preferred and Proxy-Authorization would be the correct choice, but it doesn't seem to be widely supported or at least documented. Temporarily supporting a custom header to enable users to improve their security seems like a worthwhile tradeoff to me.

Janhouse commented 6 months ago

I did some quick tests with forward auth and it seems like Jellyfin already randomly passes different headers for different requests. There is some info here

In order to make forward-auth work with Jellyfin in a good way and without making too many modifications on different clients, Jellyfin client should pass some type of identifier at all times. Or ideally - use the cookie store.

Here is why:

If Jellyfin clients enabled cookie store (not used right now), and allowed to follow redirects (also seems like media player doesn't follow redirects), forwardauth could be used to manage access. This is not basic auth, but it is much better than basic auth, since this would enable use of SSO for all the clients.

image

Simply enabling cookies and redirects don't do much without a custom forwardauth service, but that can be built (either as a 3rd party tool, or as part of Jellyfin server).

In order to tie clients to specific user, a timed pairing mechanism could be used. Similarly to Bluetooth or Zigbee pairing, where any session is allowed to pair when pairing mode is enabled. IP address/network match between client and the session enabling pairing would be required.

High level mock:

image

This can already be somewhat implemented without cookies by using existing headers (Authorization, X-Emby-Token, etc.) but the problem is that clients don't send those headers for all requests and unless user already has a session (from logging in before forwardauth was enabled), it won't be sent at all. IP address/network level checks could be used to allow connecting during initial pairing but it does not resolve the issue with public endpoints not sending the authorization headers afterwards (and we do not want to keep any resources exposed without forward-auth)

So the best way to enable this behavior would be usage of cookies and redirects (which are not enabled on clients yet).

Where does the SSO come into play? SSO can be used to grant access to pairing site.

Urogna commented 3 months ago

@Janhouse suggestion should get more attention

Janhouse commented 3 months ago

@Janhouse suggestion should get more attention

I actually implemented the custom forward-auth service for Jellyfin and the approach works fine for all the clients I tested with (android, android tv, ios, desktop).

hhftechnologies commented 1 week ago

@Janhouse can you explain it in detail please how did you do it.