{"record":{"id":"3d5a3f20a4072bf4","repo":"decolua/9router","slug":"token-endpoint-must-be-a-valid-url","errorCode":null,"errorMessage":"token_endpoint must be a valid URL","messagePattern":"token_endpoint must be a valid URL","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/lib/oauth/kiroExternalIdp.js","lineNumber":22,"sourceCode":"  \"login.windows.net\",\n]);\n\nconst DEFAULT_REGION = \"us-east-1\";\nconst DEFAULT_EXPIRES_IN = 3600;\n\nfunction normalizeString(value) {\n  return typeof value === \"string\" ? value.trim() : \"\";\n}\n\nexport function validateMicrosoftTokenEndpoint(rawEndpoint) {\n  const tokenEndpoint = normalizeString(rawEndpoint);\n  if (!tokenEndpoint) throw new Error(\"token_endpoint is required\");\n\n  let parsed;\n  try {\n    parsed = new URL(tokenEndpoint);\n  } catch {\n    throw new Error(\"token_endpoint must be a valid URL\");\n  }\n\n  if (parsed.protocol !== \"https:\") {\n    throw new Error(\"token_endpoint must use https\");\n  }\n\n  const host = parsed.hostname.toLowerCase();\n  if (!MICROSOFT_TOKEN_ENDPOINT_HOSTS.has(host)) {\n    throw new Error(\"token_endpoint must be a Microsoft login endpoint\");\n  }\n\n  return parsed.toString();\n}\n\nexport function normalizeScope(scopes) {\n  if (Array.isArray(scopes)) {\n    return scopes.map(normalizeString).filter(Boolean).join(\" \");\n  }","sourceCodeStart":4,"sourceCodeEnd":40,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/lib/oauth/kiroExternalIdp.js#L4-L40","documentation":"After requiring a non-empty token_endpoint, the validator parses it with new URL(). Anything that isn't an absolute, well-formed URL (relative path, missing scheme, bad characters, bare hostname) throws this error.","triggerScenarios":"Passing \"login.microsoftonline.com/.../token\" (no scheme), \"/oauth2/token\" (relative), \"https://{tenant}/token\" (unparsed placeholder left in), or a value with spaces/control characters.","commonSituations":"Config template placeholder like ${TENANT} or <tenant-id> never substituted; user pasted the endpoint without the https:// prefix; shell/env quoting stripped part of the URL.","solutions":["Prefix the scheme if missing: value.startsWith(\"https\") ? value : `https://${value}` — but prefer storing the full absolute URL.","Substitute tenant/placeholder values before validation; check for \"{\" \"<\" \"$\" remnants in the configured endpoint.","Copy the token_endpoint verbatim from the IdP's openid-configuration document to guarantee it is absolute and correct."],"exampleFix":"// before\nvalidateMicrosoftTokenEndpoint(\"login.microsoftonline.com/common/oauth2/v2.0/token\");\n// after\nvalidateMicrosoftTokenEndpoint(\"https://login.microsoftonline.com/common/oauth2/v2.0/token\");","handlingStrategy":"validation","validationCode":"function isAbsoluteHttpsUrl(u) {\n  if (typeof u !== \"string\") return false;\n  try { return new URL(u.trim()).protocol === \"https:\"; } catch { return false; }\n}\nif (!isAbsoluteHttpsUrl(raw)) throw new Error(`token_endpoint must be an absolute https URL, got: ${raw}`);","typeGuard":"function isUrl(x) {\n  if (typeof x !== \"string\") return false;\n  try { new URL(x); return true; } catch { return false; }\n}","tryCatchPattern":"try {\n  endpoint = validateMicrosoftTokenEndpoint(raw);\n} catch (err) {\n  if (err.message === \"token_endpoint must be a valid URL\") {\n    throw new Error(`Configured token_endpoint \"${raw}\" is not an absolute URL — include the https:// scheme and substitute all placeholders`);\n  }\n  throw err;\n}","preventionTips":["Always store the full absolute URL including https://.","Search config values for leftover placeholders ({, }, <, >, $) before use.","Paste token_endpoint directly from the tenant's openid-configuration JSON."],"tags":["oauth","microsoft","url-validation"],"backgroundTag":"invalid-url","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}