Repository: python-social-auth/social-core
Pull request #1190 (2025-06-21 10:43:00)

Fix SoundcloudOAuth2 user_data to use Authorization header and update test

**Title**: Fix SoundcloudOAuth2 to Use Authorization Header and Update Tests

**Description**:
This PR fixes a `401 Unauthorized` error in the `SoundcloudOAuth2` backend caused by an outdated `user_data` method. SoundCloud API now requires an `Authorization: OAuth <access_token>` header instead of passing `oauth_token` as a query parameter, as noted in their security updates: https://developers.soundcloud.com/blog/security-updates-api. The PR also updates the test suite to reflect these changes and adds a new test for the `user_data` method.

**Changes**:
- Updated `user_data` method in `social_core/backends/soundcloud.py` to use the `Authorization` header and change the endpoint from `/me.json` to `/me`.
- Updated `social_core/tests/backends/test_soundcloud.py` to use the new endpoint (`https://api.soundcloud.com/me`) and test the `Authorization` header.
- Added `test_user_data` to verify the `user_data` method with a mocked API response using `patch`.

**Before**:
```python
def user_data(self, access_token, *args, **kwargs):
    return self.get_json(
        "https://api.soundcloud.com/me.json", params={"oauth_token": access_token}
    )
```

**After**:
```python
def user_data(self, access_token, *args, **kwargs):
    return self.get_json(
        "https://api.soundcloud.com/me",
        headers={"Authorization": f"OAuth {access_token}"},
        params={"format": "json"},
    )
```

**Tested**:
- Tested locally with a Django project using `social-auth-app-django==5.4.3` and `social-core==4.6.1`.
- Ran tests with `python -m unittest social_core.tests.backends.test_soundcloud -v`:
  ```
  test_auth_url_parameters ... ok
  test_login ... ok
  test_partial_pipeline ... ok
  test_user_data ... ok
  Ran 4 tests in 0.013s
  OK
  ```

**Environment**:
- Python: 3.12
- `social-auth-app-django`: 5.4.3
- `social-core`: 4.6.1
- Operating System: Linux

**Additional Notes**:
- SoundCloud API documentation: https://developers.soundcloud.com/docs/api/explorer/open-api
- Tests use `patch` to mock API responses, consistent with the existing test suite.
- Code formatted with `black` and checked with `flake8`.
- Please review and let me know if additional changes or tests are needed.
```

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/python-social-auth/social-core/pull/1190?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=python-social-auth) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 77.80%. Comparing base [(`9ec58b9`)](https://app.codecov.io/gh/python-social-auth/social-core/commit/9ec58b9b858979109e9d511d55af16d4e3aff7b8?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=python-social-auth) to head [(`4c8acfa`)](https://app.codecov.io/gh/python-social-auth/social-core/commit/4c8acfaa37291b2c131b46f80d1f1834da48ef8d?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=python-social-auth).

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #1190      +/-   ##
==========================================
+ Coverage   77.78%   77.80%   +0.02%     
==========================================
  Files         352      352              
  Lines       10865    10878      +13     
  Branches      489      489              
==========================================
+ Hits         8451     8464      +13     
  Misses       2234     2234              
  Partials      180      180              
```

