Django-rest-auth : How refresh Token? - django

I'm using Django-rest-auth for authentication (https://django-rest-auth.readthedocs.io).
but when I register a new account, the api sent back to me a Token who never change after.
For more security, how can I do to have a new token every time I login ?

If you are working with an API, please structure your code in such a way.
class LoginAPIView(GenericAPIView):
serializer_class = LoginFormSerializer
#csrf_exempt
def post(self, request):
serializer = LoginFormSerializer(data=request.data)
if not serializer.is_valid():
return error_message(message=MessageKey.ERROR_MISSING_USERNAME_OR_PASSWORD.value)
username = serializer.data['username']
password = serializer.data['password']
try:
user = authenticate(username=username, password=password)
if not user:
return error_message(message=MessageKey.ERROR_INVALID_USERNAME_OR_PASSWORD.value)
if user.record_status == RecordStatus.ACTIVE.name:
# Create a new auth token using your token service
auth_token = create_auth_token(user)
user_serializer = UserSerializer(user)
user_data = user_serializer.data
user_data['auth_token'] = str(auth_token[0])
data = generate_user_account_profile(language, user, user_data)
return success_message(data=data)
else:
return error_message(MessageKey.ERROR_INVALID_USERNAME_OR_PASSWORD.value)
except Exception as ex:
traceback.print_exc()
return error_message(MessageKey.ERROR_DEFAULT_ERROR_MESSAGE.value)
The function create_auth_token(user) can be structured as folloows;
def create_auth_token(user):
"""
This is used to create or update an auth token
:param user:
:return:
"""
try:
token = Token.objects.filter(user=user)
if not token:
token = Token.objects.get_or_create(user=user)
else:
token = Token.objects.filter(user=user)
new_key = token[0].generate_key()
# Encrypt random string using SHA1
sha1_algorithm = hashlib.sha1()
sha1_algorithm.update(new_key.encode('utf-8'))
first_level_value = sha1_algorithm.hexdigest()
# Encrypt random string using MD5
md5_algorithm = hashlib.md5()
md5_algorithm.update(first_level_value.encode('utf-8'))
second_level_value = md5_algorithm.hexdigest()
token.update(key=second_level_value)
return token
except Exception as ex:
logging.error(msg=f'Failed to create auth token {ex}', stacklevel=logging.CRITICAL)
pass
Note: This can be adjusted to fit in any place of your choice i.e you can adjust it to work in your view.py file, services, etc

Related

Django returns session id but doesn't authenticate user

I have the following code that sends requests to check JWT token, then authorize user and return authorized session with Access token, Refresh Token and Session ID.
#csrf_exempt
def new_login_view(request, *args, **kwargs):
def convert_data(req):
data = {
"email": req.data['username'],
"password": req.data['password'],
}
try:
data["language"] = request.LANGUAGE_CODE
except:
data["language"] = request.POST.get('language', 'en')
return data
if request.user.is_authenticated and not request.META.get('HTTP_X_AVOID_COOKIES'):
return HttpResponseRedirect(request.GET.get(KEY_NEXT, '/'))
if request.method == 'POST':
request_data = convert_data(request)
# request to Accounts API to check if user exists
response = send_service_request(EnumAuthUrls.sign_in.value,
json_data=request_data,
original_response=True)
if isinstance(response, dict):
return JsonResponse(response)
if response.status_code == 200:
tok_ac = response.headers.get(HEADER_ACCESS_KEY)
tok_ref = response.headers.get(HEADER_REFRESH_KEY)
# checking JWT token
user = ApiAuthenticationBackend().authenticate(request, tok_ac)
# creates session
data = login_session(request, response, user)
data['user_id'] = request.user.id
data['account_id'] = request.user.profile.account_id
data['balance'] = request.user.balance
if request.META.get('HTTP_X_AVOID_COOKIES'):
return JsonResponse(data)
response = AuthResponse(
data=data,
ssid=request.session.session_key,
access_token=tok_ac,
refresh_token=tok_ref,
)
return response
else:
return ErrorApiResponse(response.json())
service = urllib.parse.quote_plus(request.build_absolute_uri())
return HttpResponseRedirect(settings.ACCOUNTS_URL + f'login/?service={service}')
Here's the code of login_session fucntion:
def login_session(request: HttpRequest, response: HttpResponse, user):
request.user = user
request.session.create()
base_data = response.json().get(KEY_DATA)
return request.user.serialize(request, base_data, token=True)
And here's the class AuthResponse that is eventually based on HttpResponse:
class AuthResponse(SuccessResponse):
def __init__(self, data={}, ssid='', access_token: str = '', refresh_token: str = '', **kwargs):
super().__init__(data, **kwargs)
if ssid:
logger.debug(f'Setting {settings.SESSION_COOKIE_NAME}: {ssid}')
self.set_cookie(key=settings.SESSION_COOKIE_NAME,
value=ssid)
if access_token:
self.set_cookie(key=settings.ACCESS_KEY_COOKIE_NAME,
value=access_token)
if refresh_token:
self.set_cookie(key=settings.REFRESH_KEY_COOKIE_NAME,
value=refresh_token)
The problem is that looks everything good on the browser side, I get all needed cookies (access token, refresh token and session id) however after trying logging in I get redirected to the main page.
There was problem in the beginning with setting cookies, but then I found out that I should not use SESSION_COOKIE_DOMAIN if it's local. Thus all cookies came up without problem but it didn't resolve situation with authorization.
While setting cookies with self.set_cookie() I tried to use secure=True, samesite='Lax', httponly=True, and all other parameters but it didn't help.
Does anyone knows what else I can try in order to fix it?
Well, I found what was wrong!
There was middleware that supposed to check token from another service. However it was checking old token, instead of new one.
So once I changed it and started to check new token - it was working just fine.
So if there's no other solutions, make sure you have checked middleware or other code where it could affect on whole system.

