Closed obbardc closed 1 month 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.
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 ;-).
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
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)
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
@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).
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.
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.
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?
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.
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?
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.
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 :-).
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?
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.
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>
.
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?
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.
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.
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.
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?
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.
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.
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.
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:
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.
@Janhouse suggestion should get more attention
@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).
@Janhouse can you explain it in detail please how did you do it.
Any update on this issue?
Great idea with forward_auth
. It can be used to easily integrate additional auth mechanisms. For example, I'm using it in Caddy along with oauth2-proxy
to add Google auth.
example.com {
handle /oauth2/* {
reverse_proxy oauth2-proxy:4180
}
handle {
forward_auth oauth2-proxy:4180 {
uri /oauth2/auth?allowed_emails=example.user1@gmail.com,example.user2@gmail.com
}
reverse_proxy jellyfin:8096
}
}
This works great for browsers, unfortunately, most other Jellyfin apps can't do OAuth. We can add some basic auth to support those, detecting the header and swapping the auth method:
example.com {
# Determine if we have basic auth
@basic_auth header Authorization "Basic *"
handle /oauth2/* {
reverse_proxy oauth2-proxy:4180
}
handle {
# Use basic auth if we have a basic auth header
handle @basic_auth {
basic_auth {
media $2a$...
}
}
# Use OAuth
handle {
forward_auth oauth2-proxy:4180 {
uri /oauth2/auth?allowed_emails=example.user1@gmail.com,example.user2@gmail.com
}
}
reverse_proxy jellyfin:8096
}
}
Now if basic auth is provided, it will use it. This gets Kodi working and any other app that uses the X-Emby-Authorization
header. Unfortunately, it won't work for apps that use the Authorization header, like Android.
We can solve this by getting creative and putting the basic auth header on the URL and mapping it back in the reverse proxy.
Authorization
header to the X-Emby-Authorization
header/basic/<base64(username:password)/...rest of the URL...
Authorization
headerCurrently, Jellyfin checks X-Emby-Authorization
before Authorization
, but if that gets swapped at some point it will be necessary to either delete the Authorization
header or map the original to a temp variable.
I tested with Kodi, Web and Android. In Android, I pass https://example.com/basic/<base64(username:password)>
as the server. Unfortunately, since Caddy doesn't have a way to base64
encode, it requires doing that with the username/password manually.
I found that Jellyfin will return incorrect redirects. This is usually solved by setting "Base URL", but we can't do that since the URL is dynamic. Instead, we can rewrite it.
reverse_proxy jellyfin:8096 {
header_down Location /?(.*) "/basic/{re.path_auth.1}/$1"
}
Here is the full version that allows OAuth2 and basic auth while supporting most clients.
example.com {
log {
format filter {
# Remove the password from the logs
request>uri regexp ^/basic/[^/]* ""
}
}
handle /oauth2/* {
reverse_proxy oauth2-proxy:4180
}
handle {
# Save a copy of the original authorization header
map "{header.X-Emby-Authorization}" "{emby_auth}" {
~(.+) "$1"
default "{header.Authorization}"
}
# Check everywhere we could have auth
@basic_auth header Authorization "Basic *"
@path_auth path_regexp ^/basic/([^/]*)
@oauth_cookie header Cookie _oauth2_proxy=*
# Make path based auth look like basic auth
uri @path_auth strip_prefix "/basic/{re.path_auth.1}"
request_header @path_auth "X-Emby-Authorization" "{emby_auth}"
request_header @path_auth Authorization "Basic {re.path_auth.1}"
handle @basic_auth {
basic_auth {
media $2a$...
}
reverse_proxy jellyfin:8096 {
header_down Location /?(.*) "/basic/{re.path_auth.1}/$1"
}
}
handle @oauth_cookie {
forward_auth oauth2-proxy:4180 {
uri /oauth2/auth?allowed_emails=example.user1@gmail.com,example.user2@gmail.com
}
reverse_proxy jellyfin:8096
}
handle {
respond <<EOF
Not Found
EOF 404
}
A couple of notes:
404
if no auth is provided instead of a 401 specifying basic auth. If this isn't desired, make the basic auth handler default./oauth2/
. If this isn't desired, add a default redirectI would recommend only using basic auth in clients that absolutely need it. Path based basic auth should be nearly secure as typical basic auth because the path is encrypted by TLS, although it will likely show in server logs.
Hopefully this helps others!
I did some ugly hacking but it seems to work: https://github.com/sj14/ip-auth Basically a proxy which only accepts specific IPs (can be preconfigured or a be a host/DDNS address), or added dynamically by passing basic auth once (from any device on the same IP). I'm currently testing it as a sidecar in my Jellyfin pod on Kubernetes.
Jellyfin has a somewhat terrible code base that is split in multiple parts and everyone is doing whatever they want, without anyone overseeing the development efforts.
Because of the fragmentation of the code base and lack of general architecture principles, my custom forward-auth for Jellyfin is forced to use multiple methods of identifying sessions:
1) Paths like /
, /web/
, /System/Info
, /Branding/Css
, /system/info/public/
, /web/*.json
don't provide DeviceId
or Token
, so they have to be let through at almost all times, which sucks and makes it simple to identify that Jellyfin is running there. So they have to be blocked behind timed gate.
2) Some requests have unique session/key identifiers '(?<=api_key=)(.*?)(?=&|$)
, (?<=[Dd]eviceId=)(.*?)(?=&|$)
, /\/web\/.+\.(js|css)\?([a-z0-9]{20})$/
, (?<=[Tt]oken=")(.*?)(?=")
, those are not great but better than the 1st option.
3) And some requests provide headers x-mediabrowser-token
, x-emby-authorization
and authorization
, and those are the best, but almost none of the code-base uses those.
4) And some clients fully support cookies, for which you can set your own cookie and ignore the rest of the issues.
None of this is set in stone, between versions, those identifiers tend to shift away, more requests are available publicly so you have to resolve to using IP whitelisting, at least partially.
In my case I use timed pairing session which opens the gate for all requests until session is identified and stored in the database.
Afterwards it uses all of the mentioned methods to keep the session open. It works the best with Jellyfin for Android TV since it always allows to identify the device id early on. One of the desktop client also supports cookies, but not on all operating systems. For other apps you sometimes have to start the pairing process again to get access.
Jellyfin developers should really step up and solve that mess by introducing some general auth mechanism on the server side that covers all of the scenarios. Either in the form of some proxy or just rewriting all of the affected services. :disappointed:
I'm going to close this issue as it has derailed quite a bit and the discussion is no longer about HTTP basic auth support in the mobile Android app. It has been mentioned before that we use the authorization header for Jellyfin itself already and thus we won't be adding any support for basic auth. We're open to alternative options like proxy auth but that wouldn't be specific to the Android client and needs to be discussed in our meta discussion repository first.
Hacking around with a reverse proxy is strongly discouraged and we won't provide any support for it.
This is unfortunate, because Jellyfin 10.9.11 also broke basic-auth for desktop clients.
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
Expected behavior
The app to connect.
Logs
Screenshots
System (please complete the following information):
Additional context