django-allauth
django-allauth copied to clipboard
Backchannel Logout
Are there any plans to add backchannel logouts to this library?
I have a collection of applications that use one oidc provider. When a user logs out of one app I'd like for them to be logged out of the others.
https://openid.net/specs/openid-connect-backchannel-1_0.html#BCRequest
For reference, this is what I've done in the time being to get this to work with Keycloak:
## Keycloak Backchannel Logout
from allauth.socialaccount.models import SocialAccount
from django.http import HttpResponse, HttpRequest
from django.views.decorators.csrf import csrf_exempt
from django.contrib.sessions.models import Session
from importlib import import_module
import jwt
def init_session(session_key):
"""
Initialize same session as done for ``SessionMiddleware``.
"""
engine = import_module(settings.SESSION_ENGINE)
return engine.SessionStore(session_key)
@csrf_exempt
def backchannel_logout(request):
''' Keycloak is set to send a POST request to this route if a user
logs out via another application, such as Superset.
This function will ensure the user is logged out of this app
as well.
'''
# First run a bunch of checks to make sure that the request is valid
if request.method != 'POST': return HttpResponse(status=405)
if settings.KEYCLOAK_PUBLIC_KEY == None: return HttpResponse(status=501)
t = request.POST['logout_token']
try:
decoded = jwt.decode(t, key=settings.KEYCLOAK_PUBLIC_KEY, algorithms=['RS256', ], audience=settings.KEYCLOAK_APPS)
uid = decoded['sub']
aud = decoded['aud']
except Exception as e:
logger.error(f"Backchannel logout failed: {e}")
return HttpResponse(406)
if not SocialAccount.objects.filter(uid=uid).exists():
logger.warning(f"User {uid} does not exist in this application.")
return HttpResponse(406)
# If we made it this far, then the request checks out. Continue to log user out.
from django.contrib.auth import logout
user = SocialAccount.objects.get(uid=uid).user
sessions = [s for s in Session.objects.all() if s.get_decoded().get('_auth_user_id') == str(user.id)]
request = HttpRequest()
for session in sessions:
request.session = init_session(session.session_key)
logout(request)
if len(sessions) != 0: logger.debug("{user.username} has been logged out by {aud}")
return HttpResponse(status=200)
@jharmon96 ,
Side question, do you have any guide / article / blog about best practice of using django / allauth with keycloak?
I just created a keycloak vanilla client for django app, without client scopes / groups. Quite sure I should, but 0 clue 😁
SOCIALACCOUNT_PROVIDERS = {
"openid_connect": {
"APPS": [
{
"provider_id": "keycloak",
"name": "Keycloak",
"client_id": "<insert-id>",
"secret": "<insert-secret>",
"settings": {
"server_url": "http://keycloak:8080/realms/master/.well-known/openid-configuration",
},
}
]
}
}
@pennersr , I managed to get it working, allauth is flawless here 😁
What I'm unsure of is the keycloak side
There is a concept of client scopes and groups that you can attach additional attributes. It's complex and depends on user experience rather than documentation. Was hoping to hear how others django users are doing these.
Just created a post on Django forum too:
https://forum.djangoproject.com/t/django-keycloak-best-practice/23924