Basic auth protected views in DRF - python

I have some API endpoints that i need to protect using HTTP Basic Authentication in Django Rest Framework. There is BasicAuthentication in DRF, but that actually authenticates against a user in Django, which is not what I'm looking for.
I found a solution using a custom permission, but ti means monkey patching the views to set the correct authenticate header.
Is there a better way?
class BasicAuthPermission(permissions.BasePermission):
def has_permission(self, request, view):
credentials = view.credentials # Will raise AttributeError on missing credentials
realm = getattr(view, 'realm', 'Protected')
auth = request.headers.get('Authorization')
with suppress(ValueError, AttributeError):
auth = b64decode(auth.split()[-1]).decode()
if auth != credentials:
# Monkey patch style
view.get_authenticate_header = lambda r: f'Basic realm="{realm}"'
raise exceptions.AuthenticationFailed('Bad credentials.')
return True
Im my view:
class ProtectedApiView(generics.GenericAPIView):
permission_classes = [BasicAuthPermission]
credentials = 'user:password'
# ...

Following Arakkal's suggestion in comment, I did this with an Authentication class instead. It does feel less hacky, but I can not set credentials on the View, like I did originally.
I realize "anonymous authentication" is a weird name, but that's because Django doesn't know anything about the user. So for all practical purposes anonymous.
from base64 import b64decode
import binascii
from rest_framework import generics, exceptions, authentication
class AnonymousBasicAuthentication(authentication.BaseAuthentication):
"""
HTTP Basic authentication against preset credentials.
"""
www_authenticate_realm = 'api'
credentials: str = None
def authenticate(self, request):
try:
auth, encoded = authentication.get_authorization_header(request).split(maxsplit=1)
except ValueError:
raise exceptions.AuthenticationFailed('Invalid basic header.')
if not auth or auth.lower() != b'basic':
raise exceptions.AuthenticationFailed('Authentication needed')
try:
credentials = b64decode(encoded).decode(authentication.HTTP_HEADER_ENCODING)
except (TypeError, UnicodeDecodeError, binascii.Error):
raise exceptions.AuthenticationFailed('Invalid basic header. Credentials not correctly base64 encoded.')
if self.credentials != credentials:
raise exceptions.AuthenticationFailed('Invalid username/password.')
def authenticate_header(self, request):
return 'Basic realm="{}"'.format(self.www_authenticate_realm)
class MyAuthentication(AnonymousBasicAuthentication):
credentials = 'user:password'
class MyProtectedView(generics.GenericAPIView):
authentication_classes = [MyAuthentication]
# ...

Related

Custom backend authentication returns 401 unauthorized in Django

