Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FUTURE WORK ( v4.0.0.0 release) - Native Client Support #61

Open
strazto opened this issue Aug 14, 2022 · 13 comments
Open

FUTURE WORK ( v4.0.0.0 release) - Native Client Support #61

strazto opened this issue Aug 14, 2022 · 13 comments
Labels
enhancement New feature or request

Comments

@strazto
Copy link
Collaborator

strazto commented Aug 14, 2022

I've been lobbying the jellyfin client maintainers regarding implementing some kind of support for our plugin/similar plugins, & I've managed to get some traction + we've reached a consensus about what the architecture might look like.

jellyfin/jellyfin-meta#28

Once I get jellyfin/jellyfin-meta#28 resolved, and the change is merged into a release (this is going to be in a 10.9 or 10.10 release of jf-server, as well as a subsequent update of as many clients as i care to touch, starting with jf-android & jf-expo, probably swiftfin eventually), we will be able to incorporate it into our plugin.

Essentially, we'll be able to redirect back to a custom scheme that opens the native jellyfin app (ios, android, etc) and provides a single use code that authenticates a session.

@MatthiasGrandl
Copy link

Man this will be so cool! I am wondering, given it's popularity in the Jellyfin community, if we could get Infuse to also add support.

@abrioy
Copy link

abrioy commented Nov 21, 2023

Great work on this plugin, I really hope it gets integrated upstream sometime soon.

In the meantime, I have made a small script that allows the official Android app to login with your plugin. It works on both Android and a Browser for me, but YMMV.

I think some of that could be integrated in a PR for this projet, but it was a bit too much work for me to setup a development environment.

FYI I didn't have to do much to get the plugin working. The auth page expects the device id to be in localstorage, but the app uses a native function: window.NativeShell.AppHost.deviceId(). The dirty way I fixed it is by storing the result of this function in localstorage before opening the auth page, a cleaner solution would be to fetch it from the iframe in the auth page itself.

Add the SSO login on Android

Disclaimer: While I'm not personally aware of any security risk this script could cause, I am not a security expert.

  • Create a file with the following script in /usr/share/jellyfin/web/add-oath-button.js
  • Open the /usr/share/jellyfin/web/index.html file and add the following script just after the opening <head> tag: <script type="text/javascript" src="/web/oauth.js"></script>
Script: add-oath-button.js
function oAuthInitDeviceId() {
  if (!localStorage.getItem('_deviceId2') && window.NativeShell && window.NativeShell.AppHost) {
    const deviceId = window.NativeShell.AppHost.deviceId();
    if (deviceId) {
      console.log('[OAuth] Adding device id to local storage...')
      localStorage.setItem('_deviceId2', deviceId);
    }
  }
}

function oAuthDisableButton(button) {
  button.disabled = true;
  button.style.opacity = 0.5;
  setTimeout(() => {
    button.disabled = false;
    button.style.opacity = 1;
  }, 3000);
}

(() => {

  function getContainer() {
    return document.querySelector('#loginPage .disclaimer');
  }  

  function createButton(container) {
    if (!document.querySelector('#loginPage #OAuthStyle')) {
      console.log('[OAuth] Adding button to login page...');
      container.innerHTML = `
        <style id="OAuthStyle">
          .disclaimer {
            display: contents;
          }
          .emby-button:not(.btnOAuth) {
            filter: grayscale();
          }
          .btnOAuth {
            margin-top: 1rem;
          }
        </style>
        <a class="raised cancel block btnOAuth emby-button"
           onclick="oAuthDisableButton(this); oAuthInitDeviceId();"
           href="/sso/OID/start/OAuth" target="_self">
          Sign in with OAuth
        </a>`;
    }
  }

  function waitForContainer() {
    console.log('[OAuth] On login page, waiting for the login form...')

    const observer = new MutationObserver((mutations) => {
      const container = getContainer();
      if (container) {
        observer.disconnect();
        createButton(container);
      }
    })

    observer.observe(document.body, {
      childList: true, subtree: true, attributes: false, characterData: false
    })
  }

  addEventListener("DOMContentLoaded", (event) => {
    setTimeout(() => {
      if (location.href.includes('login.html')) {
        console.log('[OAuth] Waiting to add the OAuth button to the login page...');
        const container = getContainer();
        if (container) {
          createButton(container);
        } else {
          waitForContainer();
        }
      }
    }, 2000);
  });

})();

You will have to repeat this procedure each time Jellyfin gets updated

@9p4 9p4 changed the title FUTURE WORK ( v1.0.0 release) - Native Client Support FUTURE WORK ( v2.0.0.0 release) - Native Client Support Nov 22, 2023
@strazto
Copy link
Collaborator Author

strazto commented Nov 24, 2023

@abrioy

In the meantime, I have made a small script that allows the official Android app to login with your plugin. It works on both Android and a Browser for me, but YMMV.