How to extract payload from a jwt Expired Token

I have made a view where I send a Refresh Token to email for activation account purpose. If token is valid everything works fine. The problem is when jwt token expire, I want to be able in backend to extract payload (user_id) from token when jwt.decode throw ExpiredSignatureError and in this way be able to auto resend an Email based on user_id extracted from token.
Here is how I generate the token:
def activation_link(request, user, email):
token = RefreshToken.for_user(user)
curent_site = "localhost:3000"
relative_link="/auth/confirm-email"
link = 'http://' + curent_site + relative_link + "/" + str(token)
html_message = render_to_string('users/email_templates/activate_account.html',{
'activation_link': link,
})
text_content = strip_tags(html_message)
email_subject = 'Activate your account'
from_email = 'notsure#yahoo.com'
to_email = email
#api_view(['POST'])
def ConfirmEmailView(request):
try:
activation_token = request.data['activation_token']
payload = jwt.decode(activation_token,settings.SECRET_KEY, algorithms=['HS256'])
user = User.objects.get(id = payload['user_id'])
if user.is_confirmed:
return Response('Already verified!', status=status.HTTP_200_OK)
user.is_confirmed = True
user.save()
return Response(status=status.HTTP_202_ACCEPTED)
except jwt.ExpiredSignatureError as identifier:
// =>>> Here I want to decode activation_token and extract user_id
return Response("Link- expired!", status=status.HTTP_403_FORBIDDEN)
except Exception as e:
print(e)
return Response(status=status.HTTP_400_BAD_REQUEST)
Well, apparently the solution is easy:
def ConfirmEmailView(request):
try:
activation_token = request.data['activation_token']
payload = jwt.decode(activation_token,settings.SECRET_KEY, algorithms=['HS256'])
user = User.objects.get(id = payload['user_id'])
if user.is_confirmed:
return Response('Already verified!', status=status.HTTP_200_OK)
user.is_confirmed = True
user.save()
return Response(status=status.HTTP_202_ACCEPTED)
except jwt.ExpiredSignatureError as identifier:
# Here we are:
payload = jwt.decode(request.data['activation_token'],settings.SECRET_KEY, algorithms=['HS256'],options={"verify_signature": False})
user_id = payload['user_id'];
return Response({'user_id':user_id, status=status.HTTP_2OO_OK})
By adding options={"verify_signature": False} is posible to decode the token just fine!

Unit testing of OAuth on Flask

