Django REST Custom (token) authentication - python

Because I'm mixing things up and just making myself more confused, maybe someone can actually guide me through it.
I need to make a Django REST API that requires login. However the User table already exists in a Postgres database. I think token-based authentication is most suitable, however, those tokens don't exist yet.
(Login once to retrieve/create a token, check the token on each request)
How can I use a POST request to submit login details purely for verifying the user?
How would I generate a token upon successful login, and should I store it in a new Token table?
After this, how can I use the token to provide authentication/authorization on API data requests?
All examples I can find use the default Django User model or don't go into enough detail, which I can't use in this case.
I've made a custom authenticator that checks the username and password, however I can't get through the next steps.
from api.models import LogonAccount
from rest_framework import authentication
from rest_framework import exceptions
import bcrypt
class ExampleAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
username = request.data.get('username') # get the username request header
password = request.data.get('password') # get the password request header
if not username or not password: # no username or password passed in request headers
return None # authentication did not succeed
try:
user = LogonAccount.objects.get(username=username)
if bcrypt.hashpw(password.encode(), user.password.encode()):
print("succes")
return (user, None) # authentication successful
except LogonAccount.DoesNotExist:
raise exceptions.AuthenticationFailed('No such user')

Don't get confused, you are simply trying to achieve token-based authentication with DRF. DRF already comes with this feature. This article will guide you through that https://simpleisbetterthancomplex.com/tutorial/2018/11/22/how-to-implement-token-authentication-using-django-rest-framework.html

Related

How to logout / signout from firebase auth with python flask

I am Creating a simple login, logout, signup flask web app using firebase auth and I successfully created login and signup but stuck in logout. So is there any way to log out or sign out from firebase auth?
Thanks
There is no way to invalidate a specific token, however you can invalidate the refresh token (refer this article)
But that doesn't seem to be your problem. So, Simply the best way to go about this would be to delete the token or forget the user from client side :
auth.current_user = None
After the user is set to None, the requests will not be authenticated and hence it's more like the user has logged out.
Still, if you want to implement this for some specific case, there is a workaround you can refer here
When you log in with firebase, it typically gives you a refresh token and an id token. These are JWT's that identify the user and the other refreshes the id token when it expires as the id token expires after 1 hour.
If you are using something like PyreBase
auth.current_user = None is not secure.
Instead, you should look at the sign out method provided.
import pyrebase
firebase = pyrebase.initialize_app(config)
auth = firebase.auth()
user = auth.sign_in_with_email_and_password(email,password)
auth.signOut()

django authentication and password reset

So, i am currently working on a web application project and i implemented authentication and password confirmation successfully.
But my issue is that i did it using html templates and now the requirement came up that we have to develop our application using api's for the backened.
Now, i am new to api and really confused how to use the authentication system i built (as we have to provide template to the in-built implemented class and they accept the values from their itself)
Is it possible to actually see and manage the registered users from the code-behind while still using there in-built mechanism
For password change you can use this generic view using the inbuilt Django auth framework
#login_required
def change_password(request):
if request.method == "POST":
form = PasswordChangeForm(request.user, request.POST)
if form.is_valid():
user = form.save()
# Important to update the session otherwise user will have to login again
update_session_auth_hash(request, user)
# Server side alert
print("Password changed for {0}".format(user.username))
return redirect('/index/')
else:
print(form.errors)
else:
form = PasswordChangeForm(request.user)
return render(request, 'website/changepassword.html', {'form': form})
You need to use djangorestframework, and use the decorator #apiview(['GET', 'POST']) to create a RestAPI
You can use TokenAuthentication available in django rest framework. See what documentation says:
TokenAuthentication
This authentication scheme uses a simple token-based HTTP Authentication scheme. Token authentication is appropriate for client-server setups, such as native desktop and mobile clients.
To use the TokenAuthentication scheme you'll need to configure the authentication classes to include TokenAuthentication, and additionally include rest_framework.authtoken in your INSTALLED_APPS setting:
INSTALLED_APPS = (
...
'rest_framework.authtoken'
)
Note: Make sure to run manage.py migrate after changing your settings. The rest_framework.authtoken app provides Django database migrations.
You'll also need to create tokens for your users.
from rest_framework.authtoken.models import Token
token = Token.objects.create(user=...)
print token.key
For clients to authenticate, the token key should be included in the Authorization HTTP header. The key should be prefixed by the string literal "Token", with whitespace separating the two strings. For example:
Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b
Note: If you want to use a different keyword in the header, such as Bearer, simply subclass TokenAuthentication and set the keyword class variable.
If successfully authenticated, TokenAuthentication provides the following credentials.
request.user will be a Django User instance.
request.auth will be a rest_framework.authtoken.models.Token instance.
Unauthenticated responses that are denied permission will result in an HTTP 401 Unauthorized response with an appropriate WWW-Authenticate header. For example:
WWW-Authenticate: Token
The curl command line tool may be useful for testing token authenticated APIs. For example:
curl -X GET http://127.0.0.1:8000/api/example/ -H 'Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b'
Note: If you use TokenAuthentication in production you must ensure that your API is only available over https.
Source: http://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication

