I have been learning how to use djangorestframework token authentication using different blog posts and youtube videos.
As for reference, I was following the blog here: https://chrisbartos.com/articles/how-to-implement-token-authentication-with-django-rest-framework/
I could not understand how are we going to check the token before accessing any page. I mean, I am developing an app, that exposes the todos a user creates through the rest framework. I have added a login that saves a user and returns the token created for that person. Now,I want to check that token to find the todos api that the person created and view it in my browser in a seperate url.
As an example:
Once I login through localhost:8000/api/v1/login,
I should get the todos created by me at api/v1/todos in json rest api format.
And if I go to api/v1/todos/1/, it should give me the details of the todo, as I have created in the serializers.
I would like to add some more info:
So, say I have created a class for the login form. It will create a token for me.
So the following is the login in the views:
def signin(request):
username = password = ''
if request.method == 'POST':
username = request.POST['username']
password = request.POST['password']
user = authenticate(username=username, password=password)
if user is not None:
if user.is_active:
return HttpResponse('Logged In')
else:
return HttpResponse('Wrong credentials')
return render(request,'login.html')
So, I want to create a token for this. As mentioned in the djangorestframework documentation https://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication
it creates a seperate view for accessing the api-auth-token that is obtained from the function obtain_auth_token. But, how do I apply this function to save the token from current login in a class based view.
Also, how do I pass this in another class based view, such that it shows no authentication in case I have not logged in but gives me the api in json when authenticated?
https://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication
Add rest_framework.authentication.TokenAuthentication to the DEFAULT_AUTHENTICATION_CLASSES in the REST_FRAMEWORK options in your Django settings.py file.
Add rest_framework.authtoken to your INSTALLED_APPS in settings.py
You can use the #authentication_classes decorator before the views you want to protect like so:
#authentication_classes((TokenAuthentication,))
def sample_function(request):
You'll also need to create tokens for your users, which is documented in that initial link.
class loginview(APIView):
permission_classes = [
permissions.AllowAny # Anyone can Login
]
def post(self,request):
email_address = request.data.get('email')
user_request = get_object_or_404(
User,
email=email_address,
)
username = user_request.username
password = request.data.get("password")
user = authenticate(username=username, password=password)
id_u = user.id
if not user:
return Response({"error": "Login failed"},
status=status.HTTP_401_UNAUTHORIZED)
token, _ = Token.objects.get_or_create(user=user)
return Response({"token": token.key,'id':id_u})
Here is some sample code you can use to obtain Token while using the Login API From the App Frontend. Auth Token can be accessed from the Token model. Don't forget to add:
from rest_framework.authtoken.models import Token
Also Add rest_framework.authtoken to installed apps in the settings.py
Related
I want to write a decorator like the login_required decorator of Django to check the Azure AD authentication and the Django authentication at the same time. If one of the two is not true, it redirects to the login page.
For the authentication, I used the tutorial (https://learn.microsoft.com/en-us/graph/tutorials/python). I do not how to deal with groups and permissions since I use Azure AD authentication. So I take the username and surname from the token that comes from the Azure Authentication and with this two infos, I create an user in the User Django models. I know it is not the best idea, but I can start to play with groups and permissions.
The django authentication is automatic without that the user create it. It is done in the callback function.
def callback(request):
# Make the token request
result = get_token_from_code(request)
#Get the user's profile
user = get_user(result['access_token'])
# Store user
store_user(request, user)
# Get user info
# user attribute like displayName,surname,mail etc. are defined by the
# institute incase you are using single-tenant. You can get these
# attribute by exploring Microsoft graph-explorer.
username = user['displayName']
password = user['surname']
email = user['mail']
try:
# if use already exist
user = User.objects.get(username=username)
except User.DoesNotExist:
# if user does not exist then create a new user
user = User.objects.create_user(username,email,password)
user.save()
user = authenticate(username=username,password=password)
if user is not None:
login(request,user)
messages.success(request,"Success: You were successfully logged in.")
return redirect('home')
return redirect('home')
If I want to check if the user is authenticated by Azure AD. From the tutorial, I should do something like that :
if request.session.get('user').get('is_authenticated') :
But I do not know how to combine with the django authentication to check both. Anyone can help me
Thanks
simplest way would be to use the user_passes_test decorator to make your own function and apply that as a decorator to your views as per the docs
from django.contrib.auth.decorators import user_passes_test
def check_azure(user):
# so something here to check the azure login which should result in True/False
return #theResult of your check
#user_passes_test(check_azure)
def my_view(request):
...
Here is my solution :
from django.shortcuts import redirect
def authenticated_user(view_func) :
def wrapper_func(request, *args, **kwargs):
if request.user.is_authenticated and request.session.get('user').get('is_authenticated') :
return view_func(request, *args, **kwargs)
else :
return redirect('login')
return wrapper_func
I have overwritten the custom user model such that I can login using email instead of username and such that I can redirect to changepassword on first login.
def login_view(request):
if request.method == 'POST':
form = AuthenticationForm(data=request.POST)
if form.is_valid():
user = form.get_user()
if user.last_login is None:
login(request, user)
return redirect('accounts:change_password')
else:
login(request, user)
return redirect('home')
else:
form = AuthenticationForm()
if request.user.is_authenticated:
return redirect('home')
else:
return render(request, 'login.html', {'form': form})
I have set up a basic rest API endpoint using DRF
class UserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
queryset = User.objects.all()
serializer_class = UserSerializer
When I access the homepage, I need to be logged in:
#login_required(login_url="/accounts/login/")
def home(request):
return render(request, 'index.html', {})
What I would like to do is to authenticate using django.contrib.auth and be redirected to homepage.
When the homepage loads, I would like to perform an AJAX call to display all users.
$.ajax(
{
type: "GET",
url: '/accounts/users/',
success: function(result){
console.log(result);
}
});
This call should only work if I am logged in with my user already.
If I access the endpoint externally, let's say in Postman, It should ask me to authenticate.
I should be able to authenticate externally in postman using token authentication.
The question:
How can I mix the django.contrib.auth authentication with the Django rest_framework Token Authentication in the manner described above? I would like to have a web application and a REST API at the same time. Authenticate in the web application using the django.contrib.auth. Authenticate to REST API using Token. But if user is already logged in the web application perform Rest API request without needing to authenticate again. Can I somehow reuse the web application session?
Already customized my user model:
https://docs.djangoproject.com/en/2.1/topics/auth/customizing/
Have looked at this, not started implementing yet. I am not sure how they can connect.
https://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication
Solution
I accepted the answer below although I found the solution meanwhile. I assumed that you can only add one method of authentication but you can have more. What I actually ended up doing was:
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
)
It works like a charm. The IsAuthenticated permission is applied globally in my case. In the accepted answer is applied per endpoint
You can always use as many authentication methods as you want. DRF has SessionAuthentication that works like the native Django authentication, in addition to its TokenAuthentication. All you need to do is set both authentication classes either globally in settings file or per view.
For example, you can set the authentication classes for your UserViewSet in this way.
from rest_framework.authentication import TokenAuthentication, SessionAuthentication
class UserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
queryset = User.objects.all()
serializer_class = UserSerializer
authentication_classes = [TokenAuthentication, SessionAuthentication]
In this way, your web client can use sessions to authenticate, while other clients use tokens.
I am trying to create a website which is authenticated by a custom login. But I have a custom Model for users. How do I authenticate my Website from anonymous Users. Is it possible to create login systems using based on sessions. Actually this is my first django project. Please Guide me. Thank you.
While login, after checking username and password create a session in which set their user object or it's object id in it. In this case i kept user id.
def login(request):
if request.method == "GET":
if (Users.objects.filter(username = request.GET.get("uname"), password = request.GET.get("upswd"))).exists():
user = Users.objects.get(username = request.GET.get("uname"), password = request.GET.get("upswd"))
request.session["user"] = user.id
# request.session.set_expiry(10)
# it shows home page
return render(request,"home.html")
#it shows again login page
return render(request,"Login.html")
only in login page you will set session, In remaining you will check whether page contains that session or not.For example, in after login in home page you should check whether request contains user session or not.
if request.session.has_key("user"):
userid = request.session["user"]
#displaying all items in database
context={"items":Products.objects.all()}
return render(request,"home.html",context)
It is better to use a POST form instead of a GET request as against the answer above. Also, instead of querying for username and password against your database, use authenticate() function.
from django.contrib.auth import authenticate, login
if request.POST:
if login_form.is_valid():
username = request.POST.get('username')
password = request.POST.get('password')
user = authenticate(username=username, password=password)
if user:
login(request, user)
# do something or redirect
You don't need to set the user id in the session object, to retrieve the currently logged in user, use request.user
I'm using httpie to test my custom authentication.
http POST http://127.0.0.1:8000/api-token-auth/ username='username1' password='Password123'
I did create a custom auth object using AbstractUser.
Using TokenAuthentication, I followed the docs and added my custom TokenAuthentication in my REST_FRAMEWORK settings:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'regis.models.CustomAuthentication',
)
}
And added rest_framework.authtoken in my installed apps.
My AUTHENTICATION_BACKEND is as follows:
AUTHENTICATION_BACKENDS = [ 'regis.models.CustomAuthentication' ]
And here is my custom authentication class:
class CustomAuthentication(authentication.TokenAuthentication):
def authenticate(self, request):
username = request.META.get('X_USERNAME')
print(username)
user_model = get_user_model()
if not username:
return None
try:
user = user_model.objects.get(username=username)
except User.DoesNotExist:
raise exceptions.AuthenticationFailed('No such user')
return (user, None)
urls.py:
urlpatterns += [
url(r'^api-token-auth/', views.obtain_auth_token),
]
I'm pretty much following the DRF docs (http://www.django-rest-framework.org/api-guide/authentication/#custom-authentication). If there's any additional info needed to solve this, please let me know and I'll update. Any help on what I'm missing would be great.
To add: Just out of curiosity, do I need to make a custom authentication system if I have a custom user?
UPDATE:
I just deleted the class above, and just added the rest_framework.authentication.TokenAuthentication in my REST_FRAMEWORK settings. I'm still using a custom authentication which fetches my user.
It looks like this (not going to format it. SO sucks at formatting code from VIM):
class CustomAuthentication(object):
def authenticate(self, email=None, password=None):
User = get_user_model()
try:
user = User.objects.get(email=email)
except User.DoesNotExist:
return None
if user.check_password(password):
return user
return None
def get_user(self, user_id):
try:
user_model = get_user_model()
user = user_model.objects.get(pk=user_id)
except User.DoesNotExist:
return None
I used this Django docs to create that code: https://docs.djangoproject.com/en/1.10/topics/auth/customizing/
If you search for the error string in the DRF code, you find this (in authtoken/serializers.py:
from django.contrib.auth import authenticate
...
if username and password:
user = authenticate(username=username, password=password)
if user:
# From Django 1.10 onwards the `authenticate` call simply
# returns `None` for is_active=False users.
# (Assuming the default `ModelBackend` authentication backend.)
if not user.is_active:
msg = _('User account is disabled.')
raise serializers.ValidationError(msg, code='authorization')
else:
msg = _('Unable to log in with provided credentials.')
raise serializers.ValidationError(msg, code='authorization')
...
So it looks like depending on which version of Django you're using, either these credentials are incorrect, or the user is not active (for Django >= 1.10)?
Have you tried logging in manually in the admin with these credentials to verify them?
OK I solved it. Inside my settings, I just had to remove the AUTHENTICATIONS_BACKEND. I thought my custom backend was different for merely logging a user in and the token authentication backend worked to get that token.
I am using built-in login in my app. There is some custom backends or packages to handle this. but many of them are not what i am looking.
i made email unique via django-registration while registering. now all i want is to ask email in login page instead of username.
but if i use some custom backends like django email as username it crashes when using with django-registration.
i dont want to change all authentication backend , i just want to change login page.
in the rest of site , i am gonna use username. p.e in my custom admin page when i write:
welcome {{user}}
it must render username. not e-mail.
i need to find the way out from this. i am stuck.
thank you.
By default django.contrib.auth.urls will create a log in page from this pattern
(r'^login/$', 'django.contrib.auth.views.login'),
You need to avoid/override this url then create a new view to handle a new type of login.
e.g.
create a new login url in your urls.py
(r'^emaillogin/$', 'email_login_view'),
create view to support login with email in views.py
# get default authenticate backend
from django.contrib.auth import authenticate, login
from django.contrib.auth.models import User
# create a function to resolve email to username
def get_user(email):
try:
return User.objects.get(email=email.lower())
except User.DoesNotExist:
return None
# create a view that authenticate user with email
def email_login_view(request):
email = request.POST['email']
password = request.POST['password']
username = get_user(email)
user = authenticate(username=username, password=password)
if user is not None:
if user.is_active:
login(request, user)
# Redirect to a success page.
else:
# Return a 'disabled account' error message
else:
# Return an 'invalid login' error message.
Ref : https://docs.djangoproject.com/en/1.4/topics/auth/#django.contrib.auth.login
The above approach does not work anymore on django 1.9. A different approach might be to override the auth form used in the view as:
class EmailLoginForm(AuthenticationForm):
def clean(self):
try:
self.cleaned_data["username"] = get_user_model().objects.get(email=self.data["username"])
except ObjectDoesNotExist:
self.cleaned_data["username"] = "a_username_that_do_not_exists_anywhere_in_the_site"
return super(EmailLoginForm, self).clean()
Then when defining the login url, define as this:
url(r'^login/$', django.contrib.auth.views.login, name="login", kwargs={"authentication_form": EmailLoginForm}),
url(r'^', include('django.contrib.auth.urls')),
The best thing about the above approach you are not really touching anything in the auth process. It's not really a "clean" solution but it's a quick workaround. As you define the login path before including auth.urls, it will be evaluated instead of the base login form