I want to authenticate Shopify requests that come from the Shopify app bridge. Simply, it adds encoded data into headers and sends requests to my app's endpoints.
Normally, my app uses JWT tokens to authenticate requests from the front-end stack (React). However, the Shopify requests require a different kind of authentication process where I have to decode the data from the header, then validate the decoded data and finally return the proper request user.
As a solution, I wrote a custom backend that handles all processes above:
backends.py
from django.contrib.auth.models import User
from shopify import session_token
class ShopifySessionTokenBackend(BaseBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
shopify_session_token = request.headers.get("Authorization")
try:
# validation process for Shopify Session Token
# ...
if shopify_user:
return shopify_user.user # returning proper user
except Exception as exc:
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
First, the request goes through middleware where the authenticate() function is called. Next, the authenticate() function collects all available backends to authenticate the request user if it passes all validations. In this case, validations are just a few checkings required by Shopify to verify the request authenticity.
After validations passed, retrieved user from DB sets to request.user which means authentication process was successful:
middleware.py
from django.contrib.auth import authenticate
from django.utils.deprecation import MiddlewareMixin
class ShopifyMiddleware(MiddlewareMixin):
def process_request(self, request):
if not hasattr(request, "user") or request.user.is_anonymous:
user = authenticate(request=request)
if user:
request.user = request._cached_user = user
The question is - response always returns with status 401 Unauthorized which is caused by reinitialization of request while sending a response. So, request.user changed back to AnonymousUser.
At this point, I don't have any clue about the reasons. Any help is appreciated.

Django to return a view with TokenAuthentication for WebView

I am trying to create a flutter app which will use webview to display authenticated data from my Django App.
Steps Involved:
Flutter app sends authentication request
Django validates the user credentials (user id & Password) and returns authtoken
Flutter then sends a request via a webview to a url (which requires login).
I would like to login the user in webapp using this token and return the webview.
If the url does not require authentcation, it works like a charm.
When the url requires authentication, I am redirected to the login page and I want users to bypass that using token authentication which is already aquired in Step 1
here is my Django view.
class QuizTake(FormView):
permission_classes = (IsAuthenticated,)
form_class = QuestionForm
template_name = 'question.html'
result_template_name = 'result.html'
single_complete_template_name = 'single_complete.html'
login_template_name='login.html'
def dispatch(self, request, *args, **kwargs):
self.quiz = get_object_or_404(Quiz, url=self.kwargs['quiz_name'])
print(self.kwargs['quiz_name'])
"""
Authenticate if the request has token authentication
"""
if self.quiz.draft and not request.user.has_perm('quiz.change_quiz'):
raise PermissionDenied
try:
self.logged_in_user = self.request.user.is_authenticated()
except TypeError:
self.logged_in_user = self.request.user.is_authenticated
if self.logged_in_user:
self.sitting = Sitting.objects.user_sitting(request.user,
self.quiz)
else:
self.sitting = self.anon_load_sitting()
if self.sitting is False:
print("sitting false")
if self.logged_in_user:
return render(request, self.single_complete_template_name)
else:
redirecturl = "/login/?next=/quiz/"+self.kwargs['quiz_name']+"/take/"
return redirect(redirecturl)
return super(QuizTake, self).dispatch(request, *args, **kwargs)
Flutter Code
class _QuizLauncherState extends State<QuizLauncher> {
final String url, authtoken;
final int userId;
String quizUrl;
_QuizLauncherState(this.url, this.authtoken,this.userId);
void initState() {
quizUrl = 'https://test.mysite.com/quiz/$url/take';
print(quizUrl);
//for reference https://test.mysite.com/quiz/56df5d90-7f67-45ff-8fe1-7c07728ba9ab/take/
super.initState();
}
Completer<WebViewController> _controller = Completer<WebViewController>();
final Set<String> _favorites = Set<String>();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// This drop down menu demonstrates that Flutter widgets can be shown over the web view.
actions: <Widget>[
NavigationControls(_controller.future),
Menu(_controller.future, () => _favorites),
],
),
body: WebView(
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
Map<String, String> headers = {"Authorization": "Bearer " + authtoken};
webViewController.loadUrl(quizUrl, headers: headers);
},
),
);
}
}
Is this possible at all? If there are any alternate ways, please tell me. Basically, I am trying to access a url via webview which requires authentication, using authtoken. Please help.
You can use custom authentication classes like this, say if you are using Authorization header:
from rest_framework.authentication import BaseAuthentication
class MyCustomAuth(BaseAuthentication):
def authenticate(self, request):
auth_method, token = request.META['HTTP_AUTHORIZATION'].split(' ', 1)
# Get your user via the token here
if you_got_your_user:
return user, None
return None # or raise AuthFailedException
class QuizTake(FormView):
authentication_classes = (MyCustomAuth, )
This still depends on how your token identifies the user though. For example if you are using JWT, there are existing authentication classes already that handles this for you.
EDIT:
Looked at knox documentation from here. If you used knox, then you should probably use their own TokenAuthentication class. Can you try with below code:
from knox.auth import TokenAuthentication
class QuizTake(FormView):
authentication_classes = (TokenAuthentication, )
You can use authentication from rest framework lib like as below code.
import base64
import binascii
from django.contrib.auth import authenticate, get_user_model
from django.middleware.csrf import CsrfViewMiddleware
from django.utils.translation import gettext_lazy as _
from rest_framework import HTTP_HEADER_ENCODING, exceptions
def get_authorization_header(request):
auth = request.META.get('HTTP_AUTHORIZATION', b'')
if isinstance(auth, str):
auth = auth.encode(HTTP_HEADER_ENCODING)
return auth
class BaseAuthentication:
raise NotImplementedError(".authenticate() must be overridden.")
def authenticate_header(self, request):
pass
class SessionAuthentication(BaseAuthentication):
user = getattr(request._request, 'user', None)
if not user or not user.is_active:
return None
self.enforce_csrf(request)
return (user, None)
def enforce_csrf(self, request):
def dummy_get_response(request):
return None
check = CSRFCheck(dummy_get_response)
check.process_request(request)
reason = check.process_view(request, None, (), {})
if reason:
raise exceptions.PermissionDenied('CSRF Failed: %s' % reason)
class TokenAuthentication(BaseAuthentication):
keyword = 'Token'
model = None
def get_model(self):
if self.model is not None:
return self.model
from rest_framework.authtoken.models import Token
return Token
Or go through the below link for better understanding
[Toke Authorization]

