@@ -656,6 +656,50 @@ async def test_client_registration(self, test_client: httpx.AsyncClient, mock_oa
656656 # client_info["client_id"]
657657 # ) is not None
658658
659+ @pytest .mark .anyio
660+ async def test_client_registration_allows_loopback_redirect_uri (self , test_client : httpx .AsyncClient ):
661+ """Test client registration with an HTTP loopback redirect URI."""
662+ client_metadata = {
663+ "redirect_uris" : ["http://localhost:3030/callback" ],
664+ "client_name" : "Loopback Client" ,
665+ }
666+
667+ response = await test_client .post (
668+ "/register" ,
669+ json = client_metadata ,
670+ )
671+ assert response .status_code == 201 , response .content
672+ assert response .json ()["redirect_uris" ] == ["http://localhost:3030/callback" ]
673+
674+ @pytest .mark .anyio
675+ @pytest .mark .parametrize (
676+ "redirect_uri" ,
677+ [
678+ "http://client.example.com/callback" ,
679+ "javascript:alert(1)" ,
680+ "data:text/html,<script>alert(1)</script>" ,
681+ "file:///tmp/callback" ,
682+ "https://client.example.com/callback#fragment" ,
683+ "https://client.example.com/callback#" ,
684+ ],
685+ )
686+ async def test_client_registration_rejects_unsafe_redirect_uris (
687+ self , test_client : httpx .AsyncClient , redirect_uri : str
688+ ):
689+ """Test client registration rejects unsafe redirect URI schemes and fragments."""
690+ client_metadata = {
691+ "redirect_uris" : [redirect_uri ],
692+ "client_name" : "Test Client" ,
693+ }
694+
695+ response = await test_client .post (
696+ "/register" ,
697+ json = client_metadata ,
698+ )
699+ assert response .status_code == 400
700+ error_data = response .json ()
701+ assert error_data ["error" ] == "invalid_redirect_uri"
702+
659703 @pytest .mark .anyio
660704 async def test_client_registration_missing_required_fields (self , test_client : httpx .AsyncClient ):
661705 """Test client registration with missing required fields."""
0 commit comments