9p4 / jellyfin-plugin-sso

This plugin allows users to sign in through an SSO provider (such as Google, Microsoft, or your own provider). This enables one-click signin.
GNU General Public License v3.0
630 stars 29 forks source link

Document How to set login disclaimer + branding css to add SSO links to frontpage #16

Closed strazto closed 1 year ago

strazto commented 2 years ago

Originally posted by @matthewstrasiotto in https://github.com/9p4/jellyfin-plugin-sso/issues/2#issuecomment-1103446246

An example - Here, I:

<a href="https://authelia.example.com/reset-password/step1" class="raised cancel block emby-button">Forgot Password</a>
<a href="https://example.com" class="raised cancel block emby-button">
   <span class="material-icons home" aria-hidden="true"></span>
   <span>Server Homepage</span>
</a>
/* Hide this in lieu of authelia link */
.emby-button.block.btnForgotPassword {
   display: none;
}

/* Make links look like buttons */
a.raised.emby-button { 
   padding: 0.9em 1em;
   color: inherit !important;
}

/* Let disclaimer take full width */
.disclaimerContainer {
   display: block;
}

This renders out like so:

image

"Manual Login" and "Quick Connect" are both builtin by jellyfin, "Forgot Password" is actually my own link to authelia.

strazto commented 2 years ago

to begin with, supporting "Sign on with authelia" would look like:

Login Disclaimer:

<a href="https://myjellyfin.example.com/sso/OID/p/authelia" class="raised cancel block emby-button authelia-sso">Sign in with Authelia</a>

Custom CSS code:

/* Make links look like buttons */
a.raised.emby-button { 
   padding: 0.9em 1em;
   color: inherit !important;
}

/* Let disclaimer take full width */
.disclaimerContainer {
   display: block;
}

/* Optionally, apply some styling to the `.authelia-sso` class, probably let users configure this */
.authelia-sso {
   /* idk set a background image or something lol */
}
9p4 commented 2 years ago

I'm planning on restructuring the login flow just a little to accommodate this (as well as solve the issue of when the web frontend is on a different domain than the JF server). The plan is to have everything work as a programmatic API, so instead of returning a webpage, it'll return JSON or something similar and other endpoints can use that API to set cookies or whatever. Then, with this, we can set it so that the button loads some JS that interacts with the API and sets the relevant information instead. The flow would look something like this:

  1. User loads JF, presses on button
  2. Button executes some JS to send a "start auth" to SSO API
  3. SSO API returns a URL to load to auth with
  4. Button JS opens URL in new tab
  5. Once the URL has finished authentication, it will redirect back to the API
  6. Button JS has been polling API "is auth done yet" and finally gets response once callback has occurred
  7. Button JS gets data and uses it to log the user in

I think it can be simplified at some points. What do you think?

fservida commented 2 years ago

Just putting also my input as I’ve been working hard to implement sso in different services, I’m not sure opening a secondary tab is the best UX in term of sso.

Also, and maybe more important, make sure automatic sso can still be available, either with something similar to the current endpoints, or (and probably better) a configuration point telling the JS script to automatically start the login flow. I’ve contributed something similar (although using headers and not saml/oidc) to ombi.

edit: also, I can try to help once I understand the architecture of JF and the plugins

edit 2: you can probably just skip the polling and: on load check if auth is already done, if yes poll the data and login, if no, and automatic login is implemented or the user clicks a button, start the flow, redirect just as you are doing now, and then when you redirect back to JF, it would reload the script but this time it would be in the logged in branch

9p4 commented 2 years ago

So the flow would be something like this:

  1. User loads JF, presses on button
  2. Button executes some JS to send a "start auth" to SSO API
  3. Button JS sets a cookie with the ID of the SSO "start"
  4. SSO API returns a URL to load to auth with
  5. Button JS opens URL
  6. Once the URL has finished authentication, it will redirect back to the API, which will redirect to the frontend
  7. Button JS checks if cookie for "start" exists, and if so, it will use the token in the cookie to get information. If not, nothing happens (step 1)
  8. Button JS gets data and uses it to log the user in
strazto commented 2 years ago

Ah sorry - I think i mean to reply with my own take on how to approach SSO flow but I must have forgotten to submit the comment.