Call Rest API with user using Python requests

I want to call an API using requests.get() method where I have to give username and password as authentication like below.
response = requests.get(url, auth=requests.auth.HTTPBasicAuth('username', 'password'))
but I don't want to give password in auth as it will work dynamically where password will be encrypted.
so is there any way to do this by only giving username and not password?
There are a handful of methods to authenticate with an API, the most popular would probably be Token Authentication with something like JWT. This will allow your users to authenticate with your API without sending username and password. If you are using django rest framework, the link I provided should help to get you started.

Custom Authentication for Google Cloud Endpoints (instead of OAuth2)

We are super excited about App Engine's support for Google Cloud Endpoints.
That said we don't use OAuth2 yet and usually authenticate users with username/password
so we can support customers that don't have Google accounts.
We want to migrate our API over to Google Cloud Endpoints because of all the benefits we then get for free (API Console, Client Libraries, robustness, …) but our main question is …
How to add custom authentication to cloud endpoints where we previously check for a valid user session + CSRF token in our existing API.
Is there an elegant way to do this without adding stuff like session information and CSRF tokens to the protoRPC messages?
I'm using webapp2 Authentication system for my entire application. So I tried to reuse this for Google Cloud Authentication and I get it!
webapp2_extras.auth uses webapp2_extras.sessions to store auth information. And it this session could be stored in 3 different formats: securecookie, datastore or memcache.
Securecookie is the default format and which I'm using. I consider it secure enough as webapp2 auth system is used for a lot of GAE application running in production enviroment.
So I decode this securecookie and reuse it from GAE Endpoints. I don't know if this could generate some secure problem (I hope not) but maybe #bossylobster could say if it is ok looking at security side.
My Api:
import Cookie
import logging
import endpoints
import os
from google.appengine.ext import ndb
from protorpc import remote
import time
from webapp2_extras.sessions import SessionDict
from web.frankcrm_api_messages import IdContactMsg, FullContactMsg, ContactList, SimpleResponseMsg
from web.models import Contact, User
from webapp2_extras import sessions, securecookie, auth
import config
__author__ = 'Douglas S. Correa'
TOKEN_CONFIG = {
'token_max_age': 86400 * 7 * 3,
'token_new_age': 86400,
'token_cache_age': 3600,
}
SESSION_ATTRIBUTES = ['user_id', 'remember',
'token', 'token_ts', 'cache_ts']
SESSION_SECRET_KEY = '9C3155EFEEB9D9A66A22EDC16AEDA'
#endpoints.api(name='frank', version='v1',
description='FrankCRM API')
class FrankApi(remote.Service):
user = None
token = None
#classmethod
def get_user_from_cookie(cls):
serializer = securecookie.SecureCookieSerializer(SESSION_SECRET_KEY)
cookie_string = os.environ.get('HTTP_COOKIE')
cookie = Cookie.SimpleCookie()
cookie.load(cookie_string)
session = cookie['session'].value
session_name = cookie['session_name'].value
session_name_data = serializer.deserialize('session_name', session_name)
session_dict = SessionDict(cls, data=session_name_data, new=False)
if session_dict:
session_final = dict(zip(SESSION_ATTRIBUTES, session_dict.get('_user')))
_user, _token = cls.validate_token(session_final.get('user_id'), session_final.get('token'),
token_ts=session_final.get('token_ts'))
cls.user = _user
cls.token = _token
#classmethod
def user_to_dict(cls, user):
"""Returns a dictionary based on a user object.
Extra attributes to be retrieved must be set in this module's
configuration.
:param user:
User object: an instance the custom user model.
:returns:
A dictionary with user data.
"""
if not user:
return None
user_dict = dict((a, getattr(user, a)) for a in [])
user_dict['user_id'] = user.get_id()
return user_dict
#classmethod
def get_user_by_auth_token(cls, user_id, token):
"""Returns a user dict based on user_id and auth token.
:param user_id:
User id.
:param token:
Authentication token.
:returns:
A tuple ``(user_dict, token_timestamp)``. Both values can be None.
The token timestamp will be None if the user is invalid or it
is valid but the token requires renewal.
"""
user, ts = User.get_by_auth_token(user_id, token)
return cls.user_to_dict(user), ts
#classmethod
def validate_token(cls, user_id, token, token_ts=None):
"""Validates a token.
Tokens are random strings used to authenticate temporarily. They are
used to validate sessions or service requests.
:param user_id:
User id.
:param token:
Token to be checked.
:param token_ts:
Optional token timestamp used to pre-validate the token age.
:returns:
A tuple ``(user_dict, token)``.
"""
now = int(time.time())
delete = token_ts and ((now - token_ts) > TOKEN_CONFIG['token_max_age'])
create = False
if not delete:
# Try to fetch the user.
user, ts = cls.get_user_by_auth_token(user_id, token)
if user:
# Now validate the real timestamp.
delete = (now - ts) > TOKEN_CONFIG['token_max_age']
create = (now - ts) > TOKEN_CONFIG['token_new_age']
if delete or create or not user:
if delete or create:
# Delete token from db.
User.delete_auth_token(user_id, token)
if delete:
user = None
token = None
return user, token
#endpoints.method(IdContactMsg, ContactList,
path='contact/list', http_method='GET',
name='contact.list')
def list_contacts(self, request):
self.get_user_from_cookie()
if not self.user:
raise endpoints.UnauthorizedException('Invalid token.')
model_list = Contact.query().fetch(20)
contact_list = []
for contact in model_list:
contact_list.append(contact.to_full_contact_message())
return ContactList(contact_list=contact_list)
#endpoints.method(FullContactMsg, IdContactMsg,
path='contact/add', http_method='POST',
name='contact.add')
def add_contact(self, request):
self.get_user_from_cookie()
if not self.user:
raise endpoints.UnauthorizedException('Invalid token.')
new_contact = Contact.put_from_message(request)
logging.info(new_contact.key.id())
return IdContactMsg(id=new_contact.key.id())
#endpoints.method(FullContactMsg, IdContactMsg,
path='contact/update', http_method='POST',
name='contact.update')
def update_contact(self, request):
self.get_user_from_cookie()
if not self.user:
raise endpoints.UnauthorizedException('Invalid token.')
new_contact = Contact.put_from_message(request)
logging.info(new_contact.key.id())
return IdContactMsg(id=new_contact.key.id())
#endpoints.method(IdContactMsg, SimpleResponseMsg,
path='contact/delete', http_method='POST',
name='contact.delete')
def delete_contact(self, request):
self.get_user_from_cookie()
if not self.user:
raise endpoints.UnauthorizedException('Invalid token.')
if request.id:
contact_to_delete_key = ndb.Key(Contact, request.id)
if contact_to_delete_key.get():
contact_to_delete_key.delete()
return SimpleResponseMsg(success=True)
return SimpleResponseMsg(success=False)
APPLICATION = endpoints.api_server([FrankApi],
restricted=False)
I wrote a custom python authentication library called Authtopus that may be of interest to anyone looking for a solution to this problem: https://github.com/rggibson/Authtopus
Authtopus supports basic username and password registrations and logins, as well as social logins via Facebook or Google (more social providers could probably be added without too much hassle too). User accounts are merged according to verified email addresses, so if a user first registers by username and password, then later uses a social login, and the verified email addresses of the accounts match up, then no separate User account is created.
From my understanding Google Cloud Endpoints provides a way to implement a (RESTful?) API and to generate a mobile client library. Authentication in this case would be OAuth2. OAuth2 provides different 'flows', some of which support mobile clients.
In the case of authentication using a principal and credentials (username and password) this doesn't seem like a good fit. I honestly think you would be better off by using OAuth2.
Implementing a custom OAuth2 flow to support your case is an approach that could work but is very error prone.
I haven't worked with OAuth2 yet but maybe an 'API key' can be created for a user so they can both use the front-end and the back-end through the use of mobile clients.
you can used jwt for authentication. Solutions here
I did not coded it yet, but it imagined next way:
When server receives login request it look up username/password in datastore. In case user not found server responds with some error object that contains appropriate message like "User doesn't exist" or like. In case found it stored in FIFO kind of collection (cache) with limited size like 100 (or 1000 or 10000).
On successful login request server returns to client sessionid like ";LKJLK345345LKJLKJSDF53KL". Can be Base64 encoded username:password.
Client stores it in Cookie named "authString" or "sessionid" (or something less eloquent) with 30 min (any) expiration.
With each request after login client sends Autorization header that it takes from cookie. Each time cookie taken, it renewed -- so it never expires while user active.
On server side we will have AuthFilter that will check presence of Authorization header in each request (exclude login, signup, reset_password). If no such header found, filter returns response to client with status code 401 (client shows login screen to user). If header found filter first checks presence of user in the cache, after in datastore and if user found -- does nothing (request handled by appropriate method), not found -- 401.
Above architecture allows to keep server stateless but still have auto disconnecting sessions.

