[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:
- Setup auth0
- Configure the plugin with oidc
- https://myjellyfin.example.com/sso/SAML/p/clientid
- 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)
Could you upload your XML configuration for the plugin with the secret parts redacted?
<?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>
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.
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.
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!
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?
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.
This is another compelling reason for https://github.com/9p4/jellyfin-plugin-sso/pull/34#issuecomment-1141460148 to go through
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