I use django, django rest framework and ember.js; my entire application thereforce communicates via ajax.
Authentication is done via oauth2 and a token is send in the headers within every request.
Everythings nice and shiny but file downloads.
At one point users can download a pdf and I don't know how to apply authentication there - because on the file download I cannot send and headers, it's just a link.
I thought of adding SessionAuthentication to that particular rest api call, but the session always flags the incoming user as anyonymous.
How can I force django to create a session on top of the oauth2 token flow?
I tried login(request, user), but it somehow does not kick in.
I ended up with signed tickets, e.g. i send back a token, that is able to bypass auth for a defined timeframe. Therefore the ajax app can first request the token and then fire again a standard get request with the token attached.
Here's the basic idea, that I mixin to views:
class DownloadableMixin():
"""
Manages a ticket response, where a ticket is a signed response that gives a user limited access to a resource
for a time frame of 5 secs.
Therefore, file downloads can request a ticket for a resource and gets a ticket in the response that he can
use for non-ajax file-downloads.
"""
MAX_AGE = 5
def check_ticket(self, request):
signer = TimestampSigner()
try:
unsigned_ticket = signer.unsign(request.QUERY_PARAMS['ticket'], max_age=self.__class__.MAX_AGE)
except SignatureExpired:
return False
except BadSignature:
return False
if self.get_requested_file_name() == unsigned_ticket:
return True
return False
def get_ticket(self):
signer = TimestampSigner()
return signer.sign(self.get_requested_file_name())
def has_ticket(self, request):
return 'ticket' in request.QUERY_PARAMS
def requires_ticket(self, request):
return 'download' in request.QUERY_PARAMS
def get_requested_file_name(self):
raise NotImplementedError('Extending classes must define the requested file name.')
Related
In my frontend i'm logging into another app's api in the browser, I'm then redirected back to my app, that hits a View in my backend which gets a code from the other app's api, sends code back in a post request then receives an access token and stores it in a model associated with the current user.
My problem is that after the user gives permission to other app in the browser it redirects back to my backend view without the users token in the header so if i have permissions_classes set it wont allow user to access that view... but if i take the permissions_classes off, the view won't know who the current user is.
View #1 that prepares the other app's API url:
class getAPIAuthURL(APIView):
authentication_class = [authentication.TokenAuthentication]
permission_class = [permissions.IsAuthenticated]
def get(self, request):
scopes = 'scopes'
url = Request('GET', 'https://accounts.api.com/authorize',
params={
'scope': scopes,
'response_type': 'code',
'redirect_uri': REDIRECT_URL,
'client_id': CLIENT_ID
}
).prepare().url
return Response(url, status=status.HTTP_200_OK)
View #2 that gets data and stores it in model (this is the REDIRECT_URL from previous view):
class APICallback(APIView):
authentication_class = [authentication.TokenAuthentication]
permission_class = [permissions.IsAuthenticated]
def api_callback(request, format=None):
code = request.GET.get('code')
if not code:
return Response({'Error': 'Code not found in request'}, status=status.HTTP_400_BAD_REQUEST)
response = post('https://accounts.api.com/api/token', data={
'code': code,
}).json()
print(response)
user = request.user
access_token = response.get('access_token')
token = APITokenModel(user=user, access_token=access_token)
token.save()
return redirect('frontend')
I have other Views that make requests and it has been able to get the token to know who the user is, but when this View is called I get a 401 Unauthorized error.
How do I let Django know the token I'm receiving from the other app's api belongs to the current user?
also... when I take off permissions and authentication class from the View it returns the user as Anonymous User
First, what authentication class are you using? You should know that your TokenAuthentication class uses the Authorization header in your request to authenticate you. If that's not been passed then you should fix that.
It would be worth knowing that you don't send auth tokens as GET and should not be sent as those. Unless of course you want to write an Authentication class of your own.
EDIT
In lieu of our discuss in the comments, try this redirect...
# import the class
from django.http import HttpResponseRedirect
# now redirect
return HttpResponseRedirect(redirect_to="url", headers=dict)
I want to call my own API in a custom view I wrote. Normally I use JWT authentication with my API calls. In this specific view though, I'd like to use a different authentication.
I want to enable logged in users to make a successful get call (without a token). Not logged in users should not be able to make that call. I tried this with Basic Authentication and Session Authentication but don't really get it tow work.
Here is my view that makes the API call:
def visualize_buildings(request, id):
passed_id = id
endpoint = 'linktomyendpoint' + str(passed_id)
response = requests.get(endpoint)
building_group_data = response.json()
# print(building_group_data)
if 'buildings' in building_group_data:
building_data = building_group_data['buildings']
context = {'building' : building_data}
return render(request, 'building_group_visualize_api.html', context)
else:
return HttpResponseNotFound("Ups. We are sorry but no Building Group was found with that id")
Here my API view:
class BuildingGroupRetrieveAPIView(RetrieveAPIView):
authentication_classes = [JSONWebTokenAuthentication,
SessionAuthentication, BasicAuthentication]
serializer_class = BuildingGroupSerializer
queryset = BuildingGroup.objects.all()
The view works with if I send a token in the headers. But how can I use Session Authentication with that? I tried getting username and password from the request and then pass it to the API call. But that doesn't work because I can't decode the password from the request (which makes sense).
So I tried to follow this: https://2.python-requests.org/en/master/user/advanced/ but I still can't authenticate my request.
Can anyone point me into the right direction? Help is very much appreciated! Thanks in advance!
Session ids are saved as a cookie on the user's device and they will be sent to the server as a header name Cookie. So if you want to use cookies instead of the JWT token then you should send your request with the session id as a cookie header.
This is the header that lets Django know your session-id when you visit the site directly:
Cookie: csrftoken=some-csrf-token; sessionid=your-session-id
Now to make your request contain something like that:
cookies = {'sessionid': 'your-session-id'}
response = requests.get(endpoint, cookies=cookies)
Note that Django might still through an error for csrf token based on your settings.
You can find your session-id on your browser. If you don't know where and how to access them, just google it. it's different based on the browser you use.
Scenario: I want a user to re-login when passing to a security sensible area after N minutes, e.g. when user is about to pay an order, however he logged in 1 hour ago, I would like to be sure it's him. This by using rest_framework_jwt.
Long description:
I've been recently testing django for modern web development (so, backend with rest-api). However, I encountered a problem which I have not yet found a solution.
In rest_framework_jwt you set the authentication class as follows.
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
This will do a great work for the general purpose. However, I want the user to reidentify (re-login) when entering an area with sensible information 10 minutes after login, e.g. sensible information can be a payment area. Thus, I would like to send a parameter to the Authentication class telling that the user is in a sensible area.
What I think as a possible solution but I don't know how to do it yet, is: The rest_framework_jwt creates the variable orig_iat when using the option JWT_ALLOW_REFRESH. I could send a flag to authentication class to tell that current view is a sensitive area or not, if so and the user logged in more than 10 minutes ago, I can send a message to say that the user needs to re-login to continue.
I don't mind forking the rest_framework_jwt project and adapting it for my purposes, however I would like to know how to send the parameter from the view to the authentication class (in this case: rest_framework_jwt.authentication.JSONWebTokenAuthentication).
Also, If there is already something done with rest_framework_jwt for this scenario, I would like to avoid re-inventing the wheel.
Well... So far, what I've done it's to create a decorator for a function view. The code for the decorator is as follows:
from functools import wraps
from rest_framework_jwt.settings import api_settings
from django.utils.translation import ugettext as _
from calendar import timegm
import datetime
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
def recently_authenticated():
def decorator(func):
#wraps(func)
def inner(request, *args, **kwargs):
jwt_payload = jwt_decode_handler(request._auth)
rencent_auth_limit = api_settings.JWT_RECENT_AUTHENTICATION_DELTA
if isinstance(rencent_auth_limit, datetime.timedelta):
rencent_auth_limit = (rencent_auth_limit.days * 24 * 3600 +
rencent_auth_limit.seconds) + jwt_payload["orig_iat"]
timenow = timegm(datetime.datetime.utcnow().utctimetuple())
if timenow>rencent_auth_limit:
return Response({"detail":_("you have to reidentify to enter this area")},
status=401)
return func(request, *args, **kwargs)
return inner
return decorator
The response format is given in the same format as rest_framework_jwt.authentication.JSONWebTokenAuthentication. The constant JWT_RECENT_AUTHENTICATION_DELTA is an ad-hoc parameter inserted in the settings.py of the rest_framework_jwt package (a fork).
Finally, in order to use it, one can add the decorator to any view. For example:
#api_view()
#recently_authenticated()
def index_view(request):
data = User.objects.filter()
return Response(UserSerializer(data, many=True).data)
And when the user has been authenticated a while ago, it will send the message {"detail":"you have to reidentify to enter this area"} with the code 401. This can be evaluated and parsed by the frontend and redirect the user to the login.
Note: The decorator only evaluates the time and the time passed. The verification telling whether or not it's a correct user and a correct token is still performed by rest_framework_jwt.authentication.JSONWebTokenAuthentication.
According to the manual: https://getblimp.github.io/django-rest-framework-jwt/#additional-settings
Disallowing refresh token should do the job. The thing is that you will get only 1 token and you won't be able to refresh it after 1 hour.
JWT_AUTH = {
'JWT_ALLOW_REFRESH': False,
'JWT_REFRESH_EXPIRATION_DELTA': timedelta(hours=1),
}
Other problems should be solved on the frontend side. You should check whether user is trying to get to "sensitive" view. If yes, then check if token is valid. If invalid - redirect to login page. If view is "insensitive" - your choice.
The recommended way to handle this is to separate token freshness from token validation. Most views require a valid token, secure views require a fresh token, one that is not only valid but also has been issued at login and has not been refreshed since.
You can do this by setting a flag on the token to mark it as 'fresh' on login, but unset the flag when refreshing. The flow then becomes:
Client accesses the site without a token -> Deny access
Client authenticates with obtain-token endpoint -> Issue token with fresh=True
Client (or server) refreshes valid token -> Issue token with fresh=False
Client accesses non-secure endpoint with valid token -> Accept token
Client accesses secure endpoint -> Accept token only if fresh=True is set on token.
The only way to obtain a fresh token is to log in again, no refresh allowed.
So you need to be able to:
distinguish between obtaining a new token and refreshing when generating the JWT payload.
inspect the fresh key in the current JWT token in order to create a custom permission.
The first requirement means you'll have to do some view subclassing, as the jwt_payload_handler callback is not given any information to determine what called it.
The easiest way to handle the first requirement is to just subclass the serializers used to produce a fresh or refreshed token, decode the token they produce, inject the applicable fresh key value, then re-encode. Then use the subclassed serializers to create a new set of API views:
from rest_framework_jwt.settings import api_settings
from rest_framework_jwt.serializers import JSONWebTokenSerializer, RefreshJSONWebTokenSerializer
from rest_framework_jwt.views import JSONWebTokenAPIView
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
class FreshJSONWebTokenSerializer(JSONWebTokenSerializer):
"""Add a 'fresh=True' flag to the JWT token when issuing"""
def validate(self, *args, **kwargs):
result = super().validate(*args, **kwargs)
payload = jwt_decode_handler(result['token'])
return {
**result,
'token': jwt_encode_handler({**payload, fresh=True})
}
class NonFreshRefreshJSONWebTokenSerializer(RefreshJSONWebTokenSerializer):
"""Set the 'fresh' flag False on refresh"""
def validate(self, *args, **kwargs):
result = super().validate(*args, **kwargs)
payload = jwt_decode_handler(result['token'])
return {
**result,
'token': jwt_encode_handler({**payload, fresh=False})
}
class ObtainFreshJSONWebToken(JSONWebTokenAPIView):
serializer_class = JSONWebTokenSerializer
class NonFreshRefreshJSONWebToken(JSONWebTokenAPIView):
serializer_class = NonFreshRefreshJSONWebTokenSerializer
obtain_jwt_token = ObtainFreshJSONWebToken.as_view()
refresh_jwt_token = NonFreshRefreshJSONWebToken.as_view()
Then register these two views as API endpoints instead of those provided by the Django REST Framework JWT project, for the obtain and refresh paths.
Next up is the permission; because the JSONWebTokenAuthentication class returns the decoded payload when authenticating the request.auth attribute is set to the payload dictionary, letting us inspect it directly in a custom permission:
class HashFreshTokenPermission(permissions.BasePermission):
message = 'This endpoint requires a fresh token, please obtain a new token.'
def has_permission(self, request, view):
return (
request.user and
request.user.is_authenticated and
request.auth and
isinstance(request.auth, dict) and
request.auth.get('fresh', False)
)
Register this permission with the REST framework:
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
'yourmodule.HashFreshTokenPermission',
),
# ...
}
and use this in views that are security sensitive:
class SampleSecureView(APIView):
permission_classes = (
permissions.IsAuthenticated,
HashFreshTokenPermission,
)
# ...
I found this snippet of code that helps me authenticate a user and then create a rest_framework token for them. The client I am using is a native android app and I will get the access token from the client side and post it to django in the ObtainAuth class.
Here is the code for the server side.
#psa('social:complete')
def register_by_access_token(request, backend):
backend = request.strategy.backend
# Split by spaces and get the array
auth = get_authorization_header(request).split()
if not auth or auth[0].lower() != b'token':
msg = 'No token header provided.'
return msg
if len(auth) == 1:
msg = 'Invalid token header. No credentials provided.'
return msg
access_token = auth[1]
user = backend.do_auth(access_token)
return user
class ObtainAuthToken(APIView):
model = Token
serializer_class = AuthTokenSerializer
parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,)
renderer_classes = (renderers.JSONRenderer,)
def post(self,request):
serializer = self.serializer_class(data= request.DATA)
if backend == 'auth':
if serializer.is_valid:
token, created = Token.objects.get_or_create(user=serializer.object['user'])
if token:
return Response({'token': token.key})
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
else:
user = register_by_access_token(request, backend)
if user and user.is_active:
token, created = Token.objects.get_or_create(user=user)
return Response({'id': user.id, 'email': user.email, 'firstname': user.first_name, 'userRole': 'user', 'token': token.key})
The register_by_access_token method will get the facebook access token and then create a user with the rest_framework.It takes a request and the backend to be used e.g 'facebook'.
If a user logs in with my backend then the backend is 'auth' and it uses the normal process of retrieving the email and password and then giving me a token to use.As detailed here
My question is how do I post the authentication backend be it 'facebook' or 'auth' so that I can receive the token?
What I've tried.
I have tried sending the backend type ('facebook' or 'auth') with the access token but I get an error that the method takes 3 arguments and I've only provided 2.
I've tried making the url take a backend like this:
url(r'^login/(?P<backend>[^/]+)/$',views.ObtainAuthToken.as_view())
then sending the access token to a url like this mysite.com:8000/login/facebook.
None of these work and I don't have much expereience with psa or django to know how to pass this parameter.
How do I send which backend to use so that it can be accepted by the method? If anyone has ever had this use case please help me out.
according to my understanding social login requires a access token , so when you are login with facebook when you call 'mysite.com:8000/login/facebook' it is expecting a access token,
for my project i defined my urls as 'url(r'^login/(?P[^/]+)/$',register_by_access_token,name='register_by_access_token')',with the token i am sending it completes the login, for facebook i send backend as 'facebook' for google i send backend as 'google-oauth2' but both the case i am sending a token given my respective backend, when you are not using a third party backend you wont get the token and the login expects that.
so my suggestion is if you are going with auth use normal login post, not the same URL.
When I make a redirect from main.py, it works, but when I try to redirect from within a method that it calls, nothing happens. There is no error, the program simply does nothing.
main.py
from githubauth import GetAuthTokenHandler
class AuthUser(webapp2.RequestHandler):
"""
If no environment variable exists with the access token in it,
auth the user as an admin. If it does exist, auth them as a regular
user.
"""
def get(self):
if not ACCESS_TOKEN:
# No access token exists, auth user as admin
get_auth_token = GetAuthTokenHandler()
get_auth_token.get()
githubauth.py
import webapp2
class GetAuthTokenHandler(webapp2.RequestHandler):
"""Redirect users to github to get an access request token."""
def get(self):
self.redirect('http://api.github.com/authorize')
It depends on what kind of authorization you're doing with Github, there are two ways to do that, OAuth token authorization and Web Application Flow.
OAuth Token Authorization
If you're doing OAuth authorization, you don't have to create a request handler to fetch Github auth token, request handler is for serving specific url on your server, for this kind of task, you should use urlfetch().
So the whole flow should be like the following code:
import webapp2
from google.appengine.api import urlfetch
def getAuthToken():
github_auth_url = "http://api.github.com/authorizations"
result = urlfetch.fetch(github_auth_url)
return result
class AuthUser(webapp2.RequestHandler):
def get(self):
if not ACCESS_TOKEN:
# No access token exists, auth user as admin
get_auth_token = getAuthToken()
# do something with your token...
Redirect Authorization (Web Application Flow)
This is the case if you have applied a client id, and want to be authorized by users as a standalone web application, the steps of this kind authorization is more complicated than former one:
Redirect users to request GitHub access
GitHub redirects back to your site
If you don't know about this flow, take a look at Github OAuth - Web Application Flow
Let's see how could we do within Google App Engine
Redirect users to request Github access
This is the part which involved in your sample, simply redirect user to the authorize url with specified parameters
from urllib import urlencode
class AuthUser(webapp2.RequestHandler):
def get(self):
# ... do something ...
# Github configuration
github_client_id = "Your github client id..."
github_redirect_url = "Your url for github redirecting users back to your GAE"
github_scope = "Gtihub scopes...."
github_authorize_url = "http://github.com/login/oauth/authorize"
github_authorize_parameters = {
'client_id': github_client_id,
'redirect_url': github_redirect_url,
'scope': github_scop
}
if not ACCESS_TOKEN:
# if no access_token found on your site, redirect users to Github for authorization
url_to_redirect = "%s?%s" % (github_authorize_url, urlencode(github_authorize_parameters))
self.redirect(url_to_redirect)
Github redirects users back to your site
Github will redirect users back to your site based on the previous parameter redirect_url, so you will have to prepare another request handler for receiving redirection from Github.
(You can do this is the same request handler, but it will mess your code)
The redirection back from the step 1 will contains one parameter, code, you will need it to exchange for an access token.
from urllib import urlencode
class GithubRequestHandler(webapp2.RequestHandler):
def get(self):
# this handler need to be bind to redirect_url
# get authentication code
github_code = self.request.get('code')
# prepare data to exchange access token
github_token_url = "https://github.com/login/oauth/access_token"
github_token_parameters = {
'client_id': 'Your Github client id',
'client_secret': 'Your Github client secret',
'code': github_code}
# exchange access token
data = urlfetch.fetch(github_token_url, payload=urlencode(github_token_parameter), method='POST')
# data will perform in the following form:
# access_token=e72e16c7e42f292c6912e7710c838347ae178b4a&scope=user%2Cgist&token_type=bearer
# extract access_token from the string
# save the access_token to user's model
P.S.
the code is kinda of simulation of your application flow, it needs some tuning to be able to run on production :)
You try to create a webapp2 request handler, but it cannot be done this way. get_auth_token is not a WSGI webapp2 handler instance.
If you do not change githubauth.py you have to change your main.py.
class AuthUser(webapp2.RequestHandler):
def get(self):
if not ACCESS_TOKEN:
self.redirect(to your GetAuthTokenHandler)
This will result in two redirects if you do not have an access token.
RequestHandler needs to be instantiated with a request and a response for things to work properly.
That said, instantiating one and calling methods on it from inside the handler-method of another is pretty weird.