{"record":{"id":"e6cc721901c18640","repo":"gotify/server","slug":"issuer-url-q-is-not-a-valid-url-w","errorCode":null,"errorMessage":"issuer url %q is not a valid url: %w","messagePattern":"issuer url %q is not a valid url: %w","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"api/oidc.go","lineNumber":432,"sourceCode":"\t\treturn \"\", err\n\t}\n\treturn hex.EncodeToString(nonce), nil\n}\n\n// resolveUser looks up, links, or creates the user bound to an OIDC identity.\n//\n//  1. Look up the user by OIDC id (<iss>#<sub>). If found, use it.\n//  2. Otherwise look up a user by the username claim. If one exists, link it to\n//     this OIDC identity, which requires GOTIFY_OIDC_LINK_BY_USERNAME and\n//     that the user is not already bound to a different identity.\n//  3. Otherwise auto-register a new user, which requires GOTIFY_OIDC_AUTOREGISTER.\nfunc (a *OIDCAPI) resolveUser(idToken *oidc.IDTokenClaims, info *oidc.UserInfo) (*model.User, int, error) {\n\tissuer := idToken.GetIssuer()\n\tif issuer == \"\" {\n\t\treturn nil, http.StatusInternalServerError, errors.New(\"issuer claim was empty\")\n\t}\n\tif _, err := url.Parse(issuer); err != nil {\n\t\treturn nil, http.StatusInternalServerError, fmt.Errorf(\"issuer url %q is not a valid url: %w\", issuer, err)\n\t}\n\tif strings.Contains(issuer, \"#\") {\n\t\treturn nil, http.StatusInternalServerError, fmt.Errorf(\"issuer url %q may not contain a fragment\", issuer)\n\t}\n\tsubject := info.GetSubject()\n\tif subject == \"\" {\n\t\treturn nil, http.StatusInternalServerError, errors.New(\"subject claim was empty\")\n\t}\n\toidcID := issuer + \"#\" + subject\n\n\tuser, err := a.DB.GetUserByOIDC(oidcID)\n\tif err != nil {\n\t\treturn nil, http.StatusInternalServerError, fmt.Errorf(\"database error: %w\", err)\n\t}\n\n\thasAdminGroup, status, err := a.resolvePermission(idToken.Claims, info.Claims)\n\tif err != nil {\n\t\tlog.Err(err).Str(\"oidc_id\", oidcID).Interface(\"idTokenClaims\", idToken.Claims).Interface(\"userinfoClaims\", info.Claims).Msg(\"OIDC: resolve permission\")","sourceCodeStart":414,"sourceCodeEnd":450,"githubUrl":"https://github.com/gotify/server/blob/14bfc256276775c425f988d621dccfe705de18ac/api/oidc.go#L414-L450","documentation":"In resolveUser, the `iss` claim from the ID token is validated before deriving the user's oidcID. If `url.Parse` on the issuer fails, the handler returns 500 'issuer url %q is not a valid url: %w'. (Note: Go's url.Parse rarely errors, so this mostly guards truly malformed issuer strings.)","triggerScenarios":"An ID token whose issuer claim is empty-adjacent garbage or otherwise unparseable as a URL — typically a misconfigured IdP or a forged/malformed token.","commonSituations":"IdP configured with a wrong issuer string (e.g. missing scheme); tokens minted by a test/staging IdP with a malformed issuer; token from a different, badly configured provider accepted due to lax issuer checks.","solutions":["Fix the issuer URL in the OIDC provider configuration (must be an absolute URL with scheme)","Ensure the server trusts only the intended provider/issuer","Inspect the wrapped parse error to see which character broke parsing"],"exampleFix":"// before (IdP config)\nissuer: \"localhost:8080\"\n// after\nissuer: \"https://localhost:8080\"","handlingStrategy":"validation","validationCode":"function isValidIssuer(iss) {\n  try { const u = new URL(iss); return u.protocol === 'https:' || u.protocol === 'http:'; }\n  catch { return false; }\n}\nif (!isValidIssuer(idToken.iss)) rejectToken(idToken);","typeGuard":"function isParseableURL(s) {\n  try { new URL(s); return true; } catch { return false; }\n}","tryCatchPattern":"try {\n  new URL(issuer);\n} catch (err) {\n  return res.status(500).json({error: `issuer url ${issuer} is not a valid url`});\n}","preventionTips":["Configure the IdP with an absolute issuer URL including scheme","Pin expected issuer values server-side and validate tokens against them","Log the raw iss claim when rejecting tokens"],"tags":["oidc","url-validation","authentication","configuration"],"backgroundTag":"invalid-issuer-url","analyzedSha":"14bfc256276775c425f988d621dccfe705de18ac","analyzedAt":"2026-09-05T12:52:36.781Z","contentChangedAt":"2026-09-05T12:52:36.781Z","schemaVersion":2},"datasetVersion":"2026-09-12T17:17:11.597Z"}