Basically - it seems that you're implying that we add some custom javascript to the login page that handles some aspects of the flow.

Although this sounds nice, I do not believe there exists an API that would allow a plugin to inject javascript / <script> elements to arbitrary pages, which would make this challenging.

The webui (correctly) runs DOMPurify on any user created html, before it's rendered (For example, the login disclaimer).

For now, I'm unsure how to address this, but the new-tab approach should be "good enough" for a while.

strazto commented 2 years ago

For my own reference, here is the setting pattern used to configure loginDisclaimer & branding css:

https://github.com/jellyfin/jellyfin-web/blob/81e240d50dfeda096d7f8b5fc44311ec6d8625fc/src/controllers/dashboard/general.js#L38-L45

Get pattern for login disclaimer:

https://github.com/jellyfin/jellyfin-web/blob/81e240d50dfeda096d7f8b5fc44311ec6d8625fc/src/controllers/session/login/index.js#L286-L290

Get pattern for custom css + disclaimer:

https://github.com/jellyfin/jellyfin-web/blob/81e240d50dfeda096d7f8b5fc44311ec6d8625fc/src/controllers/dashboard/general.js#L55

https://github.com/jellyfin/jellyfin-web/blob/81e240d50dfeda096d7f8b5fc44311ec6d8625fc/src/controllers/dashboard/general.js#L106-L109

strazto commented 2 years ago

Matrix conversation exerpt

Matthew Strasiotto As I understand it, Jellyfin presently doesn't allow plugins to inject JavaScript to any page except their own plugin pages. I'm curious if the Devs would consider allowing plugins to do that? lakerssuperman2 joined the room. Niels What would your use case for such a feature be? Matthew Strasiotto Niels https://github.com/9p4/jellyfin-plugin-sso , for example, js based flows for authenticating with OIDC

GitHub - 9p4/jellyfin-plugin-sso: This plugin allows users to sign in through an SSO provider (such as Google, Microsoft, or your own provider). This enables one-click signin. - GitHub This plugin allows users to sign in through an SSO provider (such as Google, Microsoft, or your own provider). This enables one-click signin. - GitHub - 9p4/jellyfin-plugin-sso: This plugin allows ... Niels I don't think supporting such a thing with js plugins in the frontend is really feasible Matthew Strasiotto what do you mean by that? Niels You want to inject a complete user interface with javascript, that's not going to work when the UI is managed by React. It would also 100% break for every release Matthew Strasiotto I'm aware that it would only apply to the web-ui & web-wrapper apps, but that doesn't bother me as that means that the same client-side plugin code can apply to the same view across like 4 platforms hm, I don't think I'd want to inject a complete UI with JS, it'd be more along the lines of "check headers, perform auth", maybe inject an ivew for a redirect (if that's feasible) i don't actually know how feasible that specific scenario is for OIDC flows in general, I'd need to double check so specifics of that scenario i put forward aside, imagine someone just wants to add a button that performs some action, for example, generates a share link for a file, or triggers an opensubtitle download for the currently viewed item am i right in thinking that there's currently no mechanic within the plugin api to perform these actions? Niels That's not possible no, it would be very difficult to support Matthew Strasiotto ok, fair enough

9p4 commented 2 years ago

The ideal option would be to integrate this plugin as part of the official JF spec for better frontend support, but that would be complex to maintain, and I don't know if I can manage that.

In the meantime, web-based auth in conjunction with Quick-Connect should handle 99% of the use-case.

strazto commented 2 years ago

The ideal option would be to integrate this plugin as part of the official JF spec for better frontend support, but that would be complex to maintain, and I don't know if I can manage that.

Yep, the maintainers seem pretty resistant since it would imply that every since client would have to bring its own implementation of OIDC auth

In the meantime, web-based auth in conjunction with Quick-Connect should handle 99% of the use-case.

yeah, when 10.8 gets full-released, pretty much all major clients will have implemented quick-connect

FirbyKirby commented 1 year ago

