I have a very interesting issue. We have built couple of apis using django rest framework. These apis are consumed by frontend designed using React. We use SAML withe sessions to manage the authentication process.This SAML authentication is handled by a django middleware.
Now I have a requirement to make some of these apis accessible to a different domain, where their backend would call our apis programmatically. So I need to give auth details to them for them to able to access it.
Since SAML is enabled here, how do I able to set the authentication using JWT and give them the tokens?
The saml middleware code looks like this,
class SamlMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if hasattr(request, 'user') and request.user.is_anonymous and 'samlUserdata' in request.session:
data = request.session['samlUserdata']['mail'][0]
if data is not None:
try:
email=data
user = User.objects.get(email=email)
except ObjectDoesNotExist:
user = User.objects.create_user(username=data.split("#")[0],
email=data)
login(request,user)
request.session.set_expiry(settings.SESSION_EXPIRY)
elif not settings.DEBUG and 'samlUserdata' not in request.session \
and not request.get_full_path().startswith('/admin'):
if not request.path == settings.REDIRECT_LOGIN_URL and not request.path == REDIRECT_LOGOUT_URL and \
not request.path == '/saml':
return redirect(settings.REDIRECT_LOGIN_URL)
response = self.get_response(request)
request.session.set_expiry(settings.SESSION_EXPIRY)
if request.user.is_authenticated:
tokens=self.__class__.get_tokens_for_user(self,request,request.user)
request.session.set_expiry(settings.SESSION_EXPIRY)
return response
#staticmethod
def get_tokens_for_user(self,request,user):
refresh = RefreshToken.for_user(user)
return {
'refresh': str(refresh),
'access': str(refresh.access_token),
}
return response
Now for this I am trying to use djangorestframework-simplejwt. But since SAML is used here, I am not sure how do I implement JWT here. Do I have to change something in the above middleware or any other ways?
Related
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
The system uses django-tenant-schemas for separating data. The users are all on a shared schema so they can log in from any subdomain. To prevent them from seeing another companies data there is a middleware.TenantUserRedirectMiddleware which redirects the user to the proper subdomain after authentication.
The problem is that it wants them to login once again after they get to the correct subdomain. I believe one solution is to get the login data from the request object and pass it to sessions then use that data to automatically authenticate the user at the subdomain so that they are not asked for credentials a second time. How do I get the login form data from request?
I have tried this in the Middleware:
class TenantUserRedirectMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if not request.user.is_superuser:
if request.user.is_authenticated:
if request.user.company != request.tenant:
print(request.POST) # This is empty, where is the login data?
if DEVELOPMENT:
return HttpResponseRedirect(
'//' + request.user.company.get_primary_domain().domain + ':8000/login/')
return HttpResponseRedirect('//' + request.user.company.get_primary_domain().domain + '/login/')
response = self.get_response(request)
return response
Since your you are using some multi-tenant solution it is best not to even allow users that try to login to the other subdomains, you can do this by implementing a custom authentication backend. Add this to some file in a suitable app of yours:
from django.contrib.auth.backends import ModelBackend
class TenantBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
user = super().authenticate(request, username, password, **kwargs)
if user and user.company == request.tenant:
return user
return None
Now you need to add this to the list AUTHENTICATION_BACKENDS in your settings
AUTHENTICATION_BACKENDS = [
# We replace the below backend by our own
# 'django.contrib.auth.backends.ModelBackend',
'yourapp.path_to.TenantBackend',
]
Also if you actually want to allow the login to persist over subdomains you can set SESSION_COOKIE_DOMAIN [Django docs] in the settings like so:
SESSION_COOKIE_DOMAIN = ".yourdomain.com"
If you do this your user should be successfully redirected to their correct domain with their login persisting (you would keep your TenantUserRedirectMiddleware for the redirect).
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,)
I am using Django Rest Framework and I've included a 3rd party package called REST framework simple JWT Auth which is the new framework referenced,
and this one, REST framework JWT Auth, which is the old one (I Imagine), since there was no update on github since a long time and maybe not supported for newer versions.
And I'm looking for a way, like this link on stackoverflow-3rd answer, via middlewares, to get the user information for each request in order to apply/save it, in needed, the user object in my models by using django signals.
I checked in documentation and on internet, but I didn't find anything. So, if you already had that case, I will appreciate your help.
Thank you
To get username from the user model you should use request.user. This will give you the authenticated user's info with the request. However if you use simple_jwt, it's not possible to directly use request.user in a middleware because authentication mechanism works in view function.
So you should manually authenticate inside the middleware and then you can use request.user to get whatever data from the user model.
from rest_framework_simplejwt import authentication
class MyMiddleware():
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
return response
def process_view(self, request, view_func, view_args, view_kwargs):
request.user = authentication.JWTAuthentication().authenticate(request)[0] # Manually authenticate the token
print(request.user) # Use the request.user as you want
With simple_jwt the user information will not be set on the request initially but after the request is processed it will be. So if you're just logging the information and you can do it after the request is processed, do it there.
So for example in a simple middleware:
def simple_middleware(get_response):
def middleware(request):
# request.user is an anonymous user at this point
response = get_response(request)
# request.uesr is the logged-in user here
return response
return middleware
I have setup an OAuth2 provider using Django Oauth Toolkit, and it works fine. The issue is that the Client which is making requests to my API does not pass the access token in the headers (No "AUTHORIZATION : BEARER XXXXXXX"). Instead, the access token is passed in JSON data.
How can I change the toolkit's behaviour to read the access token from the data ?
I found a way to satisfy OAuth toolkit's requirements by writing my own Django Middleware.
import json
class TokenMiddlewareFix(object):
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
try:
body = json.loads(request.body)
token = body.get("accessToken", None)
if token is not None:
request.META["HTTP_AUTHORIZATION"] = "Bearer {}".format(token)
except ValueError:
pass
response = self.get_response(request)
return response