Open Jorncg opened 2 years ago
I think this repo may helps you to implement it. just try to call authentication third-parity API in your own endpoint API that implemented by ninja
You can still use django-allauth. I guess it's best way to implement social login. One day, I will implement it too as like u. At that day, I will post some tutorial for you. Please be patient. If you don't have much time, I leave some links you can reference.
https://django-allauth.readthedocs.io/en/latest/installation.html https://django-allauth.readthedocs.io/en/latest/faq.html#this-information-is-nice-and-all-but-i-need-more https://testdriven.io/blog/django-social-auth/
Hello, Did somebody successfully implemented python-social-auth with django ninja ?
I implemented it with dj-rest-auth. It's more restful package. Look. https://testdriven.io/blog/django-rest-auth/
This can be helpful.
I am searching for a way as well... so far I did not found any repo or package that can help with that
@Nils3311 Social-nets authentication is not API/OpenAPI specific
to implement it you can use any django app that does it with regular html pages - and then use session auth in NinjaAPI
f.e allauth - https://django-allauth.readthedocs.io/en/latest/
I finally did it with python-social-auth.
After login, I redirect the user to /login/success (on my backend) :
In settings :
LOGIN_REDIRECT_URL = f"/login/success"
Then in the view, I set the API token and redirect to my front :
@session_auth_router.get("/login/success")
def login_success(request):
response = HttpResponseRedirect(settings.FRONT_URL + "/play")
response.set_cookie("api_token", request.user.token)
return response
My whole API use a TokenAuth router, so I had to create a new session auth just for this view (so request.user is set in the previous view) :
session_auth_router = Router(auth=SessionAuth())
@yleclanche Thanks for sharing.
looking to implement allauth with JWT
@Nils3311 Social-nets authentication is not API/OpenAPI specific
to implement it you can use any django app that does it with regular html pages - and then use session auth in NinjaAPI
f.e allauth - https://django-allauth.readthedocs.io/en/latest/
Hi @vitalik congrats on the framework, it seems like it's become a go-to in Django. I think it would help a lot of people such as myself if you could please explain a little further even if it's just some broad steps.
I currently have django-allauth and its html templates working well, and I'm using django-ninja's ninja.security.django_auth
. Now I would like to use this from my frontend (React). I'm seeing django-allauth is logging the following steps:
"GET /accounts/google/login/?process=login HTTP/1.1" 200 1246
"POST /accounts/google/login/?process=login HTTP/1.1" 302 0
"GET /accounts/google/login/callback/?state=<some_state>&code=<some_code>&scope=email+profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+openid&authuser=0&prompt=consent HTTP/1.1" 302 0
React is expecting a token to ultimately store it in cookies. Previously I had managed to get this working with FastAPI (fastapi-users
) and React, where React would GET that last url (the one with the state and code params) and from that response it would extract response.data["access_token"]
, store it in cookies, and the frontend would now consider itself authenticated.
What is the equivalent here? How can I get that access token now that I'm using django-ninja? Apologies if this is a basic question but I'm pretty lost and other sites are not providing me with answers. Thanks in advance.
I may have figured it out now (I had spent a couple of days on this before I posted my previous request). Not sure if this is hacky or a bad practice but it seems to work.
Indeed django-ninja
has little to do with this process since essentially Django grabs the access token created with django-allauth
from the database and stores it in cookies, which React is able to grab. Btw this means I had to switch from ninja.security.django_auth
to ninja.security.HttpBearer
.
Broadly speaking I have:
from django.core.handlers.wsgi import WSGIRequest
from allauth.socialaccount.models import SocialToken
from django.contrib.auth.models import AbstractBaseUser, AnonymousUser
def login_view(request: WSGIRequest): user: AbstractBaseUser | AnonymousUser = request.user token_info: SocialToken | None = SocialToken.objects.filter(accountuser=user, accountprovider="google").first() 3 this requires SOCIALACCOUNT_STORE_TOKENS = True in settings.py if not token_info: return 404, {"message": "User token not found in database"}
response = HttpResponseRedirect("http://localhost:5173/get-token") # my React endpoint
response.set_cookie("access_token", token_info.token)
return response
3. I add that view to /auth/urls.py:
from django.urls import path from .views import login_view
urlpatterns = [path("auth/myloginview", login_view),]
4. And I add that into my root urls.py, ie. `path("myauth/", include("auth.urls"))`.
5. My React endpoint at /get-token has a useEffect that runs this function:
const getAccessTokenFromCookies = () => {
console.log("Getting token from cookies");
let cookieValue = document.cookie.replace(/(?:(?:^|.*;\s*)access_token\s*\=\s*([^;]*).*$)|^.*$/, "$1");
#console.log(`cookieValue = ${cookieValue}`);
if (!login) { # login() is basically a setState()
throw Error("login function should not be null");
}
login(cookieValue);
};
That login() function comes from some tutorial I followed which has you create a useToken() and useAuth() hooks / context but that's beyond the scope of this issue and probably varies from your chosen client-side implementation.
Idk if this is super correct but it gets the job done. I'm open to any reasons why this is not a good way of doing things though.
Hi Everyone. I wrote a quick and dirty social login function that takes advantage of django-allauth functionality. The code basically replicates the functionality of django-rest-auth's SocialLoginSerializer validation. So removes any dependencies on DRF and doesn't require python-social-auth. Tested and working with Google OAuth so far. I'm sure it can be improved upon, but hope it helps other as a starting point.
from allauth.socialaccount.helpers import complete_social_login
from allauth.socialaccount.models import SocialAccount, SocialApp, SocialLogin
from allauth.socialaccount.providers.google.views import GoogleOAuth2Adapter
from allauth.socialaccount.providers.oauth2.views import OAuth2Adapter
from django.contrib.auth import get_user_model
from django.http.request import HttpRequest
from ninja import ModelSchema, Router, Schema
router = Router(tags=["Registration"])
class Error(Schema):
message: str
class SocialAccountSchema(ModelSchema):
class Meta:
model = SocialAccount
fields = (
"id",
"provider",
"uid",
"last_login",
"date_joined",
)
class SocialLoginSchema(Schema):
access_token: str
def social_login(
request,
app: SocialApp,
adapter: OAuth2Adapter,
access_token: str,
response=None,
connect=True,
):
"""
Uses allauth to complete a social login
If connect is True, then a new social account will be connected to an existing user
Otherwise, raises an error if the email already exists
If the email does not exist, then a new user will be created
Apparently, its not very secure to use connect = True for lesser known social apps
"""
if not isinstance(request, HttpRequest):
request = request._request
token = adapter.parse_token({"access_token": access_token})
token.app = app
try:
response = response or {}
login: SocialLogin = adapter.complete_login(request, app, token, response)
login.token = token
complete_social_login(request, login)
except Exception as e:
return 400, {"message": f"Could not complete social login: {e}"}
if not login.is_existing:
User = get_user_model()
user = User.objects.filter(email=login.user.email).first()
if user:
if connect:
login.connect(request, user)
else:
return 400, {"errors": ["Email already exists"]}
else:
login.lookup()
login.save(request)
return 200, login.account
@router.post(
"/google-login",
response={200: SocialAccountSchema, 404: Error, 400: Error},
auth=None,
)
def google_login(request, payload: SocialLoginSchema):
try:
app = SocialApp.objects.get(name="Google")
except SocialApp.DoesNotExist:
return 404, {"message": "Google app does not exist"}
adapter = GoogleOAuth2Adapter(request)
return social_login(request, app, adapter, payload.access_token)
I've explored this issue for a while and concluded that using Firebase Auth is the most robust and straightforward solution. It supports popular providers such as Google, Instagram, Facebook, Apple, etc. Other methods appear more complex to integrate. Unfortunately, I haven't found an easy plug-and-play library for Django Ninja to handle this seamlessly. For those managing a production site, relying on external authentication providers like Auth0 or Firebase Auth seems to be the safest option, based on my research. There might be other options.
I've explored this issue for a while and concluded that using Firebase Auth is the most robust and straightforward solution. It supports popular providers such as Google, Instagram, Facebook, Apple, etc. Other methods appear more complex to integrate. Unfortunately, I haven't found an easy plug-and-play library for Django Ninja to handle this seamlessly. For those managing a production site, relying on external authentication providers like Auth0 or Firebase Auth seems to be the safest option, based on my research. There might be other options.
https://github.com/pennersr/django-allauth is a very robust and actively maintained solution with 8.8k stars and 597 contributors on Github . The main benefit is that you can register your social apps and manage social accounts in the Django admin. I think people were just struggling to connect their API views to the social login logic that allauth provides.
Yes, I know this one, wish I could use it but integration seems complex to me due to not great knowledge of social auth and JWT which adds complexity as I'm using https://github.com/eadwinCode/django-ninja-jwt
Most django-allauth examples are session based which doesn't help.
Agreed it seems a bit complex but if you inspect django-rest-auth you will see that its not actually that much code. They handle JWT with all-auth rather gracefully (just remember that most of the logic is contained in the serializers and not the views). Its worth it to avoid the vendor lock in and the future fees that a solution like Firebase entails IMO. Also this is a Django Ninja issue thread so I'm providing a solution that uses Django Ninja.
@leadrobot thanks for the snippet! Are you getting access_token on the frontend? It don't seems secure as it involves using client secret on the frontend.
@leadrobot thanks for the snippet! Are you getting access_token on the frontend? It don't seems secure as it involves using client secret on the frontend.
To me, I am using nuxt-auth and it works in server-side. So I guess there is no problem. The only exposing token is JWT (access token) which is from django server.
@leadrobot thanks for the snippet! Are you getting access_token on the frontend? It don't seems secure as it involves using client secret on the frontend.
Correct me if I'm wrong but you should only need to use the client ID on the frontend which is not a secret. I haven't got to the front end implementation but I was expecting to have a link like https://accounts.google.com/o/oauth2/v2/auth?client_id=myclientid&redirect_uri=somewhereinmyapp&response_type=token... then the frontend would get the token and hit the google-login endpoint to register/login. I could be way off base here. Please let me know if you see any security issues or I'm making bad assumptions.
As far as django-allauth is concerned, an official API is under development, and the specification (as well as a React example app) are already up for review. Note that this API is framework agnostic, it does not require Ninja nor DRF.
You can read up more here: https://allauth.org/news/2024/04/api-feedback/
After a long time lurking on this topic, I've implemented Google Authentication via custom implementation following Google docs. I've just found out that for me it is the simplest and most RESTful solution. It is not that hard, so I would maybe advise that as an option. After you create a user you can use whatever authentication you want in your app.
https://googleapis.dev/python/google-auth/latest/reference/google.oauth2.html
Thanks to the incredible work of @pennersr with allauth headless, Using ninja_jwt with allauth is as simple as...
from allauth.headless.tokens.sessions import SessionTokenStrategy
from django.http import HttpRequest
from ninja_jwt.tokens import SlidingToken
class TokenStrategy(SessionTokenStrategy):
def create_access_token(self, request: HttpRequest) -> str | None:
user = request.user
if user.is_authenticated:
return str(SlidingToken.for_user(user))
return None
Setup allauth and headless as per https://docs.allauth.org/en/latest/headless/index.html. Point HEADLESS_TOKEN_STRATEGY to above in settings and off you go.
Thanks to the incredible work of @pennersr with allauth headless, Using ninja_jwt with allauth is as simple as...
from allauth.headless.tokens.sessions import SessionTokenStrategy from django.http import HttpRequest from ninja_jwt.tokens import SlidingToken class TokenStrategy(SessionTokenStrategy): def create_access_token(self, request: HttpRequest) -> str | None: user = request.user if user.is_authenticated: return str(SlidingToken.for_user(user)) return None
Setup allauth and headless as per https://docs.allauth.org/en/latest/headless/index.html. Point HEADLESS_TOKEN_STRATEGY to above in settings and off you go.
@leadrobot Thanks for the example but, just to confirm, does this work with Google oauth? It seems in your example you are having django-ninja
create an access token, but doesn't Google provide the access token for you?
@Jsalaz1989 It really depends of your application and your needs. Google will provide you an access token and you can use that token for user authentication, but if you want to handle authentication by yourself inside your app, you will need to handle user storing inside database and issuing tokens from your application. Google access tokens are used for getting information and access to Google services, but they may not be used for your application where you'll maybe need some sort of token issuing and handling.
I need to implement a social login for my api with django ninja, but i don't find any example for this.
what is the best approach?