Django Graphql middleware decode token

In my project I used Django and GraphQl for building API. User will be authenticated by API Gateway in AWS, and send a JWT token, with uuid username body, included in the request headers to the backend.
I need to decode that token and get an username value, that will be next used in the resolvers.
I have planned to use something similar as G object in Flask or something similar using Rack Djangos middleware but I'm struggling how to do it in Django.
Do you have any idea or hint?
Here's the result which I implemented:
The middleware checks the jwt token before resolver call, based on decoded username it create a User instance, that is assigned in info.context.user parameter.
The info.context will be visible in resolver.
So basically in resolver you can check a User instance as:
user = info.context.user
if isinstance(user, User):
# do something
middleware.py
class AuthorizationGraphQLMiddleware:
"""Middleware add User object for each GraphQL resolver info.context"""
def resolve(self, next, root, info, **kwargs):
username = None
auth_header = info.context.META.get('HTTP_AUTHORIZATION')
if auth_header:
username = decode_token(auth_header)['username']
if username is not None:
info.context.user = User(username)
else:
info.context.user = AnonymousUser()
return next(root, info, **kwargs)
entities.py
#dataclass
class User:
name: str
utils.py
class TokenValidationError(GraphQLError):
pass
def decode_token(token):
try:
return jwt.decode(token.replace('Bearer ', ''), verify=False)
except (jwt.DecodeError, AttributeError):
raise TokenValidationError('Invalid token.')
settings.py
GRAPHENE = {
'MIDDLEWARE': ('api.graphql.middleware.AuthorizationGraphQLMiddleware',)
}

How can I use the jwt token for authentication that i get from my login view

I need to create JWT token authentication, but I don't know how, could you explain me how to do it better or put some examples?
my view:
class UserLogin(generics.CreateAPIView):
"""
POST auth/login/
"""
# This permission class will overide the global permission
# class setting
permission_classes = (permissions.AllowAny,)
queryset = User.objects.all()
serializer_class = TokenSerializer
def post(self, request, *args, **kwargs):
username = request.data.get("username", "")
password = request.data.get("password", "")
user = auth.authenticate(request, username=username, password=password)
if user is not None:
auth.login(request, user)
return Response({
"token": jwt_encode_handler(jwt_payload_handler(user)),
'username': username,
}, status=200)
return Response(status=status.HTTP_401_UNAUTHORIZED)
You are creating the token in that view. After that, you need two other mechanism in place:
Your client should send this token the the API with each request, in the Authorization header, like:
Authorization: Bearer your_token
On the api side, you need to use an authentication class, that looks for Authorization header, takes the token and decodes it, and finds the user instance associated with the token, if the token is valid.
If you are using a library for drf jwt authentication, it should have an authentication class that you can use. If you are implementing it manually, you need to write an authentication class that subclasses DRF's BaseAuthentication class yourself. It could basically look like this:
class JwtAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
auth_header = request.META.get('HTTP_AUTHORIZATION')
if auth_header:
key, token = auth_header.split(' ')
if key == 'Bearer':
# Decode the token here. If it is valid, get the user instance associated with it and return it
...
return user, None
# If token exists but it is invalid, raise AuthenticationFailed exception
# If token does not exist, return None so that another authentication class can handle authentication
You need to tell DRF to use this authentication class. Add this to your settings file for that:
REST_FRAMEWORK = {
...
'DEFAULT_AUTHENTICATION_CLASSES': [
'path.to.JwtAuthentication',
...
]
}