I'm new to coding and I feel really stuck. I decided to unit test my micro Flask app. I use "Google sign in" to authenticate my users, but I'm not able to get through the Google authentication and assert what is really on the page. I have no idea if I need to mock, play with the session or application context.
Here is the code for authentication process:
def login_required(f):
#wraps(f)
def decorated_function(*args, **kwargs):
user = dict(session).get('profile', None)
if user is not None:
email = session['profile']['email']
else:
email = None
if user:
return f(*args, **kwargs)
return render_template('login.html', email=email)
return decorated_function
#app.route('/login/')
def login():
google = oauth.create_client('google') # create the google oauth client
redirect_uri = url_for('authorize', _external=True)
return google.authorize_redirect(redirect_uri)
#app.route('/authorize')
def authorize():
google = oauth.create_client('google') # create the google oauth client
token = google.authorize_access_token() # Access token from google (needed to get user info)
resp = google.get('userinfo') # userinfo specificed in the scrope
user_info = resp.json()
# Loading and cleaning all user emails
auth_users = db.session.query(users.email).all()
auth_users = [str(i).strip("(),'") for i in auth_users]
# Checking if user is in the list
if user_info['email'] in auth_users:
flash("Boli ste úspešne prihlásený.", category="success")
else:
flash("Nemáte práva na prístup.", category="primary")
return redirect('/')
session['profile'] = user_info
# make the session pernament so it keeps existing after broweser gets closed
session.permanent = True
return redirect('/')
The test structure look like this:
class Uvo_page(unittest.TestCase):
def setUp(self):
self.client = app.test_client(self)
self.user_info = {'email': 'xxx'}
self.auth_users = ['xxx', 'yyy']
# UVO page shows results
def test_uvo_page_show_results(self):
response = self.client.get('/show/1',
content_type='html/text',
follow_redirects=True)
test_string = bytes('Sledované stránky', 'utf-8')
self.assertTrue(test_string in response.data)
I thought that by defining the "user_info" it will be used for authentication evaluation. Instead of it it does not continue to "/authorize" and authentication, but stays on the login page.

Django get Model by token

on our Django site we need to create an admin action that will send a mail to the selected user to ask them to update their address. To do so I took a look at django's built in password reset tool, which works using tokens.
I copied and changed a bit their token thing:
class AddressEmailTokenGenerator(object):
"""
Strategy object used to generate and check tokens for the password
reset mechanism.
"""
def make_token(self, address):
"""
Returns a token that can be used once to do a password reset
for the given user.
"""
return self._make_token_with_timestamp(address, self._num_days(self._today()))
def check_token(self, address, token):
"""
Check that a password reset token is correct for a given user.
"""
# Parse the token
try:
ts_b36, hash = token.split("-")
except ValueError:
return False
try:
ts = base36_to_int(ts_b36)
except ValueError:
return False
# Check that the timestamp/uid has not been tampered with
if not constant_time_compare(self._make_token_with_timestamp(address, ts), token):
return False
# Check the timestamp is within limit
if (self._num_days(self._today()) - ts) > settings.PASSWORD_RESET_TIMEOUT_DAYS:
return False
return True
def _make_token_with_timestamp(self, address, timestamp):
# timestamp is number of days since 2001-1-1. Converted to
# base 36, this gives us a 3 digit string until about 2121
ts_b36 = int_to_base36(timestamp)
# By hashing on the internal state of the user and using state
# that is sure to change (the password salt will change as soon as
# the password is set, at least for current Django auth, and
# last_login will also change), we produce a hash that will be
# invalid as soon as it is used.
# We limit the hash to 20 chars to keep URL short
key_salt = "django.contrib.auth.tokens.PasswordResetTokenGenerator"
# Ensure results are consistent across DB backends
# login_timestamp = user.last_login.replace(microsecond=0, tzinfo=None)
value = (six.text_type(address.pk) + six.text_type(timestamp))
hash = salted_hmac(key_salt, value).hexdigest()[::2]
return "%s-%s" % (ts_b36, hash)
def _num_days(self, dt):
return (dt - date(2001, 1, 1)).days
def _today(self):
# Used for mocking in tests
return date.today()
I have my admin action:
def send_update_request(modeladmin, request, queryset):
test_mail = "test#test.ch"
for address in queryset:
token_generator = AddressEmailTokenGenerator()
token = token_generator.make_token(address)
print token
form_url = reverse("update_address", kwargs={"token":token})
send_mail("Address", form_url, "test#test.ch", [test_mail], fail_silently=False)
And now to the question: How can I get the pk of the corresponding address from the token in the url?
My view:
class UpdateAddressView(CreateView):
model = UpdateAddress
def get_context_data(self, **kwargs):
context = super(UpdateAddressView, self).get_context_data(**kwargs)
token = self.kwargs['token']
print token
#The token here is something like "3zi-ebf1da92b5e8bd3aaf86"
return context
You don't, that's not how it works. The Django reset password view has two components: a base-64 encoded string of the user ID, and the token to check validity. Decoding the ID is as simple as calling base64.urlsafe_b64decode, but you can't decode the token at all: it's hashed and salted, the only thing you can do is create a new token with the user object and check that they match.

