OIDC with Keycloak and Django - Missing States - django

I'm attempting to set up OIDC with Keycloak as my IdP and Django (using Django Rest Framework and mozilla-django-oidc) as my client server. I have got keycloak installed and a rough Django application stood up that successfully redirects to keycloak where I can then successfully authenticate (to keycloak), but when I'm redirected back to django I'm missing information, specifically oidc_states.
The redirect to django triggers this log entry:
[12/Oct/2021 08:28:06] "GET /api/oidc/callback/?state=QGsO26esqdtHZcsfRfYoXvUy0QWcGdZv&session_state=493887a4-600e-4dd2-aaaf-4134ea671c9a&code=dfe1573e-cf8e-4829-8874-a3500ba63712.493887a4-600e-4dd2-aaaf-4134ea671c9a.c1dfdb46-140c-4ccd-8308-6db3b468346a HTTP/1.1" 302 0
This contains three keys: state, session_state, and code.
The default callback view provided by mozilla-django-oidc contains this:
def get(self, request):
"""Callback handler for OIDC authorization code flow"""
if request.GET.get('error'):
if request.user.is_authenticated:
auth.logout(request)
assert not request.user.is_authenticated
elif 'code' in request.GET and 'state' in request.GET:
if 'oidc_states' not in request.session:
return self.login_failure()
# ...
Because keycloak isn't making the redirect with oidc_states added, this is immediately failing and I haven't been able to figure out why. I'm guessing that the problem is with my keycloak client configuration?

To any wayward travelers, I wish you luck. Ultimately, I solved my problem by re-configuring my mozilla-django-oidc settings. Specifically, I was missing:
AUTHENTICATION_BACKENDS = (
"django.contrib.auth.backends.ModelBackend", # default
"mozilla_django_oidc.auth.OIDCAuthenticationBackend",
)
This allowed for my application to authenticate both with the existing flow and using the new OIDC flow. The call to authenticate in the OIDC callback view was failing because I didn't have this authentication backend specified, so it was trying to just use the default.

Related

csrf_exempt set but CSRF Failed: Referer checking failed - no Referer

I have a backend API, it's in django and deployed on Google Endpoint.
I have a post request that insert data to my DB.
I created a script to use this endpoint but I got this error:
{"detail":"CSRF Failed: Referer checking failed - no Referer."}
Regarding over posts I added the crsf_exempt decorator to my class but it did not change.
I try to add the decorator two ways:
class AddUser(APIView):
""" Create user and company from csv """
#method_decorator(csrf_exempt)
def post(self, request):
#method_decorator(csrf_exempt, name='dispatch')
class AddUser(APIView):
""" Create user and company from csv """
def post(self, request):
But both failed.
This is how I contact my endpoint:
resp = requests.request(
method, url,
headers={'Authorization': 'Bearer {}'.format(
open_id_connect_token)}, **kwargs)
Any ideas ?
Thanks
EDIT
So I tried to add authentication classes to my views but it appears to be a bad idea. This is being real trouble for me.
I tried to get the csrftoken doing like this:
client = requests.session()
# Retrieve the CSRF token first
client.get(url) # sets cookie
print(client.cookies)
if 'csrftoken' in client.cookies:
# Django 1.6 and up
csrftoken = client.cookies['csrftoken']
else:
# older versions
csrftoken = client.cookies
Thing is, I am using IAP to protect my API and I do not have any csrftoken cookie but I do have a something looking like this:
<RequestsCookieJar[<Cookie GCP_IAP_XSRF_NONCE_Q0sNuY-M83380ypJogZscg=1
for ...
How can I use this to make post request to my API ?
So this happened to me because I did not set any authentication_classes to my generic view.
When this option is not set Django automatically use the SessionBackend, which need the csrf token.
I fixed it by adding this to my view: authentication_classes = [ModelBackend, GoogleOAuth2]
#Kimor - Can you try doing this in your urls.py
from django.views.decorators.csrf import csrf_exempt
url('^test/$', csrf_exempt(views.TestView.as_view())),
The get and post methods defined on the APIView class just tell DRF how the actual view should behave, but the view method that the Django router expects is not actually instantiated until you call TestView.as_view().
source
Django REST Framework CSRF Failed: CSRF cookie not set
So after working on this project for a while this is what I learned regarding the CSRF using Django.
First of all, if you are using django templates, or in any cases where your back-end and front-end are running behind the same server the most common practice is to use session for authentication.
This is activated by default by DRF.
This means that in your DRF configuration if you do not explicitly set the DEFAULT_AUTHENTICATION_CLASSES option default authentication will be set to Session + BasicAuth.
In this configuration you'll need to manage the CSRF token as described in the documentation (https://docs.djangoproject.com/en/4.0/ref/csrf/).
If your back-end and front-end are separated as in my case, using CSRF is not the only solution or even the recommended one.
As in my case I use JWT behind IAP (Identity Aware Proxy, provided by google). I had to write my own authentication classes and then use it in my DRF conf:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'main.authentication_backend.custom_auth.IAPAuthentication'],
...
}
Here is explain how to write your own authentication class: https://www.django-rest-framework.org/api-guide/authentication/#custom-authentication.