I've implemented an additional button using the method outlined in this issue to automate the SSO login, rather then just lining back to the Server homepage. Specifically, I am hitting the auth proxy redirect URL for Jellyfin. So, if I set "Name of OID Provider" in the Jellyfin's SSO settings to "authentik" the redirect would be /sso/OID/r/authentik. Anyway, this works great! However, it does launch this URL in a separate tab, which is not ideal.

So, is there any way to configure the button to open in the same tab? I've tried adding target="_self" to the link.

NeroPcStation commented 1 year ago

So, is there any way to configure the button to open in the same tab? I've tried adding target="_self" to the link.

It is hardcoded in Jellyfin web interface.

FirbyKirby commented 1 year ago

Awww. Bummer. But thanks for the quick answer! Still way better then nothing.

adamzvolanek commented 1 year ago

"authentik" the redirect would be /sso/OID/r/authentik

Did you add the redirect like this to Authentik?

image

I am still trying to verify if the Authentik/JellyFin integration is working. So any help would be greatly appreciated!

9p4 commented 1 year ago

yes that looks correct

9p4 commented 1 year ago

Fixed in 076cb1b893711b664386d46f6ee8d776243a9061

hendrik1120 commented 1 year ago

Hi, is it possible for the SSO Login button to follow the link in the same tab? href should do that by default, but at least in safari and chrome it always opens in a new tab. Is it possible to overwrite that somehow?

9p4 commented 1 year ago

It's hardcoded into the Jellyfin web interface code.

https://github.com/9p4/jellyfin-plugin-sso/issues/16#issuecomment-1591017300

isavegas commented 11 months ago

Hi, is it possible for the SSO Login button to follow the link in the same tab? href should do that by default, but at least in safari and chrome it always opens in a new tab. Is it possible to overwrite that somehow?

You can use a form + button to cause the URL to be opened in the current tab. Works well on browsers, although the official Jellyfin app for Android hangs on "Logging in..." when using Authelia.

<form action="https://jellyfin.example.com/sso/OID/start/PROVIDER"><button class="raised button-sso block emby-button" type="submit">Sign In with SSO</button></form>
button.raised.button-sso {
  background: #00a4dc;
}
nibblerrick commented 10 months ago

You can use a form + button to cause the URL to be opened in the current tab. Works well on browsers, although the official Jellyfin app for Android hangs on "Logging in..." when using Authelia.

I just tested this with authentik. On IOS it logs in fine, on Android I have the same, hangs at "logging in..."

Thanks for the idea with the form, at least on IOS SSO login seems possible this way (direct SSO without Quickconnect, that option remains, of course).

LeVraiRoiDHyrule commented 10 months ago
button.raised.button-sso {
  background: #00a4dc;
}

You are my savior, thank you so much. This makes SSO work in Jellyfin Media Player, which my users have to use because most of my files are HEVC and can't be played on most browsers. Then quick connect will do the trick for the very few that use Kodi with the Jellyfin addon.

Sapd commented 1 month ago

If someone finds it useful. There is also a way to directly redirect to SSO if not logged in. Skipping the login-form completely.

I injected JS with nginx:

    location /login-redirect.js {
        default_type application/javascript;
        add_header Content-Disposition "inline; filename=login-redirect.js";
        return 200 '
        let timer
            function isAndroidDevice() {
                return /Android/i.test(navigator.userAgent);
            }

            function checkForSignIn() {
            const isLoginPage = window.location.href.includes("login.html")
        if (isLoginPage) {
            clearInterval(timer)
                    window.location.href = "/sso/OID/start/authentik"; // Change to your desired URL
                }
            const isLoggedIn = window.location.href.includes("index.html")
        if (isLoggedIn) clearInterval(timer)
            }
            if (!isAndroidDevice())
                timer = setInterval(checkForSignIn, 10);
        ';
    }

Then inside location /web:

        sub_filter
        '</head>'
        '<script plugin="intro-skipper" src="configurationpage?name=skip-intro-button.js"></script>
        <script src="/login-redirect.js"></script>
    </head>';

It will simply periodically check if the user is at the Jellyfin login url. If yes it will redirect for authentication to SSO. If it detects that the user is at index.html (which is never called when not logged in) it will simply delete the timer.

In my test it even worked in the mobile app. Edit: Only worked in iOS, I simply added code to exclude Android