Coder Social home page Coder Social logo

9p4 / jellyfin-plugin-sso Goto Github PK

View Code? Open in Web Editor NEW
533.0 13.0 25.0 1.66 MB

This plugin allows users to sign in through an SSO provider (such as Google, Microsoft, or your own provider). This enables one-click signin.

License: GNU General Public License v3.0

C# 62.98% HTML 17.03% JavaScript 13.07% CSS 6.74% Nix 0.18%
jellyfin jellyfin-plugin sso single-sign-on

jellyfin-plugin-sso's Introduction

Jellyfin SSO Plugin

Logo

GPL 3.0 License GitHub Actions Build Status Current Release Release RSS Feed Main Commits RSS Feed

This plugin allows users to sign in through an SSO provider (such as Google, Microsoft, or your own provider). This enables one-click signin.

recording-resized.mp4

Existing users may link new SSO accounts, or remove existing links using self-service at /SSOViews/linking.

Current State:

This is 100% alpha software! PRs are welcome to improve the code.

There is NO admin configuration! You must use the API to configure the program! Added by strazto in PR #18 and #27.

This is for Jellyfin 10.8 and only on the Web UI and clients supporting Quick Connect

This README reflects the branch it is currently on! Switch tags to view version-specific documentation!

Tested Providers

Find provider specific documentation in providers.md

  • Authelia
  • authentik
  • Keycloak
    • OIDC & SAML
  • Google OpenID: Works, but usernames are all numeric

Supported Protocols

Security

This is my first time writing C# so please take all of the code written here with a grain of salt. This program should be reasonably secure since it validates all information passed from the client with either a certificate or a secret internal state.

Installing

Add the package repo https://raw.githubusercontent.com/9p4/jellyfin-plugin-sso/manifest-release/manifest.json to your Jellyfin plugin repositories.

Then, install the plugin from the plugin catalog!

See Contributing for instructions on how to build from source.

(Fallback) Legacy package repo (Versions <= 3.3.0)

We have transitioned to a release system that automates distribution, packaging & hosting. This system is new, and if something goes wrong, you can try using the old package repository as a fallback.

Instead add the old package repository: https://repo.ersei.net/jellyfin/manifest.json to your jellyfin plugin repositories.

Installing cutting edge/nightly builds

If you're impatient/brave/feel like helping us test things out, you can install the nightly build of the plugin, which is automatically built against the main branch.

The nightly build can be installed from the main plugin repo, and will always have a version number of 0.0.0.9000.

The nightly build may have new features unavailable in other builds, but be warned, things may change frequently in nightly builds, and things may break, and you could lose data.

Roadmap

  • Admin page
  • Automated tests
  • Add role/claims support
  • Use canonical usernames instead of preferred usernames
  • Add user self-service
  • Finalize RBAC access for all user properties

Examples

Note that you should add both "/r/" and "/redirect/" paths to your SSO provider's configuration!

Creating A Login Button On The Main Page

In the Jellyfin administration UI, under "General", there is a "Branding" section. In that section, add the following code in the "Login disclaimer" block (replacing PROVIDER_NAME and the domain):

<form action="https://jellyfin.example.com/sso/OID/start/PROVIDER_NAME">
  <button class="raised block emby-button button-submit">
    Sign in with SSO
  </button>
</form>

Then, add the following code in the "Custom CSS code" section:

a.raised.emby-button {
  padding: 0.9em 1em;
  color: inherit !important;
}

.disclaimerContainer {
  display: block;
}

screenshot of the configuration page with the same code

For more information, refer to issue #16.

SAML

Example for adding a SAML configuration with the API using curl:

curl -v -X POST -H "Content-Type: application/json" -d '{"samlEndpoint": "https://keycloak.example.com/realms/test/protocol/saml", "samlClientId": "jellyfin-saml", "samlCertificate": "Very long base64 encoded string here", "enabled": true, "enableAuthorization": true, "enableAllFolders": false, "enabledFolders": [], "adminRoles": ["jellyfin-admin"], "roles": ["allowed-to-use-jellyfin"], "enableFolderRoles": true, "folderRoleMapping": [{"role": "allowed-to-watch-movies", "folders": ["cc7df17e2f3509a4b5fc1d1ff0a6c4d0", "f137a2dd21bbc1b99aa5c0f6bf02a805"]}]}' "https://myjellyfin.example.com/sso/SAML/Add/PROVIDER_NAME?api_key=API_KEY_HERE"

Make sure that the JSON is the same as the configuration you would like.

The SAML provider must have the following configuration (I am using Keycloak, and I cannot speak for whatever you will see):

Make sure that clientid is replaced with the actual client ID and PROVIDER_NAME is replaced with the chosen provider name!

OpenID

Example for adding an OpenID configuration with the API using curl

curl -v -X POST -H "Content-Type: application/json" -d '{"oidEndpoint": "https://keycloak.example.com/realms/test", "oidClientId": "jellyfin-oid", "oidSecret": "short secret here", "enabled": true, "enableAuthorization": true, "enableAllFolders": false, "enabledFolders": [], "adminRoles": ["jellyfin-admin"], "roles": ["allowed-to-use-jellyfin"], "enableFolderRoles": true, "folderRoleMapping": [{"role": "allowed-to-watch-movies", "folders": ["cc7df17e2f3509a4b5fc1d1ff0a6c4d0", "f137a2dd21bbc1b99aa5c0f6bf02a805"]}], "roleClaim": "realm_access", "oidScopes" : [""]}' "https://myjellyfin.example.com/sso/OID/Add/PROVIDER_NAME?api_key=API_KEY_HERE"

The OpenID provider must have the following configuration (again, I am using Keycloak)

Make sure that clientid is replaced with the actual client ID and PROVIDER_NAME is replaced with the chosen provider name!

API Endpoints

The API is all done from a base URL of /sso/

SAML

Flow

  • POST SAML/start/PROVIDER_NAME: This is the SAML POST endpoint. It accepts a form response from the SAML provider and returns HTML and JavaScript for the client to login with a given provider name.
  • GET SAML/start/PROVIDER_NAME: This is the SAML initiator: it will begin the authorization flow for SAML with a given provider name.
  • POST SAML/Auth/PROVIDER_NAME: This is the SAML client-side API: the HTML and JavaScript client will call this endpoint to receive Jellyfin credentials given a provider name. Post format is in JSON with the following keys:
    • deviceId: string. Device ID.
    • deviceName: string. Device name.
    • appName: string. App name.
    • appVersion: string. App version.
    • data: string. The signed SAML XML request. Used to verify a request.

Configuration

These all require authorization. Append an API key to the end of the request: curl "http://myjellyfin.example.com/sso/SAML/Get?api_key=API_KEY_HERE"

  • POST SAML/Add/PROVIDER_NAME: This adds or overwrites a configuration for SAML for the given provider name. It accepts JSON with the following keys and format:
    • samlEndpoint: string. The SAML endpoint.
    • samlClientId: string. The SAML client ID.
    • samlCertificate: string. The base64 encoded SAML certificate.
    • enabled: boolean. Determines if the provider is enabled or not.
    • enableAuthorization: boolean: Determines if the plugin sets permissions for the user. If false, the user will start with no permissions and an administrator will add permissions. If disabled, then the permissions of users will not be modified and the Jellyfin defaults will be used instead.
    • enableAllFolders: boolean. Determines if the client logging in is allowed access to all folders.
    • enabledFolders: array of strings. If enableAllFolders is set to false, then this will be used to determine what folders the users who log in through this provider are allowed to use.
    • roles: array of strings. This validates the SAML response against the Role attribute. If a user has any of these roles, then the user is authenticated. Leave blank to disable role checking.
    • adminRoles: array of strings. This uses SAML response's Role attributes. If a user has any of these roles, then the user is an admin. Leave blank to disable (default is to not enable admin permissions).
    • enableFolderRoles: boolean. Determines if role-based folder access should be used.
    • folderRoleMapping: object in the format "role": string and "folders": array of strings. The user with this role will have access to the following folders if enableFolderRoles is enabled. To get the IDs of the folders, GET the /Library/MediaFolders URL with an API key. Look for the Id attribute.
    • enableLiveTvRoles: boolean. Determines if role-based Live TV access should be used.
    • liveTvRoles: array of strings. If enableLiveTvRoles is enabled, then the user's roles will be checked against these. If the user is granted permission, then the user will be able to view Live TV.
    • liveTvManagementRoles: array of strings. If enableLiveTvRoles is enabled, then the user's roles will be checked against these. If the user is granted permission, then the user will be able to manage Live TV.
    • enableLiveTv: boolean. Whether to allow Live TV by default. This applies even if enableLiveTvRoles is enabled.
    • enableLiveTvManagement: boolean. Whether to allow Live TV management by default. This applies even if enableLiveTvRoles is enabled.
    • defaultProvider: string. The set provider then gets assigned to the user after they have logged in. If it is not set, nothing is changed. With this, a user can login with SSO but is still able to log in via other providers later. See the Unregister endpoint.
    • schemeOverride: string. Sets the scheme for URLs used. Can be useful if the plugin refuses to use HTTPS URLs.
  • GET SAML/Del/PROVIDER_NAME: This removes a configuration for SAML for a given provider name.
  • GET SAML/Get: Lists the configurations currently available.

OpenID

Flow

  • GET OID/redirect/PROVIDER_NAME: This is the OpenID callback path. This will return HTML and JavaScript for the client to login with a given provider name.
  • GET OID/start/PROVIDER_NAME: This is the OpenID initiator: it will begin the authorization flow for OpenID with a given provider name.
  • POST OID/Auth/PROVIDER_NAME: This is the OpenID client-side API: the HTML and JavaScript client will call this endpoint to receive Jellyfin credentials for a given provider name. Post format is in JSON with the following keys:
    • deviceId: string. Device ID.
    • deviceName: string. Device name.
    • appName: string. App name.
    • appVersion: string. App version.
    • data: string. The OpenID state. Used to verify a request.

Configuration