Djoser password reset implementation

I am using djosers for my authentication on django backend which eventually i'll be connecting to flutter frontend and i am having trouble implementing the password reset functionality...
from what i have understood, first i need to hit the /users/reset_password/ with email body which will eventually give me the token of authentication which will be used further on confirm reset but the first thing i dont understand is PASSWORD_RESET_CONFIRM_URL field in the settings, like it needs a front end link with uid and token placeholders but what is this token field and what is this PASSWORD_RESET_CONFIRM_URL but i managed to look over a stack overflow question and filled it but now when i hit /users/reset_password/ i get this error:
[WinError 10013] An attempt was made to access a socket in a way forbidden by its access permissions
settings:
DJOSER = {
'PASSWORD_RESET_CONFIRM_URL':'reset/password/reset/confirm/{uid}/{token}',
'LOGIN_FIELD' : 'email',
'USER_CREATE_PASSWORD_RETYPE' : True,
'SERIALIZERS': {
'user_create': 'auth_app.serializers.UseriCreateSerializer',
'user': 'auth_app.serializers.UserCreateSerializer',
}
}
urls.py:
urlpatterns = [
path('',home,name='home'),
path('addInForum/',addInForum,name='addInForum'),
path('addInDiscussion/',addInDiscussion,name='addInDiscussion'),
path('<str:forum_id>/getDiscussion/',getDiscussion,name='getDiscussion'),
path('getDate/',getDate,name='getDate'),
path('reset/password/reset/confirm/<str:uid>/<str:token>/',PasswordResetView,name='PasswordResetView'),
# url(r'^reset/password/reset/confirm/(?P<uid>[\w-]+)/(?P<token>[\w-]+)/$', PasswordResetView.as_view(),),
]
views.py
#api_view(['GET'])
def PasswordResetView(request,uid,token):
post_data = {'uid': uid, 'token': token}
return Response(post_data)
Please remember that djoser should be part of your REST API based on Django REST Framework. You also need to think differently about the url routing in regard of your frontend app..
Usually urls in the form mydomain.com/some_url/whatever are considered "frontend urls" and parsed by routing of your frontend app. On the other hand urls in the form mydomain.com/api/something are considered API urls that are routed via Django's urls.py. I will refer to them as Fronted_URL and API_URL respectively.
So: resetting password works like this. The user that forgot their password and wants to reset it, surely needs to fill some king of form. This form needs to be sent to APIURL returned by resolve('user-reset-password') (by default this returns something like /users/reset_password/)
Here comes PASSWORD_RESET_CONFIRM_URL setting. Because after the body is accepted by the APIURL mentioned above, a mail will be sent to the user with a link that will point to URL entered in that setting. And it has to be FrontendURL! It should be routed by your frontend APP and preferably display some screen. But in the background your frontend app should send the values of uid and token fields to APIURL returned by resolve("user-reset-password-confirm").
This flow allows your frontend app to properly handle the response and display appropriate message to the user and then maybe redirect them to some other screen.
If you don't have a routed frontend app (probably written using REACT, ANGULAR or VUE) then you probably don't need a REST API and should just stick to django-allauth.

