Django Microsoft SSO Integration State Mismatch (Django + Azure App Service)

I’m integrating Microsoft SSO into my Django app, and I’m encountering a “State Mismatch” error during the login process. This error occurs when the state parameter, which is used to prevent cross-site request forgery (CSRF) attacks, doesn’t match the expected value.

What’s Happening:
During the login process, my Django app generates a state parameter and stores it in the user’s session. When the user is redirected back to my app from Microsoft after authentication, the state parameter returned by Microsoft should match the one stored in the session. However, in my case, the two values don’t match, resulting in a State Mismatch Error.

ERROR:django.security.SSOLogin: State mismatch during Microsoft SSO login.

State received from the callback URL:

state = request.GET.get(‘state’)

State stored in the session during SSO initiation:

session_state = request.session.get(‘oauth2_state’)

In the logs, I can see that the session state is either missing or mismatched. Here’s the log snippet:

Received state in callback: b8bfae27-xxxx-xxxx-xxxxxxxxx
Session state before validation: None

The callback state is b8bfae27-xxx-xxxx-xxxx-xxxxxxxxxxx, but the session state is None. This leads to a state mismatch and ultimately the login failure.

Expected state: The state value generated by my app and stored in the session.

Returned state: The state value returned by Microsoft after the user completes authentication.

Session ID: The session key for the user during the login attempt.

Environment Details:

Django & Azure Configuration:
Django version: 5.0.6

Python version: 3.12

SSO Integration Package: django-microsoft-sso
Cache backend: LocMemCache (planning to switch to Redis)
Azure App Service: Hosted with App Service Plan for deployment.

Time Zone: Central Time (US & Canada) on local development and UTC on the Azure server.

Session Engine: Using default Django session engine with database-backed sessions (django.contrib.sessions.backends.db).

