Provider-abstracted login
One consistent JWT response shape for every provider. Sign in with Google or GitHub through the same pattern - swap providers without rewriting your client.
Every Django project ends up needing the same thing eventually: let users sign in with Google or GitHub, hand back a token, and don’t break anything when they later connect a second provider. Getting that right means writing CSRF-safe state handling, normalizing two very different OAuth flows, issuing JWTs, and untangling account-linking edge cases - by hand, every time.
django-social-auth-rest does all of that for you. Add it to INSTALLED_APPS, drop in your provider credentials, and you’ve got production-ready social login speaking the same JWT language as the rest of your API.
Provider-abstracted login
One consistent JWT response shape for every provider. Sign in with Google or GitHub through the same pattern - swap providers without rewriting your client.
Account linking, done right
Users can connect or disconnect social accounts from an existing profile at any time, with unique constraints that keep one social identity tied to one user.
JWT-first, not bolted on
Built directly on djangorestframework-simplejwt. Access and refresh tokens come back from every login, link, and signup call - no extra wiring.
CSRF-safe by design
The OAuth flow uses signed, time-limited state tokens out of the box, so you don’t need to implement custom CSRF protection for the authorization redirect flow.
Abuse-resistant
Every auth endpoint ships with configurable rate limiting (SOCIAL_AUTH_THROTTLE_RATE), so a single misbehaving client can’t hammer your login endpoint.
Hooks into your app's logic
Django signals fire on every key event - new_user_registered, login_successful, link_account_successful, unlink_account_successful - so welcome emails and analytics are a @receiver away.
Install it.
pip install django-social-auth-restRegister the app and JWT auth.
# settings.pyINSTALLED_APPS = [ # ... "rest_framework", "django_social_auth_rest",]
REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": ( "rest_framework_simplejwt.authentication.JWTAuthentication", ),}
SIMPLE_JWT = { "AUTH_HEADER_TYPES": ("Bearer",), "ACCESS_TOKEN_LIFETIME": timedelta(minutes=60), "REFRESH_TOKEN_LIFETIME": timedelta(days=1), "ROTATE_REFRESH_TOKENS": True, "BLACKLIST_AFTER_ROTATION": True,}Configure the Signup Serializer for remove duplicates
class SignupSerializer(serializers.Serializer): ... def validate(self, attrs): attrs = super().validate(attrs) email = attrs.get("email", "").strip().lower() if SocialAccountLinked.objects.filter(email_linked=email).exists(): raise serializers.ValidationError( {"email": ["An account already exists for this email."]} ) return attrsIt is important to validate the email in the signup serializer to avoid duplicate accounts being created for the same email address. This ensures that if a user tries to sign up with an email that is already linked to a social account, they will receive a validation error.
Set a real state-signing salt.
# settings.pySOCIAL_AUTH_STATE_SALT = "a-long-random-secret-value"Drop in credentials for the providers you want.
# settings.pyGITHUB_CLIENT_ID = "your-github-client-id"GITHUB_CLIENT_SECRET = "your-github-client-secret"GOOGLE_CLIENT_ID = "your-google-client-id"# ... add other providers as neededProviders without credentials stay disabled automatically - no dead endpoints to guard against.
Wire up the URLs and migrate.
# urls.pyurlpatterns = [ path("api/social-auth/", include("django_social_auth_rest.urls")), path("api/social-auth/", include("django_social_auth_rest.urls.github")), path("api/social-auth/", include("django_social_auth_rest.urls.google")), # ... add other providers as needed]Run migrations.
python manage.py migratePOST /api/social-auth/github/login/Content-Type: application/json
{ "code": "<AUTHORIZATION_CODE>", "state": "<SIGNED_STATE_TOKEN>"}{ "access": "<JWT_ACCESS_TOKEN>", "refresh": "<JWT_REFRESH_TOKEN>"}POST /api/social-auth/google/login/Content-Type: application/json
{ "token": "<GOOGLE_ID_TOKEN>"}{ "access": "<JWT_ACCESS_TOKEN>", "refresh": "<JWT_REFRESH_TOKEN>"}Linking, unlinking, and checking which providers a user has connected follow the exact same shape - every endpoint returns JWTs or a clean 204 No Content.