Facebook OAuth login for mobile app with python-social-auth

I'm currently writing a mobile application in Xamarin and Django with Python Social Auth, my application requires personalisation so I decided to use Facebook authentication instead of allowing users to store a username/password combination due to the security implications.
This works fine when using a normal web browser due to it remembering states, but the tricky part comes when trying to use Xamarin. I tried using Xamarin.Auth, but this caused an error due to the fact that it expected an access_token in the response [1], which I'm unable to get from the Python Social Auth library as it expects a hard-coded redirect after completion [2], which in turn causes a conflict between both libraries.
I tried looking at the following example code, but this expects the mobile app to ALREADY have an access token (taken from a similar question here):
from django.contrib.auth import login
from social.apps.django_app.utils import psa
# Define an URL entry to point to this view, call it passing the
# access_token parameter like ?access_token=<token>. The URL entry must
# contain the backend, like this:
#
# url(r'^register-by-token/(?P<backend>[^/]+)/$',
# 'register_by_access_token')
#psa('social:complete')
def register_by_access_token(request, backend):
# This view expects an access_token GET parameter, if it's needed,
# request.backend and request.strategy will be loaded with the current
# backend and strategy.
token = request.GET.get('access_token')
user = request.backend.do_auth(request.GET.get('access_token'))
if user:
login(request, user)
return 'OK'
else:
return 'ERROR'
If anyone has any experience with using Xamarin and Python Social Auth, it would be really appreciated if you could point me into the right direction with this.

Middleware handler for Mobile and Browser API calls

I want to develop a Django based web service where my web application (Platform:Angular JS) and mobile applications (Platform: iOS,android,Windows Phone) will communicate with it.
My django web service app only for handling API calls. For security reasons I chose Oauth toolkit for mobile application,for web Session Authentication?
Settings.py for Authentication,
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication',
'oauth2_provider.ext.rest_framework.OAuth2Authentication',
),
My problem:
I want to handle CSRF based authentication if I received a call from browser side.
Call Oauth authentication if I received call from Native Mobile client .
How to diferentiate the two, and how to handle this authentication technique.
Need your help!!
You can use django-mobile library to detect requests from mobile browsers and then call diferent authentication classes.
django-mobile library defines MobileDetectionMiddleware and SetFlavourMiddleware middleware classes in it.
MobileDetectionMiddleware detects if the requests originated from a mobile or a web browser.
SetFlavourMiddleware class sets the flavour attribute in the request. There are 2 possible values for flavour:
'mobile' # Mobile requests
'full' # Web requests
After the correct flavour is somehow chosen by the middlewares, it's assigned to the request.flavour attribute.
Step-1: Configure django-mobile in your application
Follow the steps given in https://github.com/gregmuellegger/django-mobile#installation to install and configure django-mobile in your application.
After it has been configured, you can just use request.flavour to check if request is from a mobile browser or a web browser.
Step-2: Create a custom WebSessionAuthentication class
Since you want to use OAuth's OAuth2Authenticationfor mobile application and DRF's SessionAuthentication for web, we can create a custom WebSessionAuthentication class which will inherit from the DRF's SessionAuthentication.
This WebSessionAuthentication class will perform no authentication for mobile requests. In case of web requests, it will perform proper session authentication with CSRF checks.
from rest_framework.authentication import SessionAuthentication
class WebSessionAuthentication(SessionAuthentication):
"""
Performs session authentication for web requests
"""
def authenticate(self, request):
"""
Returns a `User` if the request session currently has a logged in user
and request is a web request
Otherwise returns `None`.
"""
underlying_request = request._request # get the underlying HttpRequest object
if underlying_request.flavour == 'mobile': # check if mobile request
return None # No authentication performed for mobile requests
# For web requests perform DRF's original session authentication
return super(WebSessionAuthentication, self).authenticate(request)
Step-3: Create a custom MobileOAuth2Authentication class
MobileOAuth2Authentication class performs authentication for only mobile requests i.e. having .flavour as mobile. No authentication is performed for web requests.
from oauth2_provider.ext.rest_framework import OAuth2Authentication
class MobileOAuth2Authentication(OAuth2Authentication):
"""
Performs outh2 authentication for mobile requests
"""
def authenticate(self, request):
"""
Returns two-tuple of (user, token) if mobile authentication succeeds,
or None otherwise.
"""
underlying_request = request._request # get the underlying HttpRequest object
if underlying_request.flavour == 'full': # check if web request
return None # No authentication performed for web requests
# For mobile requests perform OAuth2's original authentication
return super(MobileOAuth2Authentication, self).authenticate(request)
Step-4: Define your authentication classes in settings
After creating custom WebSessionAuthentication and MobileOuth2Authentication authentication classes, define these authentication class in your project settings.
'DEFAULT_AUTHENTICATION_CLASSES': (
'my_app.authentication.WebSessionAuthentication', # custom session authentication class for web requests
'my_app.authentication.MobileOAuth2Authentication', # custom oauth2 authentication class for mobile requests
),

