Django ninja token authentication with djoser - django

I have implemented CRUD with Django Ninja framework, but now I want auth in my app,
I had installed and config Djoser, so now I can generate tokens, but I don't know how to verify in my CRUD's
class AuthBearer(HttpBearer):
def authenticate(self, request, token):
if token == "supersecret":
return token
#api.get("/bearer", auth=AuthBearer())
def bearer(request):
return {"token": request.auth}
I shoud able to check token inside "AuthBearer" function, but I don't know how
my repo (link)

so basically you have to extend Ninja's HttpBearer class and implement authenticate method, which will accept request and token as parameters. This method returns None if the user is not authenticated, and a string which will be populated in request.auth if the user is authenticated. Usually this string will be the username so you can use it in all your endpoints.
Something like this (I am using PyJWT for token decoding):
import jwt
from ninja.security import HttpBearer
class AuthBearer(HttpBearer):
def authenticate(self, request, token):
try:
#JWT secret key is set up in settings.py
JWT_SIGNING_KEY = getattr(settings, "JWT_SIGNING_KEY", None)
payload = jwt.decode(token, JWT_SIGNING_KEY, algorithms=["HS256"])
username: str = payload.get("sub")
if username is None:
return None
except jwt.PyJWTError as e:
return None
return username

Related

How to write custom authentication backend for one endpoint only (/metrics) in Django?

I have a custom middleware in Django to force all the requests to go through a login authentication (with few exceptions like api/token).
This project allows users to authenticate either via a JWT token or a login in
/admin/login and all unauthenticated users are redirected to /admin/login. for authentication.
We deployed the project in Kubernetes and we want Prometheus to scrape /metrics endpoint but we don't want it to be exposed to unauthenticated users. Prometheus allows for authentication with username and password. The thing is that when a request is sent to /metrics, because of the middleware, the request is redirected to /admin/login.
So I believe I need to write a custom authentication backend specifically designed for the metrics endpoint and place it before the other authentication methods.
The request always goes through the middleware first so it will always be redirected to /admin/login and then will go through the authentication backend.
What is the right way of doing this?
middleware.py
class LoginRequiredMiddleware(MiddlewareMixin):
def __init__(self, get_response):
self.get_response = get_response
def process_request(self, request):
assert hasattr(request, 'user')
path = request.path_info.lstrip('/')
if path == '' or path == '/':
return self.get_response(request)
url_is_exempt = any(url.match(path) for url in EXEMPT_URLS)
if request.user.is_authenticated or url_is_exempt:
# If the user is authenticated OR the URL is in the exempt list
# go to the requested page
return self.get_response(request)
else:
# Trying to access any page as a non authenticated user
return redirect(f"{settings.LOGIN_URL}?next=/{path}")
backends.py
class MetricsAuthBackend(BaseBackend):
def authenticate(self, request, username=None, password=None):
if '/metrics' in request.path:
if username == "username":
#need to fix this to use the hash of the password
pwd_valid = check_password(password, "password")
if pwd_valid:
user = User.objects.get(username=username)
return user
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None

Django : How can we custom login_required decorator?

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

Django Rest API with okta OAUTH token authentication

I have a problem with Okta token authentication, I know how to authenticate with drf token and jwt token auth.
In my project, I have to use okta token which is a type of jwt as well, however, this token is generated by front-end and send back to me in the request
so here you can see how I authenticate the okta token with okta_jwt package:
def post(self, request, *args, **kwargs):
access_token = request.META.get('HTTP_AUTHORIZATION')
try:
validate_token(access_token, config.issuer, config.aud, config.client_id)
except Exception as e:
return JsonResponse({"result": e.args[0]}, status=400)
..........
Basically I have to take the token out from the header and check with okta_jwt to see if it's legal
Obviously, I don't think it's a good solution and it's hard to do unit test
Can anyone provide a better solution for this?
Thanks
I found the solution:
I just created the custom authentication inherit from BaseAuthentication. In the Custom authentication, you can do whatever authenticating process you want:
class OktaAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
access_token = request.META.get('HTTP_AUTHORIZATION')
if not access_token:
return None
payload = validate_token(access_token, config.issuer, config.aud, config.client_id)
try:
user = get_user_model().objects.get(email=payload['sub'])
except User.DoesNotExist:
raise exceptions.AuthenticationFailed('No such user')
return user, None
In the setting.py, making sure you have the custom authentication added as the Default:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'core.authentication.OktaAuthentication',
)}
In the views:
authentication_classes = (OktaAuthentication,)
permission_classes = (IsAuthenticated,)

Custom Auth backend with middleware

