Signals
RobustAuth fires Django signals at key authentication events. Connect to them in your own apps to add custom logic — alerting, rate limiting, analytics, notifications, and more — without touching RobustAuth’s internals.
Importing signals
from robustauth import signals
from django.dispatch import receiver
user_logged_in
Fired after a new session is created (successful login).
kwargs: user, session
@receiver(signals.user_logged_in)
def on_login(sender, user, session, **kwargs):
send_new_login_notification(
user=user,
ip=session.ip_address,
device=session.device_name,
os=session.os_family,
)
user_logged_out
Fired when any session is revoked — normal logout, forced logout, or password change revocation.
kwargs: user, session
@receiver(signals.user_logged_out)
def on_logout(sender, user, session, **kwargs):
analytics.track(user.id, "session_ended", {
"session_id": str(session.id),
"duration": (timezone.now() - session.created_at).seconds,
})
token_reuse_detected
Fired when a refresh token that has already been used is presented again. This is a strong signal of token theft. When this fires, RobustAuth has already revoked the entire token family and all sessions for the user.
kwargs: user, session, ip_address
@receiver(signals.token_reuse_detected)
def on_token_reuse(sender, user, session, ip_address, **kwargs):
SecurityAlert.objects.create(
user=user,
kind="token_reuse",
ip_address=ip_address,
)
send_security_email(user, "Someone may have stolen your session token.")
session_limit_reached
Fired when a user hits MAX_SESSIONS and REVOKE_OLDEST_ON_LIMIT = False. The new login has been blocked at this point. Use this signal to return a custom error or notify the user.
kwargs: user, active_sessions
@receiver(signals.session_limit_reached)
def on_limit_reached(sender, user, active_sessions, **kwargs):
# active_sessions is a list of Session objects
send_push_notification(
user,
f"Login blocked — you already have {len(active_sessions)} active sessions. "
"Please log out from another device."
)
brute_force_threshold_hit
Fired when the number of failed login attempts for a given username + IP combination exceeds FAILED_LOGIN_THRESHOLD within FAILED_LOGIN_WINDOW seconds.
kwargs: username, ip_address, failure_count
@receiver(signals.brute_force_threshold_hit)
def on_brute_force(sender, username, ip_address, failure_count, **kwargs):
BlockedIP.objects.get_or_create(ip=ip_address)
notify_security_team(
f"{failure_count} failed logins for '{username}' from {ip_address}"
)
password_changed
Fired after SessionManager.on_password_change() completes. Other sessions have already been revoked at this point. Use this to notify the user.
kwargs: user, current_session, new_pair
@receiver(signals.password_changed)
def on_password_changed(sender, user, current_session, new_pair, **kwargs):
send_email(user, "Your password was changed.")
# new_pair is an AuthTokenPair if REFRESH_TOKEN_ON_PASSWORD_CHANGE=True, else None
password_reset
Fired after SessionManager.on_password_reset() completes. All sessions have already been revoked at this point.
kwargs: user, new_pair
@receiver(signals.password_reset)
def on_password_reset(sender, user, new_pair, **kwargs):
send_email(user, "Your password was reset.")
# new_pair is an AuthTokenPair if REFRESH_TOKEN_ON_PASSWORD_RESET=True, else None
Summary
Signal |
When it fires |
Key kwargs |
|---|---|---|
|
Successful login |
|
|
Any session revocation |
|
|
Reused refresh token detected |
|
|
Max sessions hit (no auto-revoke) |
|
|
Too many failed logins |
|
|
Password change completed |
|
|
Password reset completed |
|
Registering signal handlers
The recommended place for your signal handlers is in a signals.py file inside your app, connected in AppConfig.ready():
# yourapp/apps.py
from django.apps import AppConfig
class YourAppConfig(AppConfig):
name = "yourapp"
def ready(self):
import yourapp.signal_handlers # noqa: F401