These all require authorization. Append an API key to the end of the request: curl "http://myjellyfin.example.com/sso/OID/Get?api_key=9c6e5fae4ae145669e6b7a3942f813b7"

  • POST OID/Add/PROVIDERNAME: This adds or overwrites a configuration for OpenID with a given provider name. It accepts JSON with the following keys and format:
    • oidEndpoint: string. The OpenID endpoint. Must have a .well-known path available.
    • oidClientId: string. The OpenID client ID.
    • oidSecret: string. The OpenID secret.
    • enabled: boolean. Determines if the provider is enabled or not.
    • enableAuthorization: boolean: Determines if the plugin sets permissions for the user. If false, the user will start with no permissions and an administrator will add permissions. If disabled, then the permissions of users will not be modified and the Jellyfin defaults will be used instead.
    • enableAllFolders: boolean. Determines if the client logging in is allowed access to all folders.
    • enabledFolders: array of strings. If enableAllFolders is set to false, then this will be used to determine what folders the users who log in through this provider are allowed to use.
    • roles: array of strings. This validates the OpenID response against the claim set in roleClaim. If a user has any of these roles, then the user is authenticated. Leave blank to disable role checking. This currently only works for Keycloak (to my knowledge).
    • adminRoles: array of strings. This uses the OpenID response against the claim set in roleClaim. If a user has any of these roles, then the user is an admin. Leave blank to disable (default is to not enable admin permissions).
    • enableFolderRoles: boolean. Determines if role-based folder access should be used.
    • folderRoleMapping: object in the format "role": string and "folders": array of strings. The user with this role will have access to the following folders if enableFolderRoles is enabled. To get the IDs of the folders, GET the /Library/MediaFolders URL with an API key. Look for the Id attribute.
    • enableLiveTvRoles: boolean. Determines if role-based Live TV access should be used.
    • liveTvRoles: array of strings. If enableLiveTvRoles is enabled, then the user's roles will be checked against these. If the user is granted permission, then the user will be able to view Live TV.
    • liveTvManagementRoles: array of strings. If enableLiveTvRoles is enabled, then the user's roles will be checked against these. If the user is granted permission, then the user will be able to manage Live TV.
    • enableLiveTv: boolean. Whether to allow Live TV by default. This applies even if enableLiveTvRoles is enabled.
    • enableLiveTvManagement: boolean. Whether to allow Live TV management by default. This applies even if enableLiveTvRoles is enabled.
    • roleClaim: string. This is the value in the OpenID response to check for roles. For Keycloak, it is realm_access.roles by default. The first element is the claim type, the subsequent values are to parse the JSON of the claim value. Use a "\." to denote a literal ".". This expects a list of strings from the OIDC server.
    • oidScopes : array of strings. Each contains an additional scope name to include in the OIDC request.
      • For some OIDC providers (For example, authelia), additional scopes may be required in order to validate group membership in role claim.
      • Leave empty to only request the default scopes.
    • defaultProvider: string. The set provider then gets assigned to the user after they have logged in. If it is not set, nothing is changed. With this, a user can login with SSO but is still able to log in via other providers later. See the Unregister endpoint.
    • defaultUsernameClaim: string. The provider will use the claim to create the users' usernames. If not set, it fallbacks to preferred_username.
    • disableHttps: boolean. Determines whether the OpenID discovery endpoint requires HTTPS.
    • doNotValidateEndpoints: boolean. Determines whether the OpenID discovery process will validate endpoints. This may be required for Google.
    • doNotValidateIssuerName: boolean. Determines whether the OpenID discovery process will validate the OpenID issuer name.
    • schemeOverride: string. Sets the scheme for URLs used. Can be useful if the plugin refuses to use HTTPS URLs.
  • GET OID/Del/PROVIDER_NAME: This removes a configuration for OpenID for a given provider name.
  • GET OID/Get: Lists the configurations currently available.
  • GET OID/States: Lists currently active OpenID flows in progress.

Misc

  • POST Unregister/username: This "unregisters" a user from SSO. A JSON-formatted string must be posted with the new authentication provider. To reset to the default provider, use Jellyfin.Server.Implementations.Users.DefaultAuthenticationProvider like so: curl -X POST -H "Content-Type: application/json" -d '"Jellyfin.Server.Implementations.Users.DefaultAuthenticationProvider"' "https://myjellyfin.example.com/sso/Unregister/username?api_key=API_KEY

Limitations

Logging in with an SSO account that has the same username as an existing Jellyfin account will override the permissions for the user. Use caution when overriding the administrator account!

There is no GUI to sign in. You have to make it yourself! The buttons should redirect to something like this: https://myjellyfin.example.com/sso/SAML/start/clientid replacing clientid with the provider client ID and SAML with the auth scheme (either SAML or OID).

Furthermore, there is no functional admin page (yet). PRs for this are welcome. In the meantime, you have to interact with the API to add or remove configurations. Added by strazto in PR #18 and #27.

There is also no logout callback. Logging out of Jellyfin will log you out of Jellyfin only, instead of the SSO provider as well.

This only supports Jellyfin on its own domain (for now). This is because I'm using string concatenation for generating some URLs. A PR is welcome to patch this. Fixed in PR #1.

This only works on the web UI. The user must open the Jellyfin web UI BEFORE using the SSO program to populate some values in the localStorage. Fixed by implementing a comment by Pfuenzle in Issue #5.

Contributing

Dependencies

This project uses Nix flakes to manage development dependencies. Run nix develop to use the same toolchain versions.

Building

This is built with .NET 6.0. Build with dotnet publish . for the debug release in the SSO-Auth directory. Copy over the IdentityModel.OidcClient.dll, the IdentityModel.dll and the SSO-Auth.dll files in the /bin/Debug/net6.0/publish directory to a new folder in your Jellyfin configuration: config/plugins/sso.

VSCode Workflow

An example .vscode configuration may be found at strazto/jellyfin-plugin-sso-vscode.

From the root of this repo, you may clone that to .vscode

# From repo root

git clone https://github.com/strazto/jellyfin-plugin-sso-vscode .vscode

Releasing

This plugin uses JPRM to build the plugin. Refer to the documentation there to install JPRM.

Build the zipped plugin with jprm --verbosity=debug plugin build ..

CI Releases

Anything merged to the main branch will be built and published by our CI system.

Anything tagged/released as a formal Github release will also be built and published by our CI system.

If you wish to use releases from your own fork, refer to Installing, however, you will need to change the url to the manifest file, https://raw.githubusercontent.com/9p4/jellyfin-plugin-sso/manifest-release/manifest.json so that it refers to your fork.

Credits and Thanks

Much thanks to the Jellyfin LDAP plugin for offering a base for me to start on my plugin.

I use the AspNet SAML library for the SAML side of things (patched to work with Base64 on non-Windows machines).

I use the IdentityModel OIDC Client library for the OpenID side of things.

Thanks to these projects, without which I would have been pulling my hair out implementing these protocols from scratch.

Something funny about the origins of this plugin

It totally slipped my mind, but I had requested this functionality a few years back. What goes around comes around, I guess.

jellyfin-plugin-sso's People

Contributors

9p4 avatar adamzvolanek avatar cfenner avatar crobibero avatar esmondmissen avatar fredriklindberg avatar hendrik1120 avatar lf- avatar ndragon798 avatar pfuenzle avatar sbogomolov avatar strazto avatar tbelway avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

jellyfin-plugin-sso's Issues

contributing: may I add .vscode development workflows?

To start developing on this project, I used vscode and wrote a few workflows, using the jellyfin-plugin-template as a reference

The workflows are fairly portable, and you can find them here:
https://github.com/matthewstrasiotto/jellyfin-plugin-sso/tree/main/.vscode

I accidentally checked them into my fork (oops) which I'll have to tidy up when it's time to pull, but I was wondering whether you'd accept development workflow artifacts into this project (on a separate pr)

Documentation: Update Readme to reflect admin page + document usage with a few providers (keycloak, authelia)

I think we should provide a few example docs that demonstrate usage with as many providers as have been tested successfully.

Presently, at least the following providers have been tested succesfully:

  • Keycloak:
    • successfully configured with the full suite of role-based access permissions by 9p4
    • this is demo'd in the readme.
  • Authelia:
    • successfully configured basic authentication (no role checking)
    • pending #24 , will also support role checking: #23 (comment)
    • The documentation of this should be formalized somewhere

Besides these two, which are probably most important as they constitute two of the most the most popular self-host Identity Providers it might be helpful to document additional providers, eg

  • Authentik? (New-ish self-host ID provider) (Let an authentik user figure this out)
  • Google ?
    • successfully configured basic auth, with some limitations according to 9p4

GUI to login

You wrote: Limitations - There is no GUI to sign in.

Is this a limitation of Jellyfin Plugins or not? If not, I ask for a login GUI. Thanks.

Oauth2 device flow

Is your feature request related to a problem? Please describe.
It would be awesome to able to authenticate from another device (a smartphone for instance) on limited input capability devices like TVs.

