@@ -279,22 +279,49 @@ async def test_authorize_with_an_unregistered_redirect_uri_is_rejected_directly(
279279
280280
281281@requirement ("hosting:auth:as:redirect-uri-scheme" )
282- async def test_a_non_loopback_http_redirect_uri_is_accepted_at_registration (
282+ @pytest .mark .parametrize (
283+ "redirect_uri" ,
284+ [
285+ "http://evil.example/callback" ,
286+ "javascript:alert(1)" ,
287+ "data:text/html,<script>alert(1)</script>" ,
288+ "file:///etc/passwd" ,
289+ "https://client.example.com/callback#fragment" ,
290+ ],
291+ )
292+ async def test_registration_rejects_redirect_uris_without_https_or_loopback (
283293 as_app : tuple [httpx .AsyncClient , InMemoryAuthorizationServerProvider ],
294+ redirect_uri : str ,
284295) -> None :
285- """A registration carrying a non-HTTPS, non-loopback redirect URI is accepted.
296+ """The registration endpoint rejects redirect URIs without HTTPS or a loopback host."""
297+ http , provider = as_app
298+ body = oauth_client_metadata ().model_dump (mode = "json" , exclude_none = True )
299+ body ["redirect_uris" ] = [redirect_uri ]
286300
287- The spec requires every redirect URI to be either HTTPS or a loopback host; the bundled
288- registration handler does not enforce this and registers `http://evil.example/callback`
289- successfully. See the divergence on the requirement.
290- """
301+ response = await http .post ("/register" , json = body )
302+
303+ assert response .status_code == 400
304+ assert response .json ()["error" ] == "invalid_redirect_uri"
305+ assert provider .clients == {}
306+
307+
308+ @requirement ("hosting:auth:as:redirect-uri-scheme" )
309+ async def test_registration_allows_https_and_loopback_redirect_uris (
310+ as_app : tuple [httpx .AsyncClient , InMemoryAuthorizationServerProvider ],
311+ ) -> None :
312+ """HTTPS redirect URIs and loopback HTTP redirect URIs remain valid."""
291313 http , provider = as_app
292314 body = oauth_client_metadata ().model_dump (mode = "json" , exclude_none = True )
293- body ["redirect_uris" ] = ["http://evil.example/callback" ]
315+ body ["redirect_uris" ] = [
316+ "https://client.example.com/callback?next=%2Fhome" ,
317+ "http://localhost:3000/callback" ,
318+ "http://127.0.0.1:3000/callback" ,
319+ "http://[::1]:3000/callback" ,
320+ ]
294321
295322 response = await http .post ("/register" , json = body )
296323
297324 assert response .status_code == 201
298325 info = OAuthClientInformationFull .model_validate_json (response .content )
299- assert [str (u ) for u in (info .redirect_uris or [])] == [ "http://evil.example/callback " ]
326+ assert [str (u ) for u in (info .redirect_uris or [])] == body [ "redirect_uris " ]
300327 assert info .client_id in provider .clients
0 commit comments