Using Requests python library to connect Django app failed on authentication

Maybe a stupid question here:
Is Requests(A python HTTP lib) support Django 1.4 ?
I use Requests follow the Official Quick Start like below:
requests.get('http://127.0.0.1:8000/getAllTracks', auth=('myUser', 'myPass'))
but i never get authentication right.(Of course i've checked the url, username, password again and again.)
The above url 'http://127.0.0.1:8000/getAllTracks' matches an url pattern of the url.py of a Django project, and that url pattern's callback is the 'getAllTracks' view of a Django app.
If i comment out the authentication code of the 'getAllTracks' view, then the above code works OK, but if i add those authentication code back for the view, then the above code never get authenticated right.
The authentication code of the view is actually very simple, exactly like below (The second line):
def getAllTracks(request):
if request.user.is_authenticated():
tracks = Tracks.objects.all()
if tracks:
# Do sth. here
Which means if i delete the above second line(with some indents adjustments of course), then the requests.get() operation do the right thing for me, but if not(keep the second line), then it never get it right.
Any help would be appreciated.
In Django authentication works in following way:
There is a SessionMiddleware and AuthenticationMiddleware. The process_request() of both these classes is called before any view is called.
SessionMiddleware uses cookies at a lower level. It checks for a cookie named sessionid and try to associate this cookie with a user.
AuthenticationMiddleware checks if this cookie is associated with an user then sets request.user as that corresponding user. If the cookie sessionid is not found or can't be associated with any user, then request.user is set to an instance of AnonymousUser().
Since Http is a stateless protocol, django maintains session for a particular user using these two middlewares and using cookies at a lower level.
Coming to the code, so that requests can work with django.
You must first call the view where you authenticate and login the user. The response from this view will contain sessionid in cookies.
You should use this cookie and send it in the next request so that django can authenticate this particular user and so that your request.user.is_authenticated() passes.
from django.contrib.auth import authenticate, login
def login_user(request):
user = authenticate(username=request.POST.get('username'), password=request.POST.get('password'))
if user:
login(request, user)
return HttpResponse("Logged In")
return HttpResponse("Not Logged In")
def getAllTracks(request):
if request.user.is_authenticated():
return HttpResponse("Authenticated user")
return HttpResponse("Non Authenticated user")
Making the requests:
import requests
resp = requests.post('http://127.0.0.1:8000/login/', {'username': 'akshar', 'password': 'abc'})
print resp.status_code
200 #output
print resp.content
'Logged In' #output
cookies = dict(sessionid=resp.cookies.get('sessionid'))
print cookies
{'sessionid': '1fe38ea7b22b4d4f8d1b391e1ea816c0'} #output
response_two = requests.get('http://127.0.0.1:8000/getAllTracks/', cookies=cookies)
Notice that we pass cookies using cookies keyword argument
print response_two.status_code
200 #output
print response_two.content
'Authenticated user' #output
So, our request.user.is_authenticated() worked properly.
response_three = requests.get('http://127.0.0.1:8000/hogwarts/getAllTracks/')
Notice we do not pass the cookies here.
print response_three.content
'Non Authenticated user' #output
I guess, auth keyword for Requests enables HTTP Basic authentication which is not what is used in Django. You should make a POST request to login url of your project with username and password provided in POST data, after that your Requests instance will receive a session cookie with saved authentication data and will be able to do successful requests to auth-protected views.
Might be easier for you to just set a cookie on initial authentication, pass that back to the client, and then for future requests expect the client to send back that token in the headers, like so:
r = requests.post('http://127.0.0.1:8000', auth=(UN, PW))
self.token = r.cookies['token']
self.headers = {'token': token}
and then in further calls you could, assuming you're in the same class, just do:
r = requests.post('http://127.0.0.1:8000/getAllTracks', headers=self.headers)

Categories

Resources