Describe the solution you'd like
Implement a device flow like Plex (plex.tv/link) does.
I suppose this requires native client support (#61) to work.
Newest version of Authentik (https://github.com/goauthentik/authentik/releases/tag/version%2F2022.10.0) supports oauth2 device flow (goauthentik/authentik#3334).

Describe alternatives you've considered
/

Additional context
image

Authentik as SSO: Error processing request.

Describe the bug
Today i tried to add authentik as OIDC Provider for my jellyfin instance.
I configured the jellyfin side according to the docs and also created a scope mapping for the groups for the provider on the authentik side. When logging in to authentik and "clicking the application", i get redirected to https://jellyfin.tld/sso/OID/r/authentik but get a Error processing request.

Same happens when i visit the URL directly.

Jellyfin Logs:

[13:57:12] [ERR] [92] Jellyfin.Server.Middleware.ExceptionMiddleware: Error processing request. URL GET /sso/OID/r/authentik.
System.ArgumentNullException: Value cannot be null. (Parameter 'key')
   at System.Collections.Generic.Dictionary`2.FindValue(TKey key)
   at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
   at Jellyfin.Plugin.SSO_Auth.Api.SSOController.OidPost(String provider, String state)
   at lambda_method1115(Closure , Object , Object[] )
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Jellyfin.Server.Middleware.ServerStartupMessageMiddleware.Invoke(HttpContext httpContext, IServerApplicationHost serverApplicationHost, ILocalizationManager localizationManager)
   at Jellyfin.Server.Middleware.WebSocketHandlerMiddleware.Invoke(HttpContext httpContext, IWebSocketManager webSocketManager)
   at Jellyfin.Server.Middleware.IpBasedAccessValidationMiddleware.Invoke(HttpContext httpContext, INetworkManager networkManager)
   at Jellyfin.Server.Middleware.LanFilteringMiddleware.Invoke(HttpContext httpContext, INetworkManager networkManager, IServerConfigurationManager serverConfigurationManager)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Jellyfin.Server.Middleware.QueryStringDecodingMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.ReDoc.ReDocMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Jellyfin.Server.Middleware.RobotsRedirectionMiddleware.Invoke(HttpContext httpContext)
   at Jellyfin.Server.Middleware.LegacyEmbyRouteRewriteMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.ResponseCompression.ResponseCompressionMiddleware.InvokeCore(HttpContext context)
   at Jellyfin.Server.Middleware.ResponseTimeMiddleware.Invoke(HttpContext context, IServerConfigurationManager serverConfigurationManager)
   at Jellyfin.Server.Middleware.ExceptionMiddleware.Invoke(HttpContext context)

To Reproduce

  1. Visit https://domain.tld/sso/OID/r/authentik

Expected behavior
I should be logged in

Configuration
https://domain.tld/application/o/jellyfin/.well-known/openid-configuration is working fine

<?xml version="1.0" encoding="utf-8"?>
<PluginConfiguration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <SamlConfigs />
  <OidConfigs>
    <item>
      <key>
        <string>authentik</string>
      </key>
      <value>
        <PluginConfiguration>
          <OidEndpoint>https://auth.domain.tld/application/o/jellyfin</OidEndpoint>
          <OidClientId><my cool unique ID></OidClientId>
          <OidSecret><my super secret secret></OidSecret>
          <Enabled>true</Enabled>
          <EnableAuthorization>true</EnableAuthorization>
          <EnableAllFolders>false</EnableAllFolders>
          <EnabledFolders>
            <string>9d7ad6afe9afa2dab1a2f6e00ad28fa6</string>
            <string>f137a2dd21bbc1b99aa5c0f6bf02a805</string>
            <string>a656b907eb3a73532e40e44b968d0225</string>
          </EnabledFolders>
          <AdminRoles>
            <string>["Jellyfin Admins"]</string>
          </AdminRoles>
          <Roles>
            <string>["Jellyfin Users"]</string>
          </Roles>
          <EnableFolderRoles>false</EnableFolderRoles>
          <FolderRoleMappings />
          <RoleClaim>groups</RoleClaim>
          <OidScopes>
            <string>["groups"]</string>
          </OidScopes>
        </PluginConfiguration>
      </value>
    </item>
  </OidConfigs>
</PluginConfiguration>

Versions (please complete the following information):

  • OS: unraid
  • Browser: Chrome/Firefos
  • Jellyfin Version: 10.8.1
  • Plugin Version: 3.3.0.0

enable metamask wallet log in.

Is your feature request related to a problem? Please describe.
It would be awesome if there is a functionality to log in with web3 wallets like Metamask.

SAML error Processing request

Jellyfin.Server.Middleware.ExceptionMiddleware: Error processing request. URL POST /sso/SAML/p/okta. System.ArgumentNullException: Value cannot be null. (Parameter 's')

When i initially created the provider using the jellyfin API, was getting a provider not found error. i looked at /config/data/plugins/configuration/SSO-Auth.xml, and my configuration was added to the Oauth section. I moved it into the SAML section, but now i receive
Jellyfin.Server.Middleware.ExceptionMiddleware: Error processing request. URL POST /sso/SAML/p/okta. System.ArgumentNullException: Value cannot be null. (Parameter 's')

Not sure what Parameter 's' is referring to in this case, thanks!

Unable to install - repo zip URLs 404

The links to the zips from the repo manifest https://repo.ersei.net/jellyfin/manifest.json seem to all 404. Example:

https://repo.saggis.com/jellyfin/sso-authentication/sso-authentication_3.3.0.0.zip returns 404

Jellyfin trying to install:

[16:18:08] [ERR] [81] Emby.Server.Implementations.Updates.InstallationManager: Package installation failed
System.Net.Http.HttpRequestException: Response status code does not indicate success: 404 (Not Found).
   at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()
   at Emby.Server.Implementations.Updates.InstallationManager.PerformPackageInstallation(InstallationInfo package, PluginStatus status, CancellationToken cancellationToken)
   at Emby.Server.Implementations.Updates.InstallationManager.InstallPackageInternal(InstallationInfo package, CancellationToken cancellationToken)
   at Emby.Server.Implementations.Updates.InstallationManager.InstallPackage(InstallationInfo package, CancellationToken cancellationToken)
[16:18:08] [ERR] [81] Jellyfin.Server.Middleware.ExceptionMiddleware: Error processing request. URL POST /Packages/Installed/SSO%20Authentication.
System.Net.Http.HttpRequestException: Response status code does not indicate success: 404 (Not Found).
   at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()
   at Emby.Server.Implementations.Updates.InstallationManager.PerformPackageInstallation(InstallationInfo package, PluginStatus status, CancellationToken cancellationToken)
   at Emby.Server.Implementations.Updates.InstallationManager.InstallPackageInternal(InstallationInfo package, CancellationToken cancellationToken)
   at Emby.Server.Implementations.Updates.InstallationManager.InstallPackage(InstallationInfo package, CancellationToken cancellationToken)
   at Emby.Server.Implementations.Updates.InstallationManager.InstallPackage(InstallationInfo package, CancellationToken cancellationToken)
   at Jellyfin.Api.Controllers.PackageController.InstallPackage(String name, Nullable`1 assemblyGuid, String version, String repositoryUrl)
   at lambda_method1451(Closure , Object )
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Jellyfin.Server.Middleware.ServerStartupMessageMiddleware.Invoke(HttpContext httpContext, IServerApplicationHost serverApplicationHost, ILocalizationManager localizationManager)
   at Jellyfin.Server.Middleware.WebSocketHandlerMiddleware.Invoke(HttpContext httpContext, IWebSocketManager webSocketManager)
   at Jellyfin.Server.Middleware.IpBasedAccessValidationMiddleware.Invoke(HttpContext httpContext, INetworkManager networkManager)
   at Jellyfin.Server.Middleware.LanFilteringMiddleware.Invoke(HttpContext httpContext, INetworkManager networkManager, IServerConfigurationManager serverConfigurationManager)
   at Microsoft.AspNetCore.Authorization.Policy.AuthorizationMiddlewareResultHandler.HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Jellyfin.Server.Middleware.QueryStringDecodingMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.ReDoc.ReDocMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Jellyfin.Server.Middleware.RobotsRedirectionMiddleware.Invoke(HttpContext httpContext)
   at Jellyfin.Server.Middleware.LegacyEmbyRouteRewriteMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.ResponseCompression.ResponseCompressionMiddleware.InvokeCore(HttpContext context)
   at Jellyfin.Server.Middleware.ResponseTimeMiddleware.Invoke(HttpContext context)
   at Jellyfin.Server.Middleware.ExceptionMiddleware.Invoke(HttpContext context)

Beginner with SSO and Authentik. What to do next ?

Hi,

I'm new with SSO and Authentik. I have a fully functionnal installation and for now, I've only setup it for Portainer as it was explained in Authentik's docs. The end goal is to secure all my apps behind it, with OIDC when available or proxy if not. I'm now looking to secure Jellyfin but I'm confused.

I followed the steps here : https://github.com/9p4/jellyfin-plugin-sso/blob/main/providers.md#authentik but now I'm unsure what to do next. I guess I should follow this : https://github.com/9p4/jellyfin-plugin-sso#limitations but I don't know how to do this.

It probably comes from my extremely limited knowledge of OIDC and Authentik, but I don't know with which of my app I should start learning. Jellyfin doesn't seem too complicated so I guess it makes sense to begin with jellyfin. The end goal would be to have all of my users login through Authentik, with, if possible, groups of users which will have different library access and admin rights. I don't know if this is the right place, but could someone with more knowledge guide me a bit on the next steps ?

Thanks in advance for any help, resource, or answer. Have a great day !

One Login with SSO disables normal login

Hi,
I'm not sure if it is supposed to be like this, but after logging in once using SSO, I'm unable to login using a password using the account. Using SSO is still working, but I guess it would be preferrable to keep the password-login.

The error in Jellyfin is:

[01:41:01] [ERR] [40] Jellyfin.Server.Implementations.Users.UserManager: Error authenticating with provider InvalidOrMissingAuthenticationProvider
MediaBrowser.Controller.Authentication.AuthenticationException: User Account cannot login with this provider. The Normal provider for this user cannot be found
   at Jellyfin.Server.Implementations.Users.InvalidAuthProvider.Authenticate(String username, String password)
   at Jellyfin.Server.Implementations.Users.UserManager.AuthenticateWithProvider(IAuthenticationProvider provider, String username, String password, User resolvedUser)
[01:41:01] [INF] [40] Jellyfin.Server.Implementations.Users.UserManager: Authentication request for Pfuenzle has been denied (IP: <null>).
[01:41:01] [ERR] [40] Jellyfin.Server.Middleware.ExceptionMiddleware: Error processing request: Invalid username or password entered. URL POST /Users/authenticatebyname.

Seems like the default auth provider of the user gets cleared, is this intentional?
I'm additionally using LDAP auth, may this be the reason for the error?

Also another question regarding the library's available to the user, would it be possible to get the library's from the users existing Jellyfin account, so they can be customized. From what I've seen now, it's only possible to give access to the same set of library's with SSO, no matter if the user is an admin or not.

Apache + Keycloak "Error processing request." upon linking account.

Describe the bug
Successful authentication fails to link account.

To Reproduce
Steps to reproduce the behavior:

  1. Go to https://jellyfin.domain.tld/SSO/oid/p/keycloak?isLinking=true
  2. Login with keycloak user credentials
  3. See error

Expected behavior

Proper authentication

Config

{"keycloak":{"OidEndpoint":"https://keycloak.domain.tld/realms/realm/.well-known/openid-configuration","OidClientId":"jellyfin","OidSecret":"REDACTED","Enabled":true,"EnableAuthorization":false,"EnableAllFolders":true,"EnabledFolders":[],"AdminRoles":[],"Roles":[],"EnableFolderRoles":false,"FolderRoleMapping":[],"OidScopes":[],"CanonicalLinks":{}}}

Logs

[2022-09-21 02:38:04.838 -04:00] [ERR] [177] Jellyfin.Server.Middleware.ExceptionMiddleware: Error processing request. URL "GET" "/sso/OID/r/keycloak".
System.ArgumentNullException: Value cannot be null. (Parameter 'input')
   at System.Text.RegularExpressions.ThrowHelper.ThrowArgumentNullException(ExceptionArgument arg)
   at System.Text.RegularExpressions.Regex.Split(String input)
   at Jellyfin.Plugin.SSO_Auth.Api.SSOController.OidPost(String provider, String state)
   at lambda_method1096(Closure , Object , Object[] )
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Jellyfin.Server.Middleware.ServerStartupMessageMiddleware.Invoke(HttpContext httpContext, IServerApplicationHost serverApplicationHost, ILocalizationManager localizationManager)
   at Jellyfin.Server.Middleware.WebSocketHandlerMiddleware.Invoke(HttpContext httpContext, IWebSocketManager webSocketManager)
   at Jellyfin.Server.Middleware.IpBasedAccessValidationMiddleware.Invoke(HttpContext httpContext, INetworkManager networkManager)
   at Jellyfin.Server.Middleware.LanFilteringMiddleware.Invoke(HttpContext httpContext, INetworkManager networkManager, IServerConfigurationManager serverConfigurationManager)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Jellyfin.Server.Middleware.QueryStringDecodingMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.ReDoc.ReDocMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Jellyfin.Server.Middleware.RobotsRedirectionMiddleware.Invoke(HttpContext httpContext)
   at Jellyfin.Server.Middleware.LegacyEmbyRouteRewriteMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.ResponseCompression.ResponseCompressionMiddleware.InvokeCore(HttpContext context)
   at Jellyfin.Server.Middleware.ResponseTimeMiddleware.Invoke(HttpContext context, IServerConfigurationManager serverConfigurationManager)
   at Jellyfin.Server.Middleware.ExceptionMiddleware.Invoke(HttpContext context)
[2022-09-21 02:38:47.666 -04:00] [INF] [175] Jellyfin.Plugin.SSO_Auth.Api.SSOController: SSO Controller initialized

Versions (please complete the following information):

  • OS: Arch Linux
  • Browser: Firefox
  • Jellyfin Version:10.8.4
  • Plugin Version: 3.4.0.0

Integrate with Kodi Login

Hi,

I have a Jellyfin server for years and now looking to integrate it better with all my services so I'm looking to get SSO for everything. This plugin looks really cool and I'm looking forward integrating it with Authentik or Keycloak. But my problem is that I am still using the Kodi addon on some devices, even if most of my users use web.

Is there a way to get SSO and Kodi login working together ? Is there a way to still have a password that I can input in the addon ? Is there a way to have like an "app password" on Microsoft Accounts, to connect on apps that don't have a browser ?

Thanks in advance for any answer,
Have a great day

Authelia - Login process stuck

Describe the bug
The login process is stuck

To Reproduce
Steps to reproduce the behavior:

  1. Go to https://jellyfin.domain.com/sso/OID/p/authelia
  2. Logging in... (waiting indefinitely)
  3. [18:37:18] [ERR] [79] Jellyfin.Server.Middleware.ExceptionMiddleware: Error processing request. URL POST /sso/OID/Auth/Authelia.
    System.NullReferenceException: Object reference not set to an instance of an object.
    at Jellyfin.Plugin.SSO_Auth.Api.SSOController.Authenticate(Guid userId, Boolean isAdmin, Boolean enableAuthorization, Boolean enableAllFolders, String[] enabledFolders, AuthResponse authResponse, String defaultProvider)
    at Jellyfin.Plugin.SSO_Auth.Api.SSOController.OidAuth(String provider, AuthResponse response)

Configuration

<?xml version="1.0" encoding="utf-8"?>
<PluginConfiguration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <SamlConfigs />
  <OidConfigs>
    <item>
      <key>
        <string>Authelia</string>
      </key>
      <value>
        <PluginConfiguration>
          <OidEndpoint>https://auth.domain.com/.well-known/openid-configuration/</OidEndpoint>
          <OidClientId>jellyfin</OidClientId>
          <OidSecret>REDACTED</OidSecret>
          <Enabled>true</Enabled>
          <EnableAuthorization>true</EnableAuthorization>
          <EnableAllFolders>false</EnableAllFolders>
          <EnabledFolders>
            <string>REDACTED</string>
            <string>REDACTED</string>
          </EnabledFolders>
          <AdminRoles>
            <string>media_admins</string>
            <string>admins</string>
          </AdminRoles>
          <Roles>
            <string>medias</string>
            <string>media_admins</string>
          </Roles>
          <EnableFolderRoles>false</EnableFolderRoles>
          <FolderRoleMappings />
          <RoleClaim>groups</RoleClaim>
          <OidScopes>
            <string>groups</string>
          </OidScopes>
          <DefaultProvider>Jellyfin.Plugin.LDAP_Auth.LdapAuthenticationProviderPlugin</DefaultProvider>
        </PluginConfiguration>
      </value>
    </item>
  </OidConfigs>


Versions (please complete the following information):

  • OS: Linux
  • Browser: Tried with Chrome and Firefox
  • Jellyfin Version: 10.8.5
  • Plugin Version: 3.4.0.2

It works sometime with isLinking=true as the end but with adding CanonicalLinks at the end of the config file which is normal I assume even if I don't know what is the purpose of this feature.

Doesn't show up in plugin catalog

After adding the repository URL, the plugin doesn't display in the plugin catalog.
I'm running the current stable version of Jellyfin, version 10.7.7, and your repo advertises that the target version is 10.8.0.0, so I'm assuming that's why the plugin isn't displayed.
Does the plugin require any 10.8 specific features that aren't available on the current stable? I've just manually installed it by dragging it to the /var/lib/jellyfin/plugins dir, pending setup with my keycloak install.

Btw- thanks for developing this!

Dump permissions and expected permissions to log on `Error. Check permissions.`

When the user does not have the correct permissions. You are showing Error. Check permissions.
And for security reasons i thing this is good. We dont want to give the user hints on what permissions they need.
But maybe we can log those available and needed permissions to stdout so that a admin can analyse it.

Btw: I am currently trying to setup keycloak with a client role jellyfin.allowedlogin but i dont found out how to check them. Is it even possible to have non realm roles ?

Authentik SSO: Error processing request (HTTPS Required)

Describe the bug
Configured my Jellyfin instance to use OIDC/Authentik for auth following the instructions in the repo.
When I try to navigate to the Jellyfin SSO http://192.168.x.x:8096/SSO/oid/p/JellyfinOpenID
I got "Error processing request."

Jellyfin logs:

[13:36:23] [ERR] [80] Jellyfin.Server.Middleware.ExceptionMiddleware: Error processing request. URL GET /SSO/oid/p/JellyfinOpenID.
System.InvalidOperationException: Error loading discovery document: Error connecting to http://192.168.x.x:9000/application/o/jellyfin/.well-known/openid-configuration. HTTPS required.
 ---> System.InvalidOperationException: HTTPS required
   --- End of inner exception stack trace ---
   at IdentityModel.OidcClient.OidcClient.EnsureProviderInformationAsync(CancellationToken cancellationToken) in /_/src/OidcClient/OidcClient.cs:line 407
   at IdentityModel.OidcClient.OidcClient.EnsureConfigurationAsync(CancellationToken cancellationToken) in /_/src/OidcClient/OidcClient.cs:line 371
   at IdentityModel.OidcClient.OidcClient.PrepareLoginAsync(Parameters frontChannelParameters, CancellationToken cancellationToken) in /_/src/OidcClient/OidcClient.cs:line 111
   at Jellyfin.Plugin.SSO_Auth.Api.SSOController.OidChallenge(String provider, Boolean isLinking)
   at lambda_method513(Closure , Object )
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Jellyfin.Server.Middleware.ServerStartupMessageMiddleware.Invoke(HttpContext httpContext, IServerApplicationHost serverApplicationHost, ILocalizationManager localizationManager)
   at Jellyfin.Server.Middleware.WebSocketHandlerMiddleware.Invoke(HttpContext httpContext, IWebSocketManager webSocketManager)
   at Jellyfin.Server.Middleware.IpBasedAccessValidationMiddleware.Invoke(HttpContext httpContext, INetworkManager networkManager)
   at Jellyfin.Server.Middleware.LanFilteringMiddleware.Invoke(HttpContext httpContext, INetworkManager networkManager, IServerConfigurationManager serverConfigurationManager)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Jellyfin.Server.Middleware.QueryStringDecodingMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.ReDoc.ReDocMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Jellyfin.Server.Middleware.RobotsRedirectionMiddleware.Invoke(HttpContext httpContext)
   at Jellyfin.Server.Middleware.LegacyEmbyRouteRewriteMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.ResponseCompression.ResponseCompressionMiddleware.InvokeCore(HttpContext context)
   at Jellyfin.Server.Middleware.ResponseTimeMiddleware.Invoke(HttpContext context, IServerConfigurationManager serverConfigurationManager)
   at Jellyfin.Server.Middleware.ExceptionMiddleware.Invoke(HttpContext context)

To Reproduce
Steps to reproduce the behavior:

  1. Go to http://192.168.x.x:8096/SSO/oid/p/JellyfinOpenID

Expected behavior
Able to login

Configuration
Add your plugin configuration XML file here formatted as code (with three backticks surrounding the text), or as an upload to a pastebin service.

I'm able to access http://192.168.x.x:9000/application/o/jellyfin/.well-known/openid-configuration directly

<?xml version="1.0" encoding="utf-8"?>
<PluginConfiguration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <SamlConfigs />
  <OidConfigs>
    <item>
      <key>
        <string>JellyfinOpenID</string>
      </key>
      <value>
        <PluginConfiguration>
          <OidEndpoint>http://192.168.x.x:9000/application/o/jellyfin/</OidEndpoint>
          <OidClientId>xxx</OidClientId>
          <OidSecret>xxx</OidSecret>
          <Enabled>true</Enabled>
          <EnableAuthorization>true</EnableAuthorization>
          <EnableAllFolders>true</EnableAllFolders>
          <EnabledFolders />
          <AdminRoles />
          <Roles />
          <EnableFolderRoles>false</EnableFolderRoles>
          <FolderRoleMappings />
          <RoleClaim>groups</RoleClaim>
          <OidScopes>
            <string>groups</string>
          </OidScopes>
          <DefaultProvider>Jellyfin.Server.Implementations.Users.DefaultAuthenticationProvider</DefaultProvider>
          <CanonicalLinks />
        </PluginConfiguration>
      </value>
    </item>
  </OidConfigs>
</PluginConfiguration>

Versions (please complete the following information):

  • OS: Unraid 10.6.3
  • Browser: Chrome
  • Jellyfin Version: 10.8.4
  • Plugin Version: 3.4.0.2

Bug: "Set Default Provider" field is empty in OIDC config, but authenticating via the provider still changes the user's Authentication Provider

Describe the bug
When authenticating via a provider that has an empty field for "Set Default Provider", the documented behaviour is that no change to the user's authentication provider should be made.

To Reproduce
Steps to reproduce the behavior:

  1. Configure a user (let's call them User1 ) to use a non-default auth provider, for example LDAP Auth
  2. When configuring an SSO provider, leave the "Set Default Provider" blank
  3. Log out, and log into User1 via the SSO you just configured.
  4. Log out, and attempt to log-in as User1 using the previous provider (eg, LDAP)
  5. Authentication using the previous provider will fail
  6. Login to an admin account by any means
  7. From the admin session, go to Dashboard -> Users -> User1
  8. Their Authentication Provider will have been reset to "Default"

Expected behavior

When "Set Default Provider" is left blank, the docstring states:

The set provider then gets assigned to the user after they have logged in. If it is not set, nothing is changed.

Screenshots

image

Code

Where this change takes place:

if (user == null)
{
_logger.LogInformation("SSO user doesn't exist, creating...");
user = await _userManager.CreateUserAsync(username).ConfigureAwait(false);
}
user.AuthenticationProviderId = GetType().FullName;

user.AuthenticationProviderId = GetType().FullName;

L568 is the problematic line

The intended logic lives here:

if (user == null)
{
_logger.LogInformation("SSO user doesn't exist, creating...");
user = await _userManager.CreateUserAsync(username).ConfigureAwait(false);
}
user.AuthenticationProviderId = GetType().FullName;

This behaves correctly, in that it performs no action if the setting is empty, however, the provider is still changed as described above.

As a workaround, I could change the "Set Default Provider" setting to match the provider that the user should correctly use:

In my case, this is Jellyfin.Plugin.LDAP_Auth.LdapAuthenticationProviderPlugin , as my provider (authentik) also acts an LDAP provider.

Extra Context

A few other things actually happen here:

When a user auths, the line I flagged above actually sets their provider to Jellyfin.Plugin.SSO_Auth.Api.SSOController (This is the return of GetType().FullName in this context)

For whatever reason, Jellyfin.Plugin.SSO_Auth.Api.SSOController is actually not technically registered as an authentication provider.

Jellyfin produces the following log:

[18:32:55] [WRN] [12] Jellyfin.Server.Implementations.Users.UserManager: User matt was found with invalid/missing Authentication Provider Jellyfin.Plugin.SSO_Auth.Api.SSOController. Assigning user to InvalidAuthProvider until this is corrected

I believe that when jellyfin finds a user with an invalid auth provider, it falls back to the default provider.

It's possible that the reason I experienced this bug, but others did not, is because other people were not previously using a custom auth provider for their users.

Versions (please complete the following information):

  • OS:
    • Jellyfin Host: Ubuntu 20, but in a container,
    • client: Windows
  • Browser: Chrome
  • Jellyfin Version: 10.8 beta2
  • Plugin Version: 3.3.0.0

Redirect URI drops https to http with keycloak and apache reverse proxy

Describe the bug
Redirect URL is incorrect for Jellyfin + Apache reverse proxy + Keycloak

To Reproduce
Steps to reproduce the behavior:

  1. Go to https://jellyfin.domain.tld/SSO/oid/p/keycloak?isLinking=true
  2. Login with keycloak credentials
  3. See error

From keycloak logs
type=LOGIN_ERROR, realmId=realm, clientId=jellyfin, userId=null, ipAddress=::1, error=invalid_redirect_uri, redirect_uri=http://jellyfin.domain.tld/sso/OID/r/keycloak

Expected behavior
Proper authentication, redirect URL should keep HTTPS correctly

Configuration
Add your plugin configuration XML file here formatted as code (with three backticks surrounding the text), or as an upload to a pastebin service.

Versions (please complete the following information):

  • OS: Arch Linux
  • Browser: Firefox
  • Jellyfin Version:10.8.4
  • Plugin Version: 3.4.0.0

HOTFIX
Added http://jellyfin.domain.tld/* to redirect URI allow list.

Repository Manifest offline (404 - Not Found)

Hello,

first of all, thanks for working on this plugin!

It appears that the repository URL mentioned in the Readme no longer works - it currently just returns a "404 - Not Found".

https://repo.saggis.com/jellyfin/manifest.json

curl -I https://repo.saggis.com/jellyfin/manifest.json
HTTP/2 404 
x-frame-options: SAMEORIGIN
content-security-policy: default-src 'self'; script-src 'self' 'unsafe-inline'; img-src *; style-src 'self' 'unsafe-inline'; object-src 'none'; frame-ancestors 'self'; worker-src 'self' blob:; font-src 'self' data:; connect-src 'self'; media-src 'self' blob: data:; manifest-src 'self'; base-uri 'self'; form-action 'self';
strict-transport-security: max-age=63072000; includeSubdomains; preload
referrer-policy: same-origin
content-type: text/html; charset=iso-8859-1
date: Fri, 27 May 2022 15:05:36 GMT
server: Apache

Did the repo move to a new location?

When creating a new account via SSO, Jellyfin creates the account with no password when the internal mechanism is set to the backup provider

Describe the bug
When creating a new account via SSO (Authentik), Jellyfin creates the account with no password. You are able to login to Jellyfin directly just with the username and no password.

To Reproduce
Steps to reproduce the behavior:

  1. Create a new user on the SSO app
  2. SSO into Jellyfin
  3. Open a private tab and go the the Jellyfin subdomain
  4. Login in with just the username

Expected behavior
Either use the same password as the SSO app or display a pop up to create a Jellyfin password on first login to Jellyfin

Screenshots
If applicable, add screenshots to help explain your problem.

Configuration
Add your plugin configuration XML file here formatted as code (with three backticks surrounding the text), or as an upload to a pastebin service.

Versions (please complete the following information):

  • OS: Linux
  • Browser: Firefox
  • Jellyfin Version: 10.8.1
  • Plugin Version: 3.3.0.0

Additional context
Add any other context about the problem here. Was the plugin built from source?

DefaultProvider field does not strip whitespace

Describe the bug
This is a minor behavioral change, but can be pretty hard to troubleshoot, and have major implications.

The DefaultProvider field, and possibly other fields, do not strip whitespace from the outside ends of user input. This can result in behavior including unexpectedly preventing password login

To Reproduce
Steps to reproduce the behavior:

In the SSO admin interface, configure a DefaultProvider with a leading or trailing space. The resulting configuration (retrieved via API) will retain that space.

{
  "keycloak": {
    ...
    "DefaultProvider": " Jellyfin.Server.Implementations.Users.DefaultAuthenticationProvider",
  }
}

Expected behavior
Leading and trailing whitespace should be stripped.

Versions (please complete the following information):

  • OS: Kubernetes on K3OS
  • Browser: Firefox
  • Jellyfin Version: 10.8.4
  • Plugin Version: 3.4.0.0

Error processing request. when adding either SAML or OpenID

I successfully installed the plugin using Jellyfin with Version: 10.8.0 with the LinuxServer Docker Image.
The Plugin version that got installed is 2.0.0.0

Im also using Keycloak, like you, and created a client according to the documentation.

But when I try to add any configuration using curl, I get the error "Error processing request."

My commands are as following:

curl -v -X POST -H "Content-Type: application/json" -d '{"samlEndpoint": "https://<redacted>/auth/realms/master/protocol/saml", "samlClientId": "jellyfin", "samlCertificate": "<redacted>", "enabled": true, "enableAllFolders": true, "enabledFolders": [], "adminRoles": [], "roles": []}' "https://<redacted>/sso/SAML/Add?api_key=<redacted>"

for SAML and

curl -v -X POST -H "Content-Type: application/json" -d '{"oidEndpoint": "https://<redacted>/auth/realms/master", "oidClientId": "jellyfin", "oidSecret": "<redacted>", "enabled": true, "enableAllFolders": true, "enabledFolders": [], "adminRoles": [], "roles": []}' "https://<redacted>/sso/SAML/Add?api_key=<redacted>"

for OpenID.

The error thats coming up in the Log for OpenID is the following:

[13:29:21] [INF] [74] Jellyfin.Plugin.SSO_Auth.Api.SSOController: SSO Controller initialized
[13:29:21] [ERR] [74] Jellyfin.Server.Middleware.ExceptionMiddleware: Error processing request. URL POST /sso/OID/Add.
System.NullReferenceException: Object reference not set to an instance of an object.
   at Jellyfin.Plugin.SSO_Auth.Api.SSOController.OIDAdd(OIDConfig oidConfig)
   at Microsoft.Extensions.Internal.ObjectMethodExecutor.<>c__DisplayClass33_0.<WrapVoidMethod>b__0(Object target, Object[] parameters)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.VoidResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Jellyfin.Server.Middleware.ServerStartupMessageMiddleware.Invoke(HttpContext httpContext, IServerApplicationHost serverApplicationHost, ILocalizationManager localizationManager)
   at Jellyfin.Server.Middleware.WebSocketHandlerMiddleware.Invoke(HttpContext httpContext, IWebSocketManager webSocketManager)
   at Jellyfin.Server.Middleware.IpBasedAccessValidationMiddleware.Invoke(HttpContext httpContext, INetworkManager networkManager)
   at Jellyfin.Server.Middleware.LanFilteringMiddleware.Invoke(HttpContext httpContext, INetworkManager networkManager, IServerConfigurationManager serverConfigurationManager)
   at Microsoft.AspNetCore.Authorization.Policy.AuthorizationMiddlewareResultHandler.HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Jellyfin.Server.Middleware.QueryStringDecodingMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.ReDoc.ReDocMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Jellyfin.Server.Middleware.RobotsRedirectionMiddleware.Invoke(HttpContext httpContext)
   at Jellyfin.Server.Middleware.LegacyEmbyRouteRewriteMiddleware.Invoke(HttpContext httpContext)
   at Jellyfin.Server.Middleware.ResponseTimeMiddleware.Invoke(HttpContext context)
   at Jellyfin.Server.Middleware.ExceptionMiddleware.Invoke(HttpContext context)

I cant read anything out of this error, do you have any idea why im getting it?

All other OpenID integrations are working fine, so I guess it got something to do with the plugin.

Thanks a lot for your plugin btw, really a shame that this hasnt been integrated into Jellyfin.

Bit Offtopic:
In the README.me in the OpenID curl command, the endpoint you specified is "https://keycloak.example.com/auth/reapls/test"
I guess this is a typo and should spell realms instead of reapls?

Make Distinction Between "p" and "r" Paths Clearer

Is your feature request related to a problem? Please describe.
It seems like there isn't enough of a distinction between the p and r paths for initialization and redirection respectively. Perhaps make the different clearer?

Describe the solution you'd like
Replace the p paths with start and r with redirect.

Describe alternatives you've considered
Improve the documentation perhaps?

Additional context
#49
#82
#55
#106

Re-add CI releases

Originally posted by @9p4 in #39 (comment)

First of all, why aren't we using https://github.com/Kevinjil/jellyfin-plugin-repo-action over your fork?

When I opened the PR, kevin hadn't merged my changes into his upstream, they should be at parity now though

Second, can we trust these actions with a token? I'd prefer to avoid supply chain attacks.

it's valid to be paranoid of supply chain attacks, but I don't know how to fully answer this -

Short answer is - I think so
I'm in direct correspondence with the authors + contribute to the actions directly concerned with building

Re. The actions concerned with GitHub API that use tokens, the version is pinned to a specific tag, which should be sufficient to ensure we can at least rely on the version were running.

Mostly they seem to have minimal dependencies, too

I'll have to check that in more depth, but I can vouch for the current state of Kevin's action at the very least

Your review is as good as mine for the rest

[auth0] Allow customization of the username claim

Describe the bug
System.ArgumentException: Invalid username (Parameter 'name')

Auth0 passes very ambiguous usernames. I usually set emails as the username in applications. In this case it seems to break Jellyfin's validation.

I'm using auth0 with google

To Reproduce
Steps to reproduce the behavior:

  1. Setup auth0
  2. Configure the plugin with oidc
  3. https://myjellyfin.example.com/sso/SAML/p/clientid
  4. See error

Expected behavior
Successful login

Screenshots
If applicable, add screenshots to help explain your problem.

Configuration
Add your plugin configuration XML file here formatted as code (with three backticks surrounding the text), or as an upload to a pastebin service.

Versions (please complete the following information):

  • OS: [e.g. Linux] Talos Linux
  • Browser: [e.g. chrome, safari] Chrome
  • Jellyfin Version: [e.g. 10.8 Alpha 4] 10.8.0.0
  • Plugin Version: [e.g. 2.0.1.0 or a Git tag] 3.3.0.0

Additional context
Add any other context about the problem here. Was the plugin built from source?


jellyfin-c7f784bdb-6cfgb jellyfin [23:40:57] [INF] [47] Jellyfin.Plugin.SSO_Auth.Api.SSOController: SSO Controller initialized
jellyfin-c7f784bdb-6cfgb jellyfin [23:40:57] [ERR] [47] Jellyfin.Server.Middleware.ExceptionMiddleware: Error processing request. URL POST /sso/OID/Auth/auth0.
jellyfin-c7f784bdb-6cfgb jellyfin System.ArgumentException: Invalid username (Parameter 'name')
jellyfin-c7f784bdb-6cfgb jellyfin    at Jellyfin.Server.Implementations.Users.UserManager.GetUserByName(String name)
jellyfin-c7f784bdb-6cfgb jellyfin    at Jellyfin.Plugin.SSO_Auth.Api.SSOController.Authenticate(String username, Boolean isAdmin, Boolean enableAuthorization, Boolean enableAllFolders, String[] enabledFolders, AuthResponse authResponse, String defaultProvider)
jellyfin-c7f784bdb-6cfgb jellyfin    at Jellyfin.Plugin.SSO_Auth.Api.SSOController.OidAuth(String provider, AuthResponse response)
jellyfin-c7f784bdb-6cfgb jellyfin    at lambda_method1083(Closure , Object )
jellyfin-c7f784bdb-6cfgb jellyfin    at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
jellyfin-c7f784bdb-6cfgb jellyfin    at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
jellyfin-c7f784bdb-6cfgb jellyfin    at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
jellyfin-c7f784bdb-6cfgb jellyfin    at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
jellyfin-c7f784bdb-6cfgb jellyfin    at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
jellyfin-c7f784bdb-6cfgb jellyfin    at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
jellyfin-c7f784bdb-6cfgb jellyfin --- End of stack trace from previous location ---
jellyfin-c7f784bdb-6cfgb jellyfin    at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
jellyfin-c7f784bdb-6cfgb jellyfin    at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
jellyfin-c7f784bdb-6cfgb jellyfin    at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
jellyfin-c7f784bdb-6cfgb jellyfin    at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
jellyfin-c7f784bdb-6cfgb jellyfin --- End of stack trace from previous location ---
jellyfin-c7f784bdb-6cfgb jellyfin    at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
jellyfin-c7f784bdb-6cfgb jellyfin    at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
jellyfin-c7f784bdb-6cfgb jellyfin    at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
jellyfin-c7f784bdb-6cfgb jellyfin    at Jellyfin.Server.Middleware.ServerStartupMessageMiddleware.Invoke(HttpContext httpContext, IServerApplicationHost serverApplicationHost, ILocalizationManager localizationManager)
jellyfin-c7f784bdb-6cfgb jellyfin    at Jellyfin.Server.Middleware.WebSocketHandlerMiddleware.Invoke(HttpContext httpContext, IWebSocketManager webSocketManager)
jellyfin-c7f784bdb-6cfgb jellyfin    at Jellyfin.Server.Middleware.IpBasedAccessValidationMiddleware.Invoke(HttpContext httpContext, INetworkManager networkManager)
jellyfin-c7f784bdb-6cfgb jellyfin    at Jellyfin.Server.Middleware.LanFilteringMiddleware.Invoke(HttpContext httpContext, INetworkManager networkManager, IServerConfigurationManager serverConfigurationManager)
jellyfin-c7f784bdb-6cfgb jellyfin    at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
jellyfin-c7f784bdb-6cfgb jellyfin    at Jellyfin.Server.Middleware.QueryStringDecodingMiddleware.Invoke(HttpContext httpContext)
jellyfin-c7f784bdb-6cfgb jellyfin    at Swashbuckle.AspNetCore.ReDoc.ReDocMiddleware.Invoke(HttpContext httpContext)
jellyfin-c7f784bdb-6cfgb jellyfin    at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
jellyfin-c7f784bdb-6cfgb jellyfin    at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
jellyfin-c7f784bdb-6cfgb jellyfin    at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
jellyfin-c7f784bdb-6cfgb jellyfin    at Jellyfin.Server.Middleware.RobotsRedirectionMiddleware.Invoke(HttpContext httpContext)
jellyfin-c7f784bdb-6cfgb jellyfin    at Jellyfin.Server.Middleware.LegacyEmbyRouteRewriteMiddleware.Invoke(HttpContext httpContext)
jellyfin-c7f784bdb-6cfgb jellyfin    at Microsoft.AspNetCore.ResponseCompression.ResponseCompressionMiddleware.InvokeCore(HttpContext context)
jellyfin-c7f784bdb-6cfgb jellyfin    at Jellyfin.Server.Middleware.ResponseTimeMiddleware.Invoke(HttpContext context)
jellyfin-c7f784bdb-6cfgb jellyfin    at Jellyfin.Server.Middleware.ExceptionMiddleware.Invoke(HttpContext context)

When Role Mapping is Disabled, the RoleClaim Value is Still Required

Describe the bug
From #80:

Seems like a bug that the roleclaim value is still checked even if role mapping is disabled. Try setting it to any string.

[2022-09-21 02:38:04.838 -04:00] [ERR] [177] Jellyfin.Server.Middleware.ExceptionMiddleware: Error processing request. URL "GET" "/sso/OID/r/keycloak".
System.ArgumentNullException: Value cannot be null. (Parameter 'input')
   at System.Text.RegularExpressions.ThrowHelper.ThrowArgumentNullException(ExceptionArgument arg)
   at System.Text.RegularExpressions.Regex.Split(String input)
   at Jellyfin.Plugin.SSO_Auth.Api.SSOController.OidPost(String provider, String state)
   at lambda_method1096(Closure , Object , Object[] )
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Jellyfin.Server.Middleware.ServerStartupMessageMiddleware.Invoke(HttpContext httpContext, IServerApplicationHost serverApplicationHost, ILocalizationManager localizationManager)
   at Jellyfin.Server.Middleware.WebSocketHandlerMiddleware.Invoke(HttpContext httpContext, IWebSocketManager webSocketManager)
   at Jellyfin.Server.Middleware.IpBasedAccessValidationMiddleware.Invoke(HttpContext httpContext, INetworkManager networkManager)
   at Jellyfin.Server.Middleware.LanFilteringMiddleware.Invoke(HttpContext httpContext, INetworkManager networkManager, IServerConfigurationManager serverConfigurationManager)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Jellyfin.Server.Middleware.QueryStringDecodingMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.ReDoc.ReDocMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Jellyfin.Server.Middleware.RobotsRedirectionMiddleware.Invoke(HttpContext httpContext)
   at Jellyfin.Server.Middleware.LegacyEmbyRouteRewriteMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.ResponseCompression.ResponseCompressionMiddleware.InvokeCore(HttpContext context)
   at Jellyfin.Server.Middleware.ResponseTimeMiddleware.Invoke(HttpContext context, IServerConfigurationManager serverConfigurationManager)
   at Jellyfin.Server.Middleware.ExceptionMiddleware.Invoke(HttpContext context)
[2022-09-21 02:38:47.666 -04:00] [INF] [175] Jellyfin.Plugin.SSO_Auth.Api.SSOController: SSO Controller initialized

To Reproduce
Steps to reproduce the behavior:

  1. Set EnableAuthorization to False
  2. Set the roleClaim value to null
  3. Error when logging in

Expected behavior
The roleClaim value is ignored when it is not needed.

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

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.

Remove SAML Support

It seems like nobody is using SAML, and removing it would greatly improve the maintainability of the codebase. No need for a second admin panel, small configuration, easier maintainability.

Any thoughts? Is anyone using SAML?

Suffix for SSO users

Is your feature request related to a problem? Please describe.
If user logs in using SSO once, they wont be able to change their profile password if they used Jellyfin as a normal user with same username before.

Describe the solution you'd like
A better way would be to set user profile name for users from SSO is to make users have suffixes on their names, for example: octo@nc instead of just octo

[HELP] After logging in using Authelia, I get stuck at Logging in...

Sorry if I asked in the wrong place.
So I added the sso plugin, configured it, and added an oidc config in Authelia. After going to https://jelly.mydomain.com/sso/OID/p/Authelia it forwards me to the consent page, and I click allow. Then it redirects back to jellyfin but gets stuck on a white page showing Logging in... The problem might be that I am using Cloudflare as a DNS/proxy, and they don't allow anything except HTML, so I turned off proxying for jellyfin. And somehow my cert seems to be only for my root domain and it is not a wildcard cert.

This is my config:

SSO-Auth.xml
<?xml version="1.0" encoding="utf-8"?>
<PluginConfiguration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <SamlConfigs />
  <OidConfigs>
    <item>
      <key>
        <string>Authelia</string>
      </key>
      <value>
        <PluginConfiguration>
          <OidEndpoint>https://auth.mydomain.com/.well-known/openid-configuration</OidEndpoint>
          <OidClientId>jellyfin</OidClientId>
          <OidSecret>REDACTED</OidSecret>
          <Enabled>true</Enabled>
          <EnableAuthorization>true</EnableAuthorization>
          <EnableAllFolders>true</EnableAllFolders>
          <EnabledFolders />
          <AdminRoles>
            <string>admins</string>
          </AdminRoles>
          <Roles>
            <string>user</string>
            <string>admins</string>
          </Roles>
          <EnableFolderRoles>false</EnableFolderRoles>
          <FolderRoleMappings />
          <RoleClaim>groups</RoleClaim>
          <OidScopes>
            <string>groups</string>
          </OidScopes>
          <DefaultProvider>Jellyfin.Server.Implementations.Users.DefaultAuthenticationProvider </DefaultProvider>
        </PluginConfiguration>
      </value>
    </item>
  </OidConfigs>
</PluginConfiguration>
configuration.yml
      - id: jellyfin
        description: JellyFin
        secret: REDACTED
        public: false
        authorization_policy: two_factor
        audience: []
        scopes:
          - openid
          - groups
          - email
          - profile
        redirect_uris:
          - https://jelly.mydomain.com/sso/OID/r/Authelia
        grant_types:
          - refresh_token
          - authorization_code
        response_types:
          - code
        response_modes:
          - form_post
          - query
          - fragment
        userinfo_signing_algorithm: none

image
This is the screen after clicking allow in Authelia

Q: What are the strings in folderRoleMapping

In the readme example, you have the following:

{
  "folderRoleMapping": [
    {
      "role": "allowed-to-watch-movies",
      "folders": [
        "cc7df17e2f3509a4b5fc1d1ff0a6c4d0",
        "f137a2dd21bbc1b99aa5c0f6bf02a805"
      ]
    }
  ]
}

What are the strings in the folders array? Are those IDs? Where can I find these?

CI: Automatic packaging & release using GH Actions, push to cdnjs

Related to #36

Here's what I did, personally:
Setup a GH action that builds using jprm, and pushes build artifacts to:

https://github.com/matthewstrasiotto/jellyfin-plugin-sso/tree/nightly-build

Now the following URL acts as a reference to the manifest + assets

https://cdn.jsdelivr.net/gh/matthewstrasiotto/jellyfin-plugin-sso@nightly-build/manifest.json

jsdelivr can serve from github, but caches content to limit requests / bandwidth to github

I'd need to also build a workflow that triggers on actual releases (this is feasible but I need to look into how to do it right a little more) and pushes that to a latest-release branch.

For actual releases, we can tag the corresponding nightly release branch, and make sure the correct ref is used in that actions' manifest

Redirect URL is case sensitive which causes trouble

Describe the bug
The redirect url which jellyfin uses contains OID which is in all-caps which causes trouble with Authentik (for example)
To Reproduce
Steps to reproduce the behavior:

  1. Attempt to Login
  2. SSO is denied
{
    "geo": {
        censored
    },
    "message": "Invalid redirect URI used by provider",
    "expected": [
        "https://jellyfin/sso/OID/r/authentik"
    ],
    "provider": {
        "pk": 13,
        "app": "authentik_providers_oauth2",
        "name": "jellyfin",
        "model_name": "oauth2provider"
    },
    "http_request": {
        "args": {},
        "path": "/application/o/token/",
        "method": "POST"
    },
    "redirect_uri": "https://jellyfin/sso/oid/r/authentik"
}

Expected behavior
na
Screenshots
na
Configuration
na

Versions (please complete the following information):

  • OS: Ubuntu docker
  • Browser: safari
  • Jellyfin Version: 10.8.0
  • Plugin Version: 3.3.0.0

Additional context
Add any other context about the problem here. Was the plugin built from source?

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

Originally posted by @matthewstrasiotto in #2 (comment)

  • Optionally, (and later) write frontend that autmatically injects this into the user's disclaimer settings
    • can probably use css to distinguish provider graphics

An example - Here, I:

  • add links that I style to match the native buttons (Forgot password, Quick Connect, etc),
    • Password Reset via authelia,
    • Server homepage
  • use css to hide the Forgot Password button, since my Audentity Provider is LDAP, and authelia provides password reset functionality (There's no setting for this)
<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.

"Error Processing Request" before attempting login, with "Value cannot be null" in the logs

Describe the bug
Attempting to log in with SSO causes an opaque error that I am unsure how to troubleshoot further.

To Reproduce
Steps to reproduce the behavior:

After configuring SSO from the admin panel, I opened another window to attempt to log in with a user (that is not yet created in Jellyfin), at https://jellyfin.<mydomain>/sso/OID/r/keycloak.

Expected behavior
Redirect to Keycloak, log in, redirect back to Jellyfin WebUI.

Configuration

{
  "keycloak": {
    "OidEndpoint": "https://keycloak.<domain>/realms/<realm>",
    "OidClientId": "jellyfin",
    "OidSecret": "<redacted>",
    "Enabled": true,
    "EnableAuthorization": true,
    "EnableAllFolders": true,
    "EnabledFolders": [],
    "AdminRoles": [
      "jellyfin-admin"
    ],
    "Roles": [
      "jellyfin-user"
    ],
    "EnableFolderRoles": false,
    "FolderRoleMapping": [],
    "RoleClaim": "resource_access.jellyfin.roles",
    "OidScopes": [
      "roles"
    ],
    "DefaultProvider": "Jellyfin.Server.Implementations.Users.DefaultAuthenticationProvider",
    "CanonicalLinks": {}
  }
}

Versions (please complete the following information):

  • OS: Kubernetes on K3OS
  • Browser: Firefox
  • Jellyfin Version: 10.8.4
  • Plugin Version: 3.4.0.0

Logs

This the message I see in the Jellyfin pod logs.

[23:26:15] [INF] [8] Jellyfin.Plugin.SSO_Auth.Api.SSOController: SSO Controller initialized
[23:26:15] [ERR] [8] Jellyfin.Server.Middleware.ExceptionMiddleware: Error processing request. URL GET /sso/OID/r/keycloak.
System.ArgumentNullException: Value cannot be null. (Parameter 'key')
   at System.Collections.Generic.Dictionary`2.FindValue(TKey key)
   at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
   at Jellyfin.Plugin.SSO_Auth.Api.SSOController.OidPost(String provider, String state)
   at lambda_method586(Closure , Object , Object[] )
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Jellyfin.Server.Middleware.ServerStartupMessageMiddleware.Invoke(HttpContext httpContext, IServerApplicationHost serverApplicationHost, ILocalizationManager localizationManager)
   at Jellyfin.Server.Middleware.WebSocketHandlerMiddleware.Invoke(HttpContext httpContext, IWebSocketManager webSocketManager)
   at Jellyfin.Server.Middleware.IpBasedAccessValidationMiddleware.Invoke(HttpContext httpContext, INetworkManager networkManager)
   at Jellyfin.Server.Middleware.LanFilteringMiddleware.Invoke(HttpContext httpContext, INetworkManager networkManager, IServerConfigurationManager serverConfigurationManager)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Jellyfin.Server.Middleware.QueryStringDecodingMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.ReDoc.ReDocMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Jellyfin.Server.Middleware.RobotsRedirectionMiddleware.Invoke(HttpContext httpContext)
   at Jellyfin.Server.Middleware.LegacyEmbyRouteRewriteMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.ResponseCompression.ResponseCompressionMiddleware.InvokeCore(HttpContext context)
   at Jellyfin.Server.Middleware.ResponseTimeMiddleware.Invoke(HttpContext context, IServerConfigurationManager serverConfigurationManager)
   at Jellyfin.Server.Middleware.ExceptionMiddleware.Invoke(HttpContext context)

Allow configuration of OIDC scopes to request to support role-claim

Is your feature request related to a problem? Please describe.

I recently decided to try to configure role-based access. I used the following configuration:

---
authelia:
  OidEndpoint: https://authelia.example.com/.well-known/openid-configuration/
  OidClientId: jellyfin
  OidSecret: <omitted>
  Enabled: true
  EnableAuthorization: true
  EnableAllFolders: true
  EnabledFolders: []
  Roles: ["jellyfin_user"]
  AdminRoles: ["jellyfin_admin"]
  EnableFolderRoles: false
  FolderRoleMapping: []
  RoleClaim: groups

Fields of interest are here:

authelia:
  Roles: ["jellyfin_user"]
  AdminRoles: ["jellyfin_admin"]
  RoleClaim: groups

However, I found the response was "Error. Check Permissions", with the following log entry:

[12:57:50] [WRN] [7] Jellyfin.Plugin.SSO_Auth.Api.SSOController: OpenID user su has one or more incorrect role claims: [{"Type": "jti", "Value": "***redacted***"}, {"Type": "name", "Value": "Superuser"}, {"Type": "preferred_username", "Value": "su"}, {"Type": "rat", "Value": "1651114669"}, {"Type": "sub", "Value": "su"}]. Expected any one of: ["jellyfin_user"]

Per the authelia oidc config spec: https://www.authelia.com/docs/configuration/identity-providers/oidc.html#groups , I learned that the "groups" attribute requires the client to request the "groups" scope.

To test this, I modified the request to include this scope:

https://github.com/9p4/jellyfin-plugin-sso/compare/main...matthewstrasiotto:authelia_groups?expand=1

diff --git a/SSO-Auth/Api/SSOController.cs b/SSO-Auth/Api/SSOController.cs
index ad60764..f24e44e 100644
--- a/SSO-Auth/Api/SSOController.cs
+++ b/SSO-Auth/Api/SSOController.cs
@@ -76,7 +76,7 @@ public SSOController(ILogger<SSOController> logger, ISessionManager sessionManag
                 ClientId = config.OidClientId,
                 ClientSecret = config.OidSecret,
                 RedirectUri = GetRequestBase() + "/sso/OID/r/" + provider,
-                Scope = "openid profile",
+                Scope = "openid profile groups",
             };
             options.Policy.Discovery.ValidateEndpoints = false; // For Google and other providers with different endpoints
             var oidcClient = new OidcClient(options);
@@ -243,7 +243,7 @@ public async Task<ActionResult> OidChallenge(string provider)
                 ClientId = config.OidClientId,
                 ClientSecret = config.OidSecret,
                 RedirectUri = GetRequestBase() + "/sso/OID/r/" + provider,
-                Scope = "openid profile"
+                Scope = "openid profile groups"
             };
             options.Policy.Discovery.ValidateEndpoints = false; // For Google and other providers with different endpoints
             var oidcClient = new OidcClient(options);

And indeed, requesting "groups" correctly confers user roles as well as admin roles.
It appears that for some OIDC providers, (Authelia, at the very least), additional scopes may need to be requested.

Describe the solution you'd like

Rather than hard-code these scopes into the request as I did to test the matter, I propose adding an additional config option, "additional oidc scopes" (or something) that allows the user to specify additional scopes to include in the request.

Describe alternatives you've considered

Hard-code this in, allow more opinionated provider presets (Preset, keycloak, preset authelia, etc), for my own use-case I could probably have supplied a list of usernames i wanted to get admin, but this defeats the purpose of membership based role checking. (All of these ideas are bad)

Failing to set up with Nextcloud OIDC Provider app

Describe the bug
When I try to setup authorization using OIDC provider, jellyfin throws 500 with following in log:

System.InvalidOperationException: Error loading discovery document: Error connecting to https://nc/index.php/apps/oidc/jwks. The JSON value could not be converted to System.String. Path: $.keys[0].use | LineNumber: 0 | BytePositionInLine: 29..
Apr 10 12:24:31 testarossa jellyfin[61989]:  ---> System.Text.Json.JsonException: The JSON value could not be converted to System.String. Path: $.keys[0].use | LineNumber: 0 | BytePositionInLine: 29.
Apr 10 12:24:31 testarossa jellyfin[61989]:  ---> System.InvalidOperationException: Cannot get the value of a token type 'StartArray' as a string.

To Reproduce
Steps to reproduce the behavior:

  1. Install SSO plugin in jellyfin, OIDC plugin in Nextcloud
  2. Attempt to set plugin up: curl -X POST -H "Content-Type: application/json" -d '{"oidEndpoint": "https://nc", "oidClientId": "cidwasremoved", "oidSecret": "secretwasremoved", "enabled": true, "enableAuthorization": true, "enableAllFolders": false, "enabledFolders": [], "adminRoles": ["admin"], "roles": [], "enableFolderRoles": true, "folderRoleMapping": [], "roleClaim": "roles"}' "https:/jf//sso/OID/Add/nc?api_key=apikeywasremoved" (probably invalid config, but I dont think this will change the error)
  3. Open login page
  4. See error

Expected behavior
Nextcloud login page

Versions (please complete the following information):

  • OS: Debian GNU/Linux 11 (bullseye) (Raspberry Pi OS)
  • Browser: Firefox 100.0b3
  • Jellyfin Version: 10.8
  • Plugin Version: 3.2.0.0

Additional context
I assume plugin expects use key to be string, but OIDC plugin returns list:
image

Allow custom usernames on "first" SSO sign-in

When the sign-in flow is occurring, sometimes the user doesn't already exist in Jellyfin. It would be nice to allow the user to create a username when this "first login" is happening instead of having to rely on sub/preferred_username to auto-populate it. This would be especially helpful when creating accounts with Google where no such claim exists.

Nested role JSON responses cannot be configured

I try to setup an managed access control from keycloak. Unfortunately I'm not able to do so and need some help. The SSO connection itself is working and I can access jellyfin with a keycloak user. But I'm not able to create a user with admin privileges at the moment.

Within my access token I have the following structure for the roles

"resource_access":{"jellyfin-auth-client":{"roles":["jellyfin-admin"]}},"scope":"openid profile email"

The oid api config looks like this, as you can see I define the role claim and admin roles to the matching results in the access token. But somehow the users are not created with admin privileges and in the logs I can see that the users are assigned to an InvalidAuthProvider.

{
    ...
    
    "Enabled": true,
    "EnableAuthorization": true,
    "EnableAllFolders": true,
    "EnabledFolders": [],
    "AdminRoles": [
        "jellyfin-admin"
    ],
    "Roles": [],
    "EnableFolderRoles": false,
    "FolderRoleMapping": [],
    "RoleClaim": "resource_access.jellyfin-auth-client"
}

Console log:

[13:49:17] [INF] [36] Jellyfin.Plugin.SSO_Auth.Api.SSOController: SSO Controller initialized
[13:49:17] [INF] [36] Jellyfin.Plugin.SSO_Auth.Api.SSOController: SSO user doesn't exist, creating...
[13:49:18] [INF] [36] Jellyfin.Plugin.SSO_Auth.Api.SSOController: Auth request created...
[13:49:18] [INF] [36] Emby.Server.Implementations.Session.SessionManager: Current/Max sessions for user user5: 0/0
[13:49:18] [INF] [36] Emby.Server.Implementations.Session.SessionManager: Creating new access token for user d5772d84-f5d6-4f97-8bd6-0a85e6eba4f9
[13:49:18] [WRN] [36] Jellyfin.Server.Implementations.Users.UserManager: User user5 was found with invalid/missing Authentication Provider Jellyfin.Plugin.SSO_Auth.Api.SSOController. Assigning user to InvalidAuthProvider until this is corrected
[13:49:18] [WRN] [37] Jellyfin.Server.Implementations.Users.UserManager: User user5 was found with invalid/missing Authentication Provider Jellyfin.Plugin.SSO_Auth.Api.SSOController. Assigning user to InvalidAuthProvider until this is corrected
[13:49:18] [WRN] [3] Jellyfin.Server.Implementations.Users.UserManager: User user5 was found with invalid/missing Authentication Provider Jellyfin.Plugin.SSO_Auth.Api.SSOController. Assigning user to InvalidAuthProvider until this is corrected
[13:49:22] [WRN] [21] Jellyfin.Server.Implementations.Users.UserManager: User user5 was found with invalid/missing Authentication Provider Jellyfin.Plugin.SSO_Auth.Api.SSOController. Assigning user to InvalidAuthProvider until this is corrected

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.