I finished implementing auth0 to my django and react app. But ever since I signup with a new user, the 'sub' is saved as the username instead of the real 'name'. Is there a way to fix this?
settings.py
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'django.contrib.auth.backends.RemoteUserBackend',
]
JWT_AUTH = {
'JWT_PAYLOAD_GET_USERNAME_HANDLER':
'posts.utils.jwt_get_username_from_payload_handler',
'JWT_DECODE_HANDLER':
'posts.utils.jwt_decode_token',
'JWT_ALGORITHM': 'RS256',
'JWT_AUDIENCE': '<API_IDENTIFIER>',
'JWT_ISSUER': 'https://<APP_DOMAIN>/',
'JWT_AUTH_HEADER_PREFIX': 'Bearer',
}
posts.utils.py
from django.contrib.auth import authenticate
import json
import jwt
import requests
def jwt_get_username_from_payload_handler(payload):
username = payload.get('sub').replace('|', '.')
authenticate(remote_user=username)
return username
def jwt_decode_token(token):
header = jwt.get_unverified_header(token)
jwks = requests.get(
'https://{}/.well-known/jwks.json'.format('<APP_DOMAIN>')).json()
public_key = None
for jwk in jwks['keys']:
if jwk['kid'] == header['kid']:
public_key = jwt.algorithms.RSAAlgorithm.from_jwk(json.dumps(jwk))
if public_key is None:
raise Exception('Public key not found.')
issuer = 'https://{}/'.format('<APP_DOMAIN>')
return jwt.decode(token, public_key, audience='<API_IDENTIFIER>', issuer=issuer, algorithms=['RS256'])
Here are some of my codes I used at the auth0 tutorial. I tried changing the username payload handler but it didn't work yet.
Related
I've been following this tutorial:https://medium.com/#gerrysabar/implementing-google-login-with-jwt-in-django-for-restful-api-authentication-eaa92e50522d to try and implement a google based login from my front end, so far everything works in terms of the creating the account based on the google token and then creating the token using RefreshToken.for_user(). However, I tried testing my application by making a very simple view to test it via the permission classes as below:
class VerifyAuthView(APIView):
permission_classes = (IsAuthenticated,)
def post(self,request):
return Response({"status":"true"})
I get returned a 401 error when I try and access this.
I have a couple ideas what it might be such as:
I have noticed in the django admin panel under tokens there is none listed even immediately after creation, maybe the token is only being generated and not saving although I can't work out why
I've seen a couple of people say it could be to do with rest_framework permissions classes, although changing these hasn't helped so far
My Views.py (also contains my VerifyAuthView as above):
from django.contrib.auth.base_user import BaseUserManager
from django.contrib.auth.hashers import make_password
from rest_framework.utils import json
from rest_framework.views import APIView,status
from rest_framework.response import Response
import requests
from rest_framework_simplejwt.tokens import RefreshToken
from django.contrib.auth.models import User
class GoogleView(APIView):
def post(self,request):
payload = {'access_token':request.data.get("access_token")} #validating token
req = requests.get('https://www.googleapis.com/oauth2/v2/userinfo',params= payload)
data = json.loads(req.text)
if 'error' in data:
content = {'message': 'Invalid Google Token'}
return Response(content)
email = data['email']
#check if user has authenticated before or not
try:
user = User.objects.get(email=data['email'])
except User.DoesNotExist:
#Create user if they have not logged in before
user = User()
user.username = data['email']
user.password = make_password(BaseUserManager().make_random_password())
user.email = data['email']
user.save()
#Creating access token for user
token = RefreshToken.for_user(user)
response = {}
response['username'] = user.username
response['access_token'] = str(token.access_token)
response['refresh_token'] = str(token)
return Response(response)
And in my settings.py my rest_framework attribute is set up as follows:
CORS_ORIGIN_ALLOW_ALL = True
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES':[
#for demo purposes will need to be changed for privacy concerns
'rest_framework.permissions.AllowAny',
],
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
],
}
I am making the request on the frontend in react via axios as follows:
const headers = { Authorization: 'Bearer ' + accesstoken};
let res = await axios.post("http://localhost:8000/rest-auth/token/verify-access-token/",
{headers}
);
I am using django-rest-jwt for authentication in my app.
By default it user username field to autenticate a user but I want let the users login using email or username.
Is there any mean supported by django-rest-jwt to accomplish this.
I know the last option would be write my own login method.
No need to write a custom authentication backend or custom login method.
A Custom Serializer inheriting JSONWebTokenSerializer, renaming the 'username_field' and overriding def validate() method.
This works perfectly for 'username_or_email' and 'password' fields where the user can enter its username or email and get the JSONWebToken for correct credentials.
from rest_framework_jwt.serializers import JSONWebTokenSerializer
from django.contrib.auth import authenticate, get_user_model
from django.utils.translation import ugettext as _
from rest_framework import serializers
from rest_framework_jwt.settings import api_settings
User = get_user_model()
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
jwt_get_username_from_payload = api_settings.JWT_PAYLOAD_GET_USERNAME_HANDLER
class CustomJWTSerializer(JSONWebTokenSerializer):
username_field = 'username_or_email'
def validate(self, attrs):
password = attrs.get("password")
user_obj = User.objects.filter(email=attrs.get("username_or_email")).first() or User.objects.filter(username=attrs.get("username_or_email")).first()
if user_obj is not None:
credentials = {
'username':user_obj.username,
'password': password
}
if all(credentials.values()):
user = authenticate(**credentials)
if user:
if not user.is_active:
msg = _('User account is disabled.')
raise serializers.ValidationError(msg)
payload = jwt_payload_handler(user)
return {
'token': jwt_encode_handler(payload),
'user': user
}
else:
msg = _('Unable to log in with provided credentials.')
raise serializers.ValidationError(msg)
else:
msg = _('Must include "{username_field}" and "password".')
msg = msg.format(username_field=self.username_field)
raise serializers.ValidationError(msg)
else:
msg = _('Account with this email/username does not exists')
raise serializers.ValidationError(msg)
In urls.py:
url(r'{Your url name}$', ObtainJSONWebToken.as_view(serializer_class=CustomJWTSerializer)),
Building on top of Shikhar's answer and for anyone coming here looking for a solution for rest_framework_simplejwt (since django-rest-framework-jwt seems to be dead, it's last commit was 2 years ago) like me, here's a general solution that tries to alter as little as possible the original validation from TokenObtainPairSerializer:
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
class CustomJWTSerializer(TokenObtainPairSerializer):
def validate(self, attrs):
credentials = {
'username': '',
'password': attrs.get("password")
}
# This is answering the original question, but do whatever you need here.
# For example in my case I had to check a different model that stores more user info
# But in the end, you should obtain the username to continue.
user_obj = User.objects.filter(email=attrs.get("username")).first() or User.objects.filter(username=attrs.get("username")).first()
if user_obj:
credentials['username'] = user_obj.username
return super().validate(credentials)
And in urls.py:
url(r'^token/$', TokenObtainPairView.as_view(serializer_class=CustomJWTSerializer)),
Found out a workaround.
#permission_classes((permissions.AllowAny,))
def signin_jwt_wrapped(request, *args, **kwargs):
request_data = request.data
host = request.get_host()
username_or_email = request_data['username']
if isEmail(username_or_email):
# get the username for this email by model lookup
username = Profile.get_username_from_email(username_or_email)
if username is None:
response_text = {"non_field_errors":["Unable to login with provided credentials."]}
return JSONResponse(response_text, status=status.HTTP_400_BAD_REQUEST)
else:
username = username_or_email
data = {'username': username, 'password':request_data['password']}
headers = {'content-type': 'application/json'}
url = 'http://' + host + '/user/signin_jwt/'
response = requests.post(url,data=dumps(data), headers=headers)
return JSONResponse(loads(response.text), status=response.status_code)
I check that whether the text that I received is a username or an email.
If email then I lookup the username for that and then just pass that to /signin_jwt/
authentication.py
from django.contrib.auth.models import User
class CustomAuthBackend(object):
"""
This class does the athentication-
using the user's email address.
"""
def authenticate(self, request, username=None, password=None):
try:
user = User.objects.get(email=username)
if user.check_password(password):
return user
return None
except User.DoesNotExist:
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
settings.py
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'app_name.authentication.CustomAuthBackend',
]
How it works:
If user try to authenticate using their username django will look at the ModelBackend class. However, if the user adds its email instead, django will try ModelBackend but will not find the logic needed, then will try the CustomAuthBackend class making it work the authentication.
Alternatively, this new DRF Auth project dj-rest-auth seems to provide support for log in by username or email through djangorestframework-simplejwt.
dj-rest-auth works better for authentication and authorization. By default dj-rest-auth provides - username, email and password fields for login. User can provide email and password or username and password. Token will be generated, if the provided values are valid.
If you need to edit these login form, extend LoginSerializer and modify fields. Later make sure to add new custom serializer to settings.py.
REST_AUTH_SERIALIZERS = {
'LOGIN_SERIALIZER': 'yourapp.customlogin_serializers.CustomLoginSerializer'
}
Configuring dj-rest-auth is bit tricky, since it has an open issue related to the refresh token pending. There is workaround suggested for that issue, so you can follow below links and have it configured.
https://medium.com/geekculture/jwt-authentication-in-django-part-1-implementing-the-backend-b7c58ab9431b
https://github.com/iMerica/dj-rest-auth/issues/97
If you use the rest_framework_simplejwt this is a simple mode. views.py
from rest_framework_simplejwt.tokens import RefreshToken
from django.http import JsonResponse
from rest_framework import generics
class EmailAuthToken(generics.GenericAPIView):
def post(self, request):
user_data = request.data
try:
user = authenticate(request, username=user_data['username_or_email'], password=user_data['password'])
if user is not None:
login(request, user)
refresh = RefreshToken.for_user(user)
return JsonResponse({
'refresh': str(refresh),
'access': str(refresh.access_token),
}, safe=False, status=status.HTTP_200_OK)
else:
return JsonResponse({
"detail": "No active account found with the given credentials"
}, safe=False, status=status.HTTP_200_OK)
except:
return Response({'error': 'The format of the information is not valid'}, status=status.HTTP_401_UNAUTHORIZED)
I am trying to implement login functionality using mongoengine and django.
I have included 'mongoengine.django.mongo_auth' in INSTALLED_APPS
Following are my settings.py from mongoengine site.
MONGOENGINE_USER_DOCUMENT = 'mongoengine.django.auth.User'
AUTH_USER_MODEL = 'mongo_auth.MongoUser'
SESSION_ENGINE = 'mongoengine.django.sessions'
AUTHENTICATION_BACKENDS = (
'mongoengine.django.auth.MongoEngineBackend',
)
This is from models.py
class UserInfo(User,DynamicDocument):
address = EmbeddedDocumentField(Address)
dob = DateTimeField(required = True)
sex = StringField(max_length = 1, choices = SEX)
primary_number = LongField(required = True)
And following is from views.py
def LoginOrCreateUser(request):
formAuth = AuthenticationForm(data=(request.POST or None))
if(request.method=='POST'):
if(formAuth.is_valid()):
if(formAuth.clean_email()):
if(formAuth.clean_password()):
formAuth.save(True)
user=authenticate(username=formAuth.cleaned_data['username'],password = formAuth.cleaned_data['password1'])
login(request,user)
return HttpResponse('New User Success')
This code gives me error <obj_id "user"> is NOT JSON serializable.
The error is raised for login, so I guess here login API is provided by django but the user we are providing to it is the value got from authenticate which is mongoengine's provided api.
I looked into the auth.py of django and mongoengine. So, we don't have login API in mongoengine. And the authenticate of django returns the user instance, while authenticate of mongoengine returns a string i.e. username.
Any suggestions here or mistakes I am making in the implementation here.
André I just tried you're example but it gives me
NameError at /game/
global name 'jsonResponse' is not defined
If I import jsonResponse like
from django.http import HttpResponse,JsonResponse
then gives me
ImportError at /game/ cannot import name JsonResponse
I just only want to manage simple user auth with my mongoengine backend.
Thanks.
1st question, are you trying to customize the User object? Or you want to use default Django User model? I ask because you should set MONGOENGINE_USER_DOCUMENT and AUTH_USER_MODEL if you do want to make a custom model.
I will show you how I'm authenticating using mongoengine x Django:
On settings.py
connect('localhost')
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.dummy',
}
}
AUTHENTICATION_BACKENDS = (
'mongoengine.django.auth.MongoEngineBackend'
)
SESSION_ENGINE = 'mongoengine.django.sessions'
On views.py
from django.contrib.auth import authenticate, login, logout
from django.http import HttpResponse
def jsonResponse(responseDict):
return HttpResponse(simplejson.dumps(responseDict), mimetype='application/json')
def createSession(request):
if not request.is_ajax():
return jsonResponse({'error':True})
if not request.session.exists(request.session.session_key):
request.session.create()
data = extractDataFromPost(request)
email = data["email"]
password = data["password"]
try:
user = User.objects.get(username=email)
if user.check_password(password):
user.backend = 'mongoengine.django.auth.MongoEngineBackend'
user = authenticate(username=email, password=password)
login(request, user)
request.session.set_expiry(3600000) # 1 hour timeout
return jsonResponse(serializeUser(user))
else:
result = {'error':True, 'message':'Invalid credentials'}
return jsonResponse(result)
except User.DoesNotExist:
result = {'error':True, 'message':'Invalid credentials'}
return jsonResponse(result)
def serializeUser(user):
return simplejson.dumps({'email': user.email, 'username': user.username, 'id': str(user.id), 'firstName': user.first_name, 'lastName': user.last_name})
After reading lots of stuff I could make it work this way following MongoEngine User authentication (django).
I'm not setting anything on models.py since I use the default Django user model.
Regards
I have an app running using django.
Now i want only users that are authenticated via an openldap server to see "their view" (therefore i only need their uid after successfull authentication)
How can i achieve that?
I guess django-auth-ldap is the way to go, so i tried the whole day to get to know where the authentication actually takes place and how i can get the uid of the user requesting a view.
I used the documentation for the settings.py but i could not find out how to "actually use" it. Maybe someone can point me in the right direction?
settings.py:
import ldap
AUTHENTICATION_BACKENDS = (
'django_auth_ldap.backend.LDAPBackend',
'django.contrib.auth.backends.ModelBackend',
)
AUTH_LDAP_SERVER_URI = "ldap://123.60.56.61"
AUTH_LDAP_BIND_DN = ""
AUTH_LDAP_BIND_PASSWORD = ""
AUTH_LDAP_USER_DN_TEMPLATE = "uid=%(user)s,dc=rd,dc=corpintra,dc=net"
(By the way: i already can perform ldap-searche with python-ldap and get results like ldapsearch on the command line, so everything else works just fine...)
What do i need in my views?
Thanks for your help!
Here's a snippet from one of our sites.
# Django Auth Ldap
main_dn = 'dc=____,dc=organisation,dc=com'
groups_dn = 'ou=Groups,'+main_dn
users_dn = 'ou=Users,'+main_dn
AUTHENTICATION_BACKENDS = (
'django_auth_ldap.backend.LDAPBackend',
'django.contrib.auth.backends.ModelBackend',
)
AUTH_LDAP_SERVER_URI = "ldap://ldap.organisation.com"
AUTH_LDAP_BIND_DN = 'cn=___,'+main_dn
AUTH_LDAP_BIND_PASSWORD = "__________________"
AUTH_LDAP_USER_SEARCH = LDAPSearch(users_dn, 2, "(uid=%(user)s)")
AUTH_LDAP_USER_ATTR_MAP = {
"first_name": "givenName",
"last_name": "sn",
"email": "mail"
}
AUTH_LDAP_MIRROR_GROUPS = True
AUTH_LDAP_ALWAYS_UPDATE_USER = True
AUTH_LDAP_GROUP_TYPE = PosixGroupType()
AUTH_LDAP_GROUP_SEARCH = LDAPSearch(groups_dn, ldap.SCOPE_SUBTREE, "(objectClass=posixGroup)")
AUTH_LDAP_USER_FLAGS_BY_GROUP = {
"is_staff": "cn=admins,"+groups_dn,
"is_superuser": "cn=developers,"+groups_dn,
}
EDIT:
Since the question is "What do i need in my views?", The answer is that this config will save the user's uid as the username field on the User model, so in your views, you need
uid = request.user.username
Hopefully this gets you up and running.
Since django-auth-ldap is a normal Django authentication backend, request.user should be set to the authenticated user (assuming you have the standard middleware installed—see the Django docs). With a typical setup, request.user.username will be the uid of the user's DN. If you need more information, you can get it from request.user.ldap_user.
I'm not use django-auth-ldap, i write my own ldap authentification backend's.
#define your backend authentification
AUTHENTICATION_BACKENDS = (
'netipa.managment.ldapwm.netipaldapdjango.NetIpaLdap',
#'django.contrib.auth.backends.ModelBackend ',
)
For more information about extend the User model, see https://docs.djangoproject.com/en/1.5/topics/auth/customizing/#specifying-a-custom-user-model
#!/usr/bin/env python
#coding:utf-8
# Author: peter --<pjl#hpc.com.py>
# Created: 22/04/12
from django.conf import settings
import ldap
#this is a abstrac class to add some custom fields to the default django User model
#see https://docs.djangoproject.com/en/1.5/topics/auth/customizing/#specifying-a-custom-user-model, for more informacion
from netipa.contrib.accesos.models import LdapUsers as User
from django.contrib.auth.backends import ModelBackend
#import logging
class NetIpaLdap(object):
supports_inactive_user = False
def authenticate(self, username=None, password=None):
# logging.basicConfig(format='%(asctime)s %(message)s',filename="/tmp/auth.log",level=logging.DEBUG)
if username is None:
return None
try:
# a variable's define in settings
ip_server = settings.LDAP_BASES.get('ip')
userdn = settings.LDAP_BASES.get('users')
ldap.initialize('ldap://%s' % ip_server)
lop = ldap.simple_bind_s(
"uid=%s,%s" % (username, userdn),
password
)
except ldap.LDAPError, e:
print e
return None
except Exception,e:
print e
return None
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
ldap_at = lop.search(settings.LDAP_BASES.get('users'),
fil='uid=%s' % username,
types=1,
attr=['uidnumber', 'mail'])
user = User(username=username, password=password, ldap_id=ldap_at[0][-1].get('uidnumber')[0],
ldap_mail=ldap_at[0][-1].get('mail')[0])
user.is_staff = True
user.is_superuser = True
user.save()
return user
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
Here is my extend User Class Model
from django.db import models
from django.contrib.auth.models import AbstractUser
# Create your models here.
class LdapUsers(AbstractUser):
ldap_id = models.IntegerField()
ldap_mail = models.EmailField()
I'm using django-social-auth to do Facebook server-side authentication, and I am getting this error that I've been unable to track down: AuthFailed('There was an error authenticating the app').
I do NOT get this error when I test locally and I have the Facebook app site URL pointing to localhost.
However, I do get this error every time I point the Facebook app site URL to my dev instance -- e.g., https://dev.mydomain.com.
I have not been able to find a list of things that might cause the AuthFailed('There was an error authenticating the app') error.
Also, I have not been able to find others that have used django-social-auth successfully and solved this problem already.
I've found some useful info at the following:
https://github.com/omab/django-social-auth/blob/master/doc/configuration.rst
http://django-social-auth.readthedocs.org/en/latest/
https://developers.facebook.com/docs/technical-guides/login/ https://developers.facebook.com/docs/howtos/login/server-side-login/
Lastly, I want to do server-side authentication because I want to retrieve users' name, email, and photo. However, if that can be done via client-side authentication, I guess I could try that.
Code below...
in settings.py:
LOGIN_URL = '/account/login/'
LOGIN_REDIRECT_URL = '/'
LOGIN_ERROR_URL = '/account/login/' # added when trying to debug Facebook error
AUTHENTICATION_BACKENDS = (
'social_auth.backends.facebook.FacebookBackend',
'django.contrib.auth.backends.ModelBackend',
)
FACEBOOK_APP_ID = 'xxx'
FACEBOOK_API_SECRET = 'xxxxxx'
FACEBOOK_EXTENDED_PERMISSIONS = ['email']
SOCIAL_AUTH_CREATE_USERS = True
SOCIAL_AUTH_FORCE_RANDOM_USERNAME = False
SOCIAL_AUTH_DEFAULT_USERNAME = 'socialauth_user'
SOCIAL_AUTH_COMPLETE_URL_NAME = 'socialauth_complete'
SOCIAL_AUTH_ERROR_KEY = 'socialauth_error'
SOCIAL_AUTH_REDIRECT_IS_HTTPS = True # force https in dev and production
# is SOCIAL_AUTH_LOGIN_REDIRECT_URL or SOCIAL_AUTH_BACKEND_ERROR_URL needed???
SOCIAL_AUTH_PIPELINE = (
'social_auth.backends.pipeline.social.social_auth_user',
'social_auth.backends.pipeline.associate.associate_by_email',
'social_auth.backends.pipeline.misc.save_status_to_session',
'apps.dsa_pipeline.redirect_to_form',
'apps.dsa_pipeline.username',
'apps.dsa_pipeline.phone',
'social_auth.backends.pipeline.user.create_user',
'social_auth.backends.pipeline.social.associate_user',
'social_auth.backends.pipeline.social.load_extra_data',
'social_auth.backends.pipeline.user.update_user_details',
'apps.dsa_pipeline.profile',
)
in dsa_pipeline.py:
from django.http import HttpResponseRedirect
from urllib2 import urlopen
def redirect_to_form(*args, **kwargs):
if not kwargs['request'].session.get('saved_username') and kwargs.get('user') is None:
return HttpResponseRedirect('/account/signup_dsa/')
def username(request, *args, **kwargs):
if kwargs.get('user'):
username = kwargs['user'].username
else:
username = request.session.get('saved_username')
return {'username': username}
def phone(request, *args, **kwargs):
if kwargs.get('phone'):
phone = kwargs['phone'].phone
else:
phone = request.session.get('saved_phone')
return {'phone': phone}
def profile(backend, details, response, social_user, uid, user, *args, **kwargs):
profile = user.profile
if not profile.phone:
ph = kwargs.get('phone')
if ph:
profile.phone = ph
profile.save()
in urls.py:
urlpatterns = patterns("",
url(r"^$", direct_to_template, {"template": "homepage.html"}, name="home"),
url(r"^admin/", include(admin.site.urls)),
# profiles
url(r'^', include("apps.profiles.urls")),
url(r"^account/signup/$", SignupView.as_view(), name="account_signup"),
url(r"^account/signup_dsa/$", SignupViewDSA.as_view(), name="account_signup_dsa"),
url(r"^account/", include("account.urls")),
# Django Social Auth
url(r'', include('social_auth.urls')),
Thanks for any help!
That AuthFailed error is raised here, I would check the values for client_id, redirect_uri and client_secret being used a few lines above and compare against your app configuration on Facebook.