{"record":{"id":"9ecc30f59a1a30c8","repo":"spring-projects/spring-security","slug":"targeturi-cannot-contain-query-or-fragment-parts","errorCode":null,"errorMessage":"targetUri cannot contain query or fragment parts","messagePattern":"targetUri cannot contain query or fragment parts","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/DPoPProofContext.java","lineNumber":178,"sourceCode":"\t\t\treturn new DPoPProofContext(this.dPoPProof, this.method, this.targetUri, this.accessToken);\n\t\t}\n\n\t\tprivate void validate() {\n\t\t\tif (!\"GET\".equals(this.method) && !\"HEAD\".equals(this.method) && !\"POST\".equals(this.method)\n\t\t\t\t\t&& !\"PUT\".equals(this.method) && !\"PATCH\".equals(this.method) && !\"DELETE\".equals(this.method)\n\t\t\t\t\t&& !\"OPTIONS\".equals(this.method) && !\"TRACE\".equals(this.method)) {\n\t\t\t\tthrow new IllegalArgumentException(\"method is invalid\");\n\t\t\t}\n\t\t\tURI uri;\n\t\t\ttry {\n\t\t\t\turi = new URI(this.targetUri);\n\t\t\t\turi.toURL();\n\t\t\t}\n\t\t\tcatch (Exception ex) {\n\t\t\t\tthrow new IllegalArgumentException(\"targetUri must be a valid URL\", ex);\n\t\t\t}\n\t\t\tif (uri.getQuery() != null || uri.getFragment() != null) {\n\t\t\t\tthrow new IllegalArgumentException(\"targetUri cannot contain query or fragment parts\");\n\t\t\t}\n\t\t}\n\n\t}\n\n}\n","sourceCodeStart":160,"sourceCodeEnd":185,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/DPoPProofContext.java#L160-L185","documentation":"DPoPProofContext.validate rejects target URIs that contain a query string or fragment. Per RFC 9449, the DPoP proof htu claim must be the target URI without query or fragment components, so a URI with either is considered invalid input. This is thrown as an IllegalArgumentException after the URI passed URL parsing.","triggerScenarios":"Constructing a DPoPProofContext with a targetUri like \"https://server/oauth2/token?grant_type=client_credentials\" or \"https://server/oauth2/token#frag\".","commonSituations":"Passing the full request URL (which includes query params) instead of the bare endpoint URL, or appending OAuth parameters to the token endpoint URL when they belong in the POST body.","solutions":["Strip the query and fragment before building the proof: use uriComponentsBuilder.replaceQuery(null).fragment(null).build().toUriString().","Pass only the endpoint base URL for the token/resource request.","If you need the request URL, reconstruct htu from scheme, host, port, and path only.","Validate with uri.getQuery() == null && uri.getFragment() == null before calling the proof builder."],"exampleFix":"// before\nnew DPoPProofContext(\"POST\", \"https://server/oauth2/token?grant_type=x\", ...); // throws\n// after\nnew DPoPProofContext(\"POST\", \"https://server/oauth2/token\", ...);","handlingStrategy":"validation","validationCode":"URI uri = URI.create(targetUri);\nif (uri.getQuery() != null || uri.getFragment() != null) {\n    throw new IllegalArgumentException(\"targetUri must not contain query or fragment\");\n}","typeGuard":"boolean isBareEndpointUrl(String s) {\n    URI u = URI.create(s);\n    return u.getQuery() == null && u.getFragment() == null;\n}","tryCatchPattern":null,"preventionTips":["Strip query/fragment before passing URLs to DPoP proof builders.","Remember OAuth parameters belong in the POST body, not the token endpoint URL.","Derive htu from the request's scheme/host/port/path only."],"tags":["dpop","url","validation","oauth2","spring-security"],"backgroundTag":"invalid-url","analyzedSha":"96852e8860138a482cb13d1479573f24ff6443c6","analyzedAt":"2026-09-10T23:25:23.477Z","contentChangedAt":"2026-09-10T23:25:23.477Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}