```text

What I’ve Tried:
Session Persistence: I verified that session data is being correctly stored and retrieved during the login process.

Time Synchronization: I checked that the time on my server and Microsoft’s authentication server is synchronized to avoid potential timing issues.

Cache Settings: I’m currently using LocMemCache for caching and session management in local development, but I suspect this may be causing issues in Azure due to its lack of persistence across multiple instances.

SSO Settings: I reviewed the Microsoft SSO settings and ensured the correct URLs and callback mechanisms are being used.

Code:


from django.shortcuts import render, redirect

from django.urls import reverse

from django.contrib.auth import login

from django.utils.timezone import now

from django.http import JsonResponse

from django.views.decorators.csrf import csrf_exempt

import binascii

import os

import logging

logger = logging.getLogger(__name__)

View to handle login failure
def login_failed(request):

Log basic failure info
logger.debug(f"Login failed at {now()}.")

Render failure message
context = {'message': 'We were unable to log you in using Microsoft SSO.'}

return render(request, 'claims/login_failed.html', context)

View to handle Microsoft SSO callback
u/csrf_exempt

def microsoft_sso_callback(request):

Log basic info for debugging
logger.debug(f"SSO callback triggered at {now()}")

Retrieve state from the callback and session
state = request.GET.get('state')

session_state = request.session.get('oauth2_state')

Check for state mismatch or missing state
if not state or state != session_state:

logger.error(f"State mismatch or state missing. Received: {state}, Expected: {session_state}")

request.session.flush() # Clear session to test if a fresh session resolves it

return redirect(reverse('login_failed'))

Process the Microsoft user data
microsoft_user = getattr(request, 'microsoft_user', None)

if microsoft_user:

email = microsoft_user.get('email')

if email:

try:

user = User.objects.get(email=email)

Log the user in using the correct backend
login(request, user, backend='django.contrib.auth.backends.ModelBackend')

return redirect('admin:index')

except User.DoesNotExist:

return redirect(reverse('login_failed'))

else:

return redirect(reverse('login_failed'))

else:

return redirect(reverse('login_failed'))

View to initiate the Microsoft SSO login process
def sso_login(request):

Generate a secure random state
state = binascii.hexlify(os.urandom(16)).decode()

Store state in session and save
request.session['oauth2_state'] = state

request.session.save()

Build the Microsoft login URL
login_url = 'https://login.microsoftonline.com/{}/oauth2/v2.0/authorize'.format(settings.MICROSOFT_SSO_TENANT_ID)

params = {

'client_id': settings.MICROSOFT_SSO_APPLICATION_ID,

'response_type': 'code',

'redirect_uri': settings.MICROSOFT_SSO_REDIRECT_URI,

'response_mode': 'query',

'scope': ' '.join(settings.MICROSOFT_SSO_SCOPES),

'state': state,

}

login_url_with_params = f"{login_url}?{'&'.join(f'{key}={value}' for key, value in params.items())}"

return redirect(login_url_with_params)

Django Settings:


USE_TZ = True
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
CSRF_COOKIE_SECURE = True
SESSION_SAVE_EVERY_REQUEST = True
MICROSOFT_SSO_ENABLED = True
MICROSOFT_SSO_APPLICATION_ID = 'My-App-ID'
MICROSOFT_SSO_CLIENT_SECRET = 'My-Client-Secret'
MICROSOFT_SSO_TENANT_ID = 'My-Tenant-ID'
MICROSOFT_SSO_REDIRECT_URI = 'http://localhost:8000/xxx/xxxx/'
MICROSOFT_SSO_SCOPES = ['openid', 'profile', 'email']

6

I connected Redis to the Django project in the settings.py file using port 6379 and was able to log in successfully.

django_microsoft_sso/sso/views.py :

from django.shortcuts import render, redirect
from django.urls import reverse
from django.contrib.auth import login as django_login1, logout as django_logout1
from django.utils.timezone import now
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.conf import settings
from django.contrib.auth.models import User  # Add this import
import binascii
import os
import logging

logger = logging.getLogger(__name__)
def home(request):
    return render(request, 'sso/home.html')
def login_failed(request):
    logger.debug(f"Login failed at {now()}.")
    context = {'message': 'We were unable to log you in using Microsoft SSO.'}
    return render(request, 'sso/login_failed.html', context)

@csrf_exempt
def microsoft_sso_callback(request):
    logger.debug(f"SSO callback triggered at {now()}")
    state = request.GET.get('state')
    session_state = request.session.get('oauth2_state')

    if not state or state != session_state:
        logger.error(f"State mismatch or state missing. Received: {state}, Expected: {session_state}")
        request.session.flush() 
        return redirect(reverse('login_failed'))
    microsoft_user = getattr(request, 'microsoft_user', None)
    if microsoft_user:
        email = microsoft_user.get('email')
        if email:
            try:
                user = User.objects.get(email=email)
                django_login1(request, user, backend='django.contrib.auth.backends.ModelBackend')
                return redirect('admin:index')
            except User.DoesNotExist:
                return redirect(reverse('login_failed'))
        else:
            return redirect(reverse('login_failed'))
    else:
        return redirect(reverse('login_failed'))
def sso_login(request):
    state = binascii.hexlify(os.urandom(16)).decode()
    request.session['oauth2_state'] = state
    request.session.save()
    login_url = f'https://login.microsoftonline.com/{settings.MICROSOFT_SSO_TENANT_ID}/oauth2/v2.0/authorize' 
    params = {
        'client_id': settings.MICROSOFT_SSO_APPLICATION_ID,
        'response_type': 'code',
        'redirect_uri': settings.MICROSOFT_SSO_REDIRECT_URI,
        'response_mode': 'query',
        'scope': ' '.join(settings.MICROSOFT_SSO_SCOPES),
        'state': state,
    }
    login_url_with_params = f"{login_url}?{'&'.join(f'{key}={value}' for key, value in params.items())}"
    return redirect(login_url_with_params)
def logout(request):
    django_logout1(request)
    return redirect('home')

django_microsoft_sso/sso/urls.py :

from django.urls import path
from . import views
urlpatterns = [
    path('', views.home, name='home'),
    path('login/', views.sso_login, name='sso_login'),
    path('callback/', views.microsoft_sso_callback, name='microsoft_sso_callback'),
    path('login-failed/', views.login_failed, name='login_failed'),
    path('logout/', views.logout, name='logout'), 
]

django_microsoft_sso/django_microsoft_sso/settings.py :

import os
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SECRET_KEY = '<secretkey>)'
DEBUG = True
ALLOWED_HOSTS = []
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'sso',
]
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'django_microsoft_sso.urls'
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]
WSGI_APPLICATION = 'django_microsoft_sso.wsgi.application'
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}
AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
STATIC_URL = 'static/'
SESSION_COOKIE_SECURE = False
CSRF_COOKIE_SECURE = False
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
SESSION_SAVE_EVERY_REQUEST = True
CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': 'redis://127.0.0.1:6379/1',
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',
        }
    }
}
MICROSOFT_SSO_ENABLED = True
MICROSOFT_SSO_APPLICATION_ID = 'ClientID'
MICROSOFT_SSO_CLIENT_SECRET = 'ClientSecret'
MICROSOFT_SSO_TENANT_ID = 'TenantID'
MICROSOFT_SSO_REDIRECT_URI = 'http://localhost:8000/sso/callback/'
MICROSOFT_SSO_SCOPES = ['openid', 'profile', 'email', 'offline_access']

django_microsoft_sso/django_microsoft_sso/urls.py :

from django.contrib import admin
from django.urls import path, include
from sso import views
urlpatterns = [
    path('admin/', admin.site.urls),
    path('sso/', include('sso.urls')),  
    path('', views.home, name='home'),  
]

login_failed.html :

<!DOCTYPE html>
<html>
<head>
    <title>Login successful</title>
</head>
<body>
    <form action="{% url 'logout' %}" method="post">
        {% csrf_token %}
        <button type="submit">Logout</button>
    </form>
</body>
</html>

home.html:

<!DOCTYPE html>
<html>
<head>
    <title>Home</title>
</head>
<body>
    <h1>Welcome to the Home Page</h1>
    <a href="{% url 'sso_login' %}">Login with Microsoft</a>
</body>
</html>

I added the below redirect URI in the Azure app registration as shown below.

http://localhost:8000/sso/callback/

Before going to run the above Django project, you need to install Redis.

Output :

I ran Redis using the below command,

redis-server.exe

The Django project ran successfully, as shown below.

python manage.py runserver localhost:8000

Browser Output :

I successfully logged in and logged out by clicking the Login with Microsoft and Logout buttons, as shown below.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật