Clicking "Save SSO Settings" when "Enable SSO" is checked can cause the "Issuer" field to get changed (in particular, URLs without a trailing slash will have one added). I assume this happens somewhere in the config validation/normalization, but I haven't looked much further.
Since jose checks for an exact issuer match, this can cause SSO to fail with "Invalid SSO token." (user-facing) and JWTClaimValidationFailed/ERR_JWT_CLAIM_VALIDATION_FAILED/"Failed to verify SSO id_token" (in the logs, see below).
Check "Enable SSO" and set the "Issuer" to https://example.com
Click "Save SSO Settings" and refresh the page
The "Issuer" is now set to https://example.com/, which will cause SSO to fail if the identity provider returns the URL without a trailing slash as the iss value
Environment
Latest docker image (v1)
Logs or screenshots
Short video demonstrating the config value changing (I am refreshing the page after saves):
608915653-5b06efed-c4a6-47d7-b944-def36e16b849.mp4
When properly setting up SSO and attempting to complete the auth flow, the logged error will look like the following:
{"level":"error","time":"2026-06-17T00:39:05.931Z","service":"fluxer-api","env":"production","logger":"SsoService","error":{"type":"JWTClaimValidationFailed","message":"unexpected \"iss\" claim value","stack":"JWTClaimValidationFailed: unexpected \"iss\" claim value\n at validateClaimsSet (file:///usr/src/app/fluxer_api/node_modules/.pnpm/jose@6.1.3/node_modules/jose/dist/webapi/lib/jwt_claims_set.js:114:15)\n at jwtVerify (file:///usr/src/app/fluxer_api/node_modules/.pnpm/jose@6.1.3/node_modules/jose/dist/webapi/jwt/verify.js:9:21)\n at async SsoService.verifyIdToken (/usr/src/app/fluxer_api/src/api/auth/services/SsoService.ts:611:23)\n at async SsoService.resolveClaims (/usr/src/app/fluxer_api/src/api/auth/services/SsoService.ts:553:14)\n at async SsoService.completeLogin (/usr/src/app/fluxer_api/src/api/auth/services/SsoService.ts:319:18)\n at async AuthRequestService.toSsoCompleteResponse (/usr/src/app/fluxer_api/src/api/auth/AuthRequestService.ts:383:18)\n at async Object.handler (/usr/src/app/fluxer_api/src/api/auth/AuthController.ts:104:19)\n at async dispatch (file:///usr/src/app/fluxer_api/node_modules/.pnpm/hono@4.12.2/node_modules/hono/dist/compose.js:22:17)\n at async handler (file:///usr/src/app/fluxer_api/node_modules/.pnpm/hono@4.12.2/node_modules/hono/dist/hono-base.js:118:39)\n at async dispatch (file:///usr/src/app/fluxer_api/node_modules/.pnpm/hono@4.12.2/node_modules/hono/dist/compose.js:22:17)","code":"ERR_JWT_CLAIM_VALIDATION_FAILED","name":"JWTClaimValidationFailed","claim":"iss","reason":"check_failed","payload":{"iss":"https://example.com","aud":["1516580774764351578"],"iat":1781656745,"exp":1781660345,"auth_time":1781656739,"at_hash":"CD3h8zmT_AAtZkEGHFq_LQ","sub":"82824507223445504"}},"msg":"Failed to verify SSO id_token"}
You can see in "payload" that the SSO provider returns "iss":"https://example.com", which will not match the https://example.com/ in the config.
4 comments
Comment by @vesaber
Comment by @jameslindesay
Comment by @jbcarreon123
Comment by @mrjasonzee