jellyfin-plugin-sso icon indicating copy to clipboard operation
jellyfin-plugin-sso copied to clipboard

[auth0] Allow customization of the username claim

Open anthr76 opened this issue 3 years ago • 9 comments

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)

anthr76 avatar Jun 25 '22 03:06 anthr76

Could you upload your XML configuration for the plugin with the secret parts redacted?

9p4 avatar Jun 25 '22 16:06 9p4

<?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>auth0</string>
      </key>
      <value>
        <PluginConfiguration>
          <OidEndpoint>https://redact.us.auth0.com/</OidEndpoint>
          <OidClientId></OidClientId>
          <OidSecret></OidSecret>
          <Enabled>true</Enabled>
          <EnableAuthorization>true</EnableAuthorization>
          <EnableAllFolders>true</EnableAllFolders>
          <EnabledFolders />
          <AdminRoles>
            <string>admins</string>
          </AdminRoles>
          <Roles>
            <string>family</string>
            <string>admins</string>
          </Roles>
          <EnableFolderRoles>false</EnableFolderRoles>
          <FolderRoleMappings />
          <RoleClaim>https://kutara\.io/groups</RoleClaim>
          <OidScopes />
          <DefaultProvider>Jellyfin.Server.Implementations.Users.DefaultAuthenticationProvider</DefaultProvider>
        </PluginConfiguration>
      </value>
    </item>
  </OidConfigs>
</PluginConfiguration>

anthr76 avatar Jun 26 '22 01:06 anthr76

Seems like the error is thrown here: https://github.com/jellyfin/jellyfin/blob/53209830e7b566949c16b43c864f6f85336cb92c/Jellyfin.Server.Implementations/Users/UserManager.cs#L122

Can you check on https://oidcdebugger.com to see what the preferred_username claim is?

As for your original request of changing the configured username claim, once we implement user linking (#34) changing the claim won't really matter anymore since the user (during the first sign-in process) would choose a username and we would end up using an attribute that is wholly unique and guaranteed by the OpenID spec.

9p4 avatar Jun 26 '22 06:06 9p4

TIL oidcdebugger. That's a really nice tool.

Here's the entire payload:

{
   "https://kutara.io/groups": [
      "admins",
      "family"
   ],
   "given_name": "Anthony A",
   "family_name": "redact",
   "nickname": "hello",
   "name": "Anthony A redact",
   "picture": "https://lh3.googleusercontent.com/redact",
   "locale": "en",
   "updated_at": "2022-06-26T13:43:25.928Z",
   "email": "[email protected]",
   "email_verified": true,
   "iss": "https://redact.us.auth0.com/",
   "sub": "google-oauth2|redact",
   "aud": "redact",
   "iat": ,
   "exp": ,
   "at_hash": "",
   "c_hash": "",
   "nonce": ""
}

It appears auth0 doesn't send a preferred_username with the way auth0 works I usually lean on email as the source of truth for usernames.

anthr76 avatar Jun 26 '22 14:06 anthr76

So as a workaround I'm able to set this rule in auth0:

function (user, context, callback) {
  user.preferred_username = user.email;
  return callback(null, user, context);
}

To set preferred_username to be email. This succeeded!

anthr76 avatar Jun 26 '22 14:06 anthr76

Alright, seems like the issue is in this chunk of code: https://github.com/9p4/jellyfin-plugin-sso/blob/6a0fcfe7118265bd5a5cb128de9ad524895564a6/SSO-Auth/Api/SSOController.cs#L184

The validation is performed incorrectly. The code should look something like

if (StateManager.Username == null)
{
    foreach (var claim in result.User.Claims)
    {
        if (claim.Type == "sub")
        {
            StateManager[state].Username = claim.Value;
        }
    }
}

if (config.Roles.Length == 0)
{
    StateManager[state].Valid = true;
}

And remove lines 104-107 (https://github.com/9p4/jellyfin-plugin-sso/blob/6a0fcfe7118265bd5a5cb128de9ad524895564a6/SSO-Auth/Api/SSOController.cs#L104)

I don't have access to a computer for the next couple of weeks, so I can't test that patch, but could you take a look and see if the patch fixes the issue for you?

9p4 avatar Jun 27 '22 12:06 9p4

Sure! Im about a 5 day old Jellyfin user so I might have a few bumps getting the plugin for myself going but I surely poke at it in a few days.

anthr76 avatar Jun 27 '22 13:06 anthr76

This is another compelling reason for https://github.com/9p4/jellyfin-plugin-sso/pull/34#issuecomment-1141460148 to go through

strazto avatar Jun 29 '22 15:06 strazto

personally, I kind of like the idea of making the claim customizable, at least for a while to give admins time to adjust, while warning that sub is preferred to preferred_username (a kind of soft-deprecation) It would simplify the logic without breaking current iterations.

I'd also like the default username for a new user to be given by a configurable claim, too, maybe, so using sub or a configured claim for canonical name, and for display name, users can choose which claim, eg, preferred_name

strazto avatar Jun 29 '22 15:06 strazto