I tried looking at this answer, as well as using django sessions here.
The login with my custom auth works fine, but I want to validate the token on every request with middleware, and I can't figure out how to store the token so that it may be accessed from both the middleware as well as views.
I tried storing a session variable from my auth backend, but I would always get a key error when trying to access it from my views.
Is there a good way to do this?
Thanks!
class MyAuthBackend(object):
supports_inactive_user = False
supports_object_permissions = False
supports_anonymous_user = False
def authenticate(self, username=None, password=None):
# This makes a call to my API to varify login, then return token if valid. I need to make login_valid accessible to my middleware and views.
login_valid = auth.login(username,password)
if login_valid:
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
user = User(username=username, password='never_used')
user.is_active = True
user.save()
return user
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
class MyAuthMiddleware(object):
def process_request(self, request):
if not request.user.is_anonymous():
# API call to my backend to check if token is still valid. If not, return to login page.
token_variable = ???????????
if isTokenStillValid(token_variable):
return
else:
return HttpResponseRedirect('/accounts/login/?next=%s' % request.path)
Are you using the default django.contrib.auth login view for logging in? It seems to completely clear the session during the login process (which happens after your authentication backend is called, in contrib.auth.login function, described here).
I think you might either try to write your own login view, with an alternative login function that preserves the auth token, or store the token somewhere else (database table, cache system). The latter might make it difficult to allow multiple simultaneous logins for one user.

Test that user was logged in successfully

How can I test that a user is logged in after submitting the registration form?
I tried the following but it returns True even before I added the login logic to my registration view.
def test_that_user_gets_logged_in(self):
response = self.client.post(reverse('auth-registration'),
{ 'username':'foo',
'password1':'bar',
'password2':'bar' } )
user = User.objects.get(username='foo')
assert user.is_authenticated()
The code that's being tested:
class RegistrationView(CreateView):
template_name = 'auth/registration.html'
form_class = UserCreationForm
success_url = '/'
def auth_login(self, request, username, password):
'''
Authenticate always needs to be called before login because it
adds which backend did the authentication which is required by login.
'''
user = authenticate(username=username, password=password)
login(request, user)
def form_valid(self, form):
'''
Overwrite form_valid to login.
'''
#save the user
response = super(RegistrationView, self).form_valid(form)
#Get the user creditials
username = form.cleaned_data['username']
password = form.cleaned_data['password1']
#authenticate and login
self.auth_login(self.request, username, password)
return response
You can use the get_user method of the auth module. It says it wants a request as parameter, but it only ever uses the session attribute of the request. And it just so happens that our Client has that attribute.
from django.contrib import auth
user = auth.get_user(self.client)
assert user.is_authenticated
This is not the best answer. See https://stackoverflow.com/a/35871564/307511
Chronial has given
an excellent example on how to make this assertion below. His answer
better than mine for nowadays code.
The most straightforward method to test if a user is logged in is by testing the Client object:
self.assertIn('_auth_user_id', self.client.session)
You could also check if a specific user is logged in:
self.assertEqual(int(self.client.session['_auth_user_id']), user.pk)
As an additional info, the response.request object is not a HttpRequest object; instead, it's an ordinary dict with some info about the actual request, so it won't have the user attribute anyway.
Also, testing the response.context object is not safe because you don't aways have a context.
Django's TestClient has a login method which returns True if the user was successfully logged in.
The method is_authenticated() on the User model always returns True. False is returned for request.user.is_authenticated() in the case that request.user is an instance of AnonymousUser, which is_authenticated() method always returns False.
While testing you can have a look at response.context['request'].user.is_authenticated().
You can also try to access another page in test which requires to be logged in, and see if response.status returns 200 or 302 (redirect from login_required).
Where are you initialising your self.client? What else is in your setUp method? I have a similar test and your code should work fine. Here's how I do it:
from django.contrib.auth.models import User
from django.test import TestCase
from django.test.client import Client
class UserTestCase(TestCase):
def setUp(self):
self.client = Client()
def testLogin(self):
print User.objects.all() # returns []
response = self.client.post(reverse('auth-registration'),
{ 'username':'foo',
'password1':'bar',
'password2':'bar' } )
print User.objects.all() # returns one user
print User.objects.all()[0].is_authenticated() # returns True
EDIT
If I comment out my login logic, I don't get any User after self.client.post(. If you really want to check if the user has been authenticated, use the self.client to access another url which requires user authentication. Continuing from the above, access another page:
response = self.client.get(reverse('another-page-which-requires-authentication'))
print response.status_code
The above should return 200 to confirm that the user has authenticated. Anything else, it will redirect to the login page with a 302 code.
There is another succinct way, using wsgi_request in response:
response = self.client.post('/signup', data)
assert response.wsgi_request.user.is_authenticated()
and #Chronial 's manner is also available with wsgi_request:
from django.contrib import auth
user = auth.get_user(response.wsgi_request)
assert user.is_authenticated()
Because response.wsgi_request object has a session attribute.
However, I think using response.wsgi_request.user is more simple.