Awesome work, script works for me on both platforms (with one caveat that's more a limitation of using webviews for OAuth), what an excellent and minimal workaround

You will have to repeat this procedure each time Jellyfin gets updated

For the people hosting on containers, I've written a Dockerfile that automates this.

I've uploaded it + a modified version of the script to the following repo:

https://github.com/strazto/jellyfin-plugin-sso-custom-jf-dockerfile

Basically just modifies the standard jellyfin image by adding the script, and injecting the <script> tag into index.html

ARG JELLYFIN_TAG=10.8.10
FROM jellyfin/jellyfin:${JELLYFIN_TAG}

COPY ./add-oauth-button.js /jellyfin/jellyfin-web/oauth.js
RUN sed -ri \
    's#<head>#&<script type="text/javascript" src="/web/oauth.js"></script>#' \
    /jellyfin/jellyfin-web/index.html

I've also updated the script for the following:

  • no longer destroys the existing disclaimer contents (disclaimer section serves other functions too)
  • fetch sso provider list automatically via the API & generates links

Caveat of OAuth within jellyfin app

This approach doesn't appear to work if using Google as your idp, due to google's security policy around "secure browsers".

This policy is probably enforced in this case due to the degraded security of using android Webviews or iframes for oauth flows potentially allowing information leakage (I forget the specifics).

I realized this, as my idp server, Authentik, is configured to allow me login to my account either manually (username/password), or via external social sign on (eg Google).

Username/password works in my case, but google gives an error when going via android app.

This limitation is probably not resolvable on our end, but rather would be solved by implementing the flow specified in jellyfin/jellyfin-meta#28 , which I've been intending to get around to for over a year at this point 🙃

@abrioy
Copy link

abrioy commented Nov 24, 2023

I've uploaded it + a modified version of the script to the following repo:

https://github.com/strazto/jellyfin-plugin-sso-custom-jf-dockerfile

Your repo appears to be private 🙃

I'm happy If could be of help. I've been trying to make the Android app work with the auth_request that I've setup in my reverse proxy, but I'm not sure I can make it work without upstream changes to the Jellyfin app.

@abrioy
Copy link

abrioy commented Nov 26, 2023

Here I found a workaround to serve jellyfin behind a reverse proxy with forward auth. I'm using nginx within Nginx Proxy Manager, but it should work for Traefik as well.

It's a dirty dirty hack that uses knowledge of the internal working of the Android app and could stop working at any point in the future.

The code shared here is not to be used as is, I didn't detail the auth_request configuration nor any security or even the headers for the proxy_pass. But it should be enough for someone dedicated enough to update their own configuration :)

Reverse proxy (nginx) configuration
# This endpoint is called before opening the webview
location /System/Info/Public {
        proxy_pass http://jellyfin_host:8086/System/Info/Public;
}

# This is called from the webview, the application checks if the application has loaded by waiting
# for a call to fetch main js bundle. We trick it by making a request to a file with a similar name that
# does not exist before redirecting to the index.html that is protected by a auth_request
location = / {   
        add_header Content-Type text/html;
        return 200 '<html><head><script>fetch("/main.xxx.bundle.js"); window.location.replace("/web/index.html");</script></head><body>Loading...</body></html>';
}

# This proxy_pass will basically serve the whole application
location / {
        auth_request @authcheck;
        proxy_pass http://jellyfin_host:8086;
}

@9p4
Copy link
Owner

9p4 commented Nov 26, 2023

I was considering a mechanism like that, but I think it'll break anything that doesn't use the webUI (clients that use the API exclusively, ie devices without webviews)

@abrioy
Copy link

abrioy commented Nov 30, 2023

Yes that's true, it works for me because I'm only using a browser and the web player of the official Android app. And I'm willing to make some sacrifices for the increased security.

But to implement a change like this upstream would be a lot more involved

@TheRealGramdalf
Copy link

I believe switching the default username from preferred_username to sub (and ideally pretty printing of the preferred name) should be a requirement as well - even if it's not human readable, using preferred_username is a security issue - especially on larger providers such as google etc, where the preferred_name can be changed at any point.

@ciupkaj
Copy link

ciupkaj commented Jan 5, 2024

  • Create a file with the following script in /usr/share/jellyfin/web/add-oath-button.js
  • Open the /usr/share/jellyfin/web/index.html file and add the following script just after the opening <head> tag: <script type="text/javascript" src="/web/oauth.js"></script>

Script: add-oath-button.js
You will have to repeat this procedure each time Jellyfin gets updated

Thanks, tried your script. Yet when I use the android app and press the sso button I am logged in in the browser but not the app.

@9p4 9p4 changed the title FUTURE WORK ( v2.0.0.0 release) - Native Client Support FUTURE WORK ( v4.0.0.0 release) - Native Client Support Feb 21, 2024
@HugoFollic
Copy link

Hi, is there any news about this feature ?

@9p4
Copy link
Owner

9p4 commented Apr 6, 2024

I am working on a set of patches to bring OpenID support to Jellyfin natively.

@strazto
Copy link
Collaborator Author

strazto commented Apr 9, 2024

I am working on a set of patches to bring OpenID support to Jellyfin natively.

@9p4 that's awesome - just in case you're not across it, you may wanna let them know on jellyfin/jellyfin-meta#68 , particularly taking note of their implementation discussions

Thanks for picking this up :D

@Terrorwolf01
Copy link

Hi,
Are there any Updates on this? Unfortunately that's the only thing in the Moment to stop my from using my IdP with SSO.
Thanks for making the SSO Plugin in the first Place.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

8 participants