Handling CSRF tokens in Django

I've written a small standalone python script that's calling my django-based backend and everything is working fine with login and calling views requiring auth and so on.
A bit of code
def dostuff():
session = login(username, password)
license = add_license(session)
def _helper(self, url, cookie=None):
http = httplib2.Http()
if cookie:
headers = { "Cookie" : cookie }
else:
headers = {}
response, content = http.request(host + url, "GET", headers=headers, body="")
return response, content
def login(self, username, password):
url = "/license/login?username=%s&password=%s" % (username, password)
response, content = self._helper(url)
sessioncookie = response["set-cookie"]
customer_id = re.search("id=(?P<id>\d+)", content)
if response["status"] == "200":
return sessioncookie, customer_id.group("id")
def add_license(self, session):
cookie = session[0]
customer_id = int(session[1])-1
url = "/license/add_license?customer_id=%s" % customer_id
response, content = self._helper(url, cookie)
content = content[1:-1]
if response["status"] == "200": #ok
data = json.loads(content)
return data["fields"]
If I cahnge "GET" to "POST" I encounter the Django CSRF-error page(CSRF verification failed) in return. How can I send POST data to Django?
My login view in Django, do I need to do anything special to add the csrf token? My plan is to rewrite this to send json once things are working.
def my_login(request):
done, username = get_input(request, "username")
if not done:
return username
done, password = get_input(request, "password")
if not done:
return password
user = authenticate(username=username, password=password)
if user is not None:
if user.is_active:
login(request, user)
return HttpResponse("Done, id=%s" % user.pk)
else:
return HttpResponse("User disabled")
else:
return HttpResponse("Invalid login")
I got it working and this is how I did it. Like suggested by toto_tico I worte a dummy view that I retrieve thought GET to get the CSRF token. At first it didn't send the csrf token over GET so I had to add the decorator ensure_csrf_cookie.
#ensure_csrf_cookie
def dummy(request):
return HttpResponse("done")
And then I handle login requests normally.
def my_login(request):
...handle login...
It turned out that just adding the cookie to the POST wasn't enough, I had to write a token to the POST data as well.
def _helper(self, url, method="POST"):
req = urllib2.Request(host + url)
self.cookieMgr.add_cookie_header(req)
try:
if method == "GET":
response = self.opener.open(req)
else:
for cookie in self.cookieMgr:
if cookie.name == "csrftoken":
csrf = cookie.value
values = { "csrfmiddlewaretoken" : csrf}
params = urllib.urlencode(values)
response = self.opener.open(req, params)
code = response.getcode()
info = response.info()
content = response.read()
return code, info, content
except urllib2.HTTPError as ex:
print str(ex)
sys.exit(1)
def get_csrf(self):
url = "/license/dummy"
self._helper(url, method="GET")
def login(self, username, password):
self.get_csrf()
url = "/license/login?username=%s&password=%s" % (username, password)
code, info, content = self._helper(url)
if code == 200:
#done!
You have to add the csrftoken cookie value when you make a request to Django. Alternatively you can add #csrf_exempt to your Django backend to accept those requests.
Start reading about CSFR and ajax. I usually do the following with the code provided:
Create a csfr.js file
Paste the code in the csfr.js file
Reference the code in the template that needs it|
If you are using templates and have something like base.html where you extend from, then you can just reference the script from there and you don't have to worry any more in there rest of your programming. As far as I know, this shouldn't represent any security issue.