Django: Basic Auth for one view (avoid middleware)

I need to provide http-basic-auth to one view.
I want to avoid modifying the middleware settings.
Background: This is a view which gets filled in by a remote application.
When you do a basic auth request, you're really adding credentials into the Authorization header. Before transit, these credentials are base64-encoded, so you need to decode them on receipt.
The following code snippet presumes that there's only one valid username and password:
import base64
def my_view(request):
auth_header = request.META.get('HTTP_AUTHORIZATION', '')
token_type, _, credentials = auth_header.partition(' ')
expected = base64.b64encode(b'username:password').decode()
if token_type != 'Basic' or credentials != expected:
return HttpResponse(status=401)
# Your authenticated code here:
...
If you wish to compare to the username and password of a User model, try the following instead:
def my_view(request):
auth_header = request.META.get('HTTP_AUTHORIZATION', '')
token_type, _, credentials = auth_header.partition(' ')
username, password = base64.b64decode(credentials).split(':')
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
return HttpResponse(status=401)
password_valid = user.check_password(password)
if token_type != 'Basic' or not password_valid:
return HttpResponse(status=401)
# Your authenticated code here:
...
Please note that this latter version is not extremely secure. At first glance, I can see that it is vulnerable to timing attacks, for example.
You can try a custom decorator (as seems to be the recommended way here and here) instead of adding new middleware:
my_app/decorators.py:
import base64
from django.http import HttpResponse
from django.contrib.auth import authenticate
from django.conf import settings
def basicauth(view):
def wrap(request, *args, **kwargs):
if 'HTTP_AUTHORIZATION' in request.META:
auth = request.META['HTTP_AUTHORIZATION'].split()
if len(auth) == 2:
if auth[0].lower() == "basic":
uname, passwd = base64.b64decode(auth[1]).decode(
"utf8"
).split(':', 1)
user = authenticate(username=uname, password=passwd)
if user is not None and user.is_active:
request.user = user
return view(request, *args, **kwargs)
response = HttpResponse()
response.status_code = 401
response['WWW-Authenticate'] = 'Basic realm="{}"'.format(
settings.BASIC_AUTH_REALM
)
return response
return wrap
Then use this to decorate your view:
from my_app.decorators import basicauth
#basicauth
def my_view(request):
...
This library could be used: https://github.com/hirokiky/django-basicauth
Basic auth utilities for Django.
The docs show how to use it:
Applying decorator to CBVs
To apply #basic_auth_requried decorator to Class Based Views, use
django.utils.decorators.method_decorator.
Source: https://github.com/hirokiky/django-basicauth#applying-decorator-to-cbvs
For those that already use django-rest-framework (DRF):
DRF has a BasicAuthentication class which, more-or-less, does what is described in the other answers (see source).
This class can also be used in "normal" Django views.
For example:
from rest_framework.authentication import BasicAuthentication
def my_view(request):
# use django-rest-framework's basic authentication to get user
user = None
user_auth_tuple = BasicAuthentication().authenticate(request)
if user_auth_tuple is not None:
user, _ = user_auth_tuple

Categories

Resources