Python Social auth authentication via access-token fails

I am currently developing a serverbackand with Django (1.7) that should handle authentication via social Networks by using python-social-auth.
I followed the Tutorial on this site, which describes the process for a simple Webapp.
This worked perfectly for Google and Twitter login.
Since the Server should be just a REST-FULL Backend I decided to get the Access-Token on the client side and send it to the server.
The server than will authenticate with it. This process should be no problem and is even given as an example in the docs of python-social-auth.
However if I do set everything up I will receive an error that says: "Backend not Found 404".
Here a minimal part of the project:
settings.py: (I also included API_KEY and SECRET)
AUTHENTICATION_BACKENDS = (
#'social.backends.facebook.FacebookOAuth2',
'social.backends.google.GoogleOAuth2',
'social.backends.twitter.TwitterOAuth',
'django.contrib.auth.backends.ModelBackend',
)
views.py (for the authentication view)
from django.contrib.auth import login
from social.apps.django_app.utils import psa
#psa('social:complete')
def register_by_access_token(request, backend):
token = request.GET.get('access_token')
user = request.backend.do_auth(request.GET.get('access_token'))
if user:
login(request, user)
return 'OK'
else:
return 'ERROR'
This i copied strait from the docs and only changed backend.do_auth to request.backend.do_auth. This seems to be an error in the docs.
urls.py:
...
url(r'^register-by-token/(?P<backend>[^/]+)/$', 'register_by_access_token')
...
Also as suggested in the docs.
I just tried to get this working just for google-oauth because there is a simple js-lib that gives you the access-token.
This also worked quite nice and I send a request to
GET http://localhost:8000/register-by-token/google-oauth2/<access-token>/
As described above the return was a 404 Backend not found.
I did a little bit of debugging and found out that the error is raised in the login function not the do_auth() function of the backend.
Therefor the actual authentication process works. I also tried using a random generated string as a token and got an according error, that the user cannot be authenticated.
The funny thing is that the user even has a property backend which holds 'social.backends.google.GoogleOAuth2' as it should.
Thank you if you stayed with me for the long post, and I hope someone has an idea what could be wrong :).
Looking forward to your answers.
In you register_by_access_token view, you are getting access_token in GET params
user = request.backend.do_auth(request.GET.get('access_token'))
and url you defiend is:
url(r'^register-by-token/(?P<backend>[^/]+)/$', 'register_by_access_token')
So you need to request something like:
GET http://localhost:8000/register-by-token/google-oauth2/?access_token=<access_token>
whereas, you are doing:
GET http://localhost:8000/register-by-token/google-oauth2/<access-token>/
You are passing access_token in url params, which is wrong.