| [Flag](https://app.codecov.io/gh/python-social-auth/social-core/pull/1190/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=python-social-auth) | Coverage Δ | |
|---|---|---|
| [unittests](https://app.codecov.io/gh/python-social-auth/social-core/pull/1190/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=python-social-auth) | `77.80% <100.00%> (+0.02%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=python-social-auth#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/python-social-auth/social-core/pull/1190?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=python-social-auth).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=python-social-auth).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>

Patch:
diff --git a/pyproject.toml b/pyproject.toml
index 50b4ca27..70e25536 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -14,7 +14,7 @@ dev = [
   "pytest>=4.5",
   "responses==0.25.7",
   "types-defusedxml==0.7.0.20250516",
-  "types-oauthlib==3.2.0.20250516",
+  "types-oauthlib==3.3.0.20250622",
   "types-python-jose==3.5.0.20250531",
   "types-requests-oauthlib==2.0.0.20250516",
   "types-requests==2.32.4.20250611"
@@ -82,7 +82,7 @@ dev = [
   "pytest>=4.5",
   "responses==0.25.7",
   "types-defusedxml==0.7.0.20250516",
-  "types-oauthlib==3.2.0.20250516",
+  "types-oauthlib==3.3.0.20250622",
   "types-python-jose==3.5.0.20250531",
   "types-requests-oauthlib==2.0.0.20250516",
   "types-requests==2.32.4.20250611"
diff --git a/social_core/backends/soundcloud.py b/social_core/backends/soundcloud.py
index 0748fe0d..19de72d0 100644
--- a/social_core/backends/soundcloud.py
+++ b/social_core/backends/soundcloud.py
@@ -36,7 +36,9 @@ class SoundcloudOAuth2(BaseOAuth2):
     def user_data(self, access_token, *args, **kwargs):
         """Loads user data from service"""
         return self.get_json(
-            "https://api.soundcloud.com/me.json", params={"oauth_token": access_token}
+            "https://api.soundcloud.com/me",
+            headers={"Authorization": f"OAuth {access_token}"},
+            params={"format": "json"},
         )
 
     def auth_url(self):


Test patch:
diff --git a/social_core/tests/backends/test_soundcloud.py b/social_core/tests/backends/test_soundcloud.py
index d72ef1ac..d51348b8 100644
--- a/social_core/tests/backends/test_soundcloud.py
+++ b/social_core/tests/backends/test_soundcloud.py
@@ -1,11 +1,12 @@
 import json
+from unittest.mock import patch
 
 from .oauth import BaseAuthUrlTestMixin, OAuth2Test
 
 
 class SoundcloudOAuth2Test(OAuth2Test, BaseAuthUrlTestMixin):
     backend_path = "social_core.backends.soundcloud.SoundcloudOAuth2"
-    user_data_url = "https://api.soundcloud.com/me.json"
+    user_data_url = "https://api.soundcloud.com/me"
     expected_username = "foobar"
     access_token_body = json.dumps({"access_token": "foobar", "token_type": "bearer"})
     user_data_body = json.dumps(
@@ -40,14 +41,56 @@ class SoundcloudOAuth2Test(OAuth2Test, BaseAuthUrlTestMixin):
             "upload_seconds_left": 7200,
             "country": None,
             "uri": "https://api.soundcloud.com/users/10101010",
-            "avatar_url": "https://a1.sndcdn.com/images/"
-            "default_avatar_large.png?ca77017",
+            "avatar_url": "https://a1.sndcdn.com/images/default_avatar_large.png?ca77017",
             "plan": "Free",
         }
     )
 
-    def test_login(self) -> None:
-        self.do_login()
+    def test_login(self):
+        """Test standard login flow"""
+        assert self.user_data_body is not None
+        with patch.object(
+            self.backend, "user_data", return_value=json.loads(self.user_data_body)
+        ):
+            self.do_login()
 
-    def test_partial_pipeline(self) -> None:
-        self.do_partial_pipeline()
+    def test_partial_pipeline(self):
+        """Test partial pipeline flow"""
+        assert self.user_data_body is not None
+        with patch.object(
+            self.backend, "user_data", return_value=json.loads(self.user_data_body)
+        ):
+            self.do_partial_pipeline()
+
+    def test_user_data(self):
+        """Test user_data method with Authorization header"""
+        self.strategy.set_settings(
+            {
+                "SOCIAL_AUTH_SOUNDCLOUD_KEY": "test_client_id",
+                "SOCIAL_AUTH_SOUNDCLOUD_SECRET": "test_client_secret",
+            }
+        )
+
+        # Mock the HTTP request to the user data endpoint
+        with patch("social_core.backends.base.BaseAuth.request") as mock_request:
+            assert self.user_data_body is not None
+            mock_request.return_value.json.return_value = json.loads(
+                self.user_data_body
+            )
+            response = self.backend.user_data(access_token="foobar")  # noqa: S106
+
+            # Verify the request was made with the correct parameters
+            mock_request.assert_called_once_with(
+                "https://api.soundcloud.com/me",
+                headers={"Authorization": "OAuth foobar"},
+                params={"format": "json"},
+                method="GET",
+                data=None,
+                auth=None,
+            )
+
+            # Verify the response data
+            self.assertEqual(response["username"], self.expected_username)
+            self.assertEqual(response["permalink_url"], "http://soundcloud.com/foobar")
+            self.assertEqual(response["id"], 10101010)
+            self.assertEqual(response["full_name"], "Foo Bar")