Advanced Logout
Advanced logout provides enhanced security features for managing user sessions and tokens in the UBU Finance backend.
Overview
The advanced logout functionality includes token blacklisting and token versioning to ensure that users can securely terminate their sessions. This prevents unauthorized access using previously issued tokens.
Features
Token Blacklisting
When a user logs out, their current token is added to a blacklist. Any subsequent requests using that token will be rejected, even if the token has not yet expired.
Token Versioning
Each user has a token version number that is incremented when they log out from all devices. All tokens issued before the version increment are considered invalid. This allows users to invalidate all existing sessions across all devices with a single action.
How It Works
Single Device Logout
When a user logs out from a single device:
- The current token is extracted from the authorization header
- The token is added to the blacklist with an expiry time matching the token's expiry
- Any subsequent requests using that token are rejected
All Devices Logout
When a user logs out from all devices:
- The user's token version is incremented in the database
- All tokens issued before the version increment are considered invalid
- The user must log in again on all devices to obtain new tokens with the updated version
API Endpoints
The following endpoints are available for logout functionality:
POST /authentication/logout: Logout from the current devicePOST /authentication/logout-all-devices: Logout from all devices
Client Implementation Examples
Logging Out from Current Device
import requests
def logout(base_url, token):
url = f"{base_url}/authentication/logout"
headers = {
"Authorization": f"Bearer {token}"
}
response = requests.post(url, headers=headers)
if response.status_code == 200:
print("Successfully logged out")
return True
else:
print(f"Logout failed: {response.json()}")
return False
async function logout(baseUrl, token) {
const url = `${baseUrl}/authentication/logout`;
const headers = {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
};
try {
const response = await fetch(url, {
method: 'POST',
headers: headers
});
const data = await response.json();
if (response.ok) {
console.log("Successfully logged out");
return true;
} else {
console.error(`Logout failed: ${data.detail}`);
return false;
}
} catch (error) {
console.error(`Error during logout: ${error}`);
return false;
}
}
#!/bin/bash
logout() {
local base_url=$1
local token=$2
response=$(curl -s -w "%{http_code}" \
-X POST \
-H "Authorization: Bearer $token" \
"$base_url/authentication/logout")
http_code=${response: -3}
content=${response:0:${#response}-3}
if [ "$http_code" == "200" ]; then
echo "Successfully logged out"
return 0
else
echo "Logout failed: $content"
return 1
fi
}
using System;
using System.Net.Http;
using System.Threading.Tasks;
public class AuthClient
{
private readonly HttpClient _client;
private readonly string _baseUrl;
public AuthClient(string baseUrl)
{
_client = new HttpClient();
_baseUrl = baseUrl;
}
public async Task<bool> LogoutAsync(string token)
{
string url = $"{_baseUrl}/authentication/logout";
_client.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
try
{
HttpResponseMessage response = await _client.PostAsync(url, null);
if (response.IsSuccessStatusCode)
{
Console.WriteLine("Successfully logged out");
return true;
}
else
{
string content = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Logout failed: {content}");
return false;
}
}
catch (Exception ex)
{
Console.WriteLine($"Error during logout: {ex.Message}");
return false;
}
}
}
Logging Out from All Devices
import requests
def logout_all_devices(base_url, token):
url = f"{base_url}/authentication/logout-all-devices"
headers = {
"Authorization": f"Bearer {token}"
}
response = requests.post(url, headers=headers)
if response.status_code == 200:
print("Successfully logged out from all devices")
return True
else:
print(f"Logout from all devices failed: {response.json()}")
return False
async function logoutAllDevices(baseUrl, token) {
const url = `${baseUrl}/authentication/logout-all-devices`;
const headers = {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
};
try {
const response = await fetch(url, {
method: 'POST',
headers: headers
});
const data = await response.json();
if (response.ok) {
console.log("Successfully logged out from all devices");
return true;
} else {
console.error(`Logout from all devices failed: ${data.detail}`);
return false;
}
} catch (error) {
console.error(`Error during logout from all devices: ${error}`);
return false;
}
}
#!/bin/bash
logout_all_devices() {
local base_url=$1
local token=$2
response=$(curl -s -w "%{http_code}" \
-X POST \
-H "Authorization: Bearer $token" \
"$base_url/authentication/logout-all-devices")
http_code=${response: -3}
content=${response:0:${#response}-3}
if [ "$http_code" == "200" ]; then
echo "Successfully logged out from all devices"
return 0
else
echo "Logout from all devices failed: $content"
return 1
fi
}
using System;
using System.Net.Http;
using System.Threading.Tasks;
public class AuthClient
{
private readonly HttpClient _client;
private readonly string _baseUrl;
public AuthClient(string baseUrl)
{
_client = new HttpClient();
_baseUrl = baseUrl;
}
public async Task<bool> LogoutAllDevicesAsync(string token)
{
string url = $"{_baseUrl}/authentication/logout-all-devices";
_client.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
try
{
HttpResponseMessage response = await _client.PostAsync(url, null);
if (response.IsSuccessStatusCode)
{
Console.WriteLine("Successfully logged out from all devices");
return true;
}
else
{
string content = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Logout from all devices failed: {content}");
return false;
}
}
catch (Exception ex)
{
Console.WriteLine($"Error during logout from all devices: {ex.Message}");
return false;
}
}
}
Implementation Details
Token Blacklist Storage
The token blacklist is stored in Redis for high-performance lookups, with an in-memory fallback if Redis is not available. Blacklisted tokens are automatically removed from the blacklist when they expire to prevent the blacklist from growing indefinitely.
Token Version Storage
Token versions are stored in the user database record. When a token is validated, its version is compared with the user's current token version. If the token's version is lower than the user's current version, the token is considered invalid.
Configuration
Advanced logout features can be configured via environment variables or by modifying the app/config/security_config.py file.
Environment Variables
# Redis Configuration (for token blacklist)
REDIS_ENABLED=true
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_DB=0
Best Practices
- Always Logout: Always provide a logout option in your client application and encourage users to log out when they're done.
- Clear Client-Side Storage: When logging out, clear any client-side storage of tokens or sensitive data.
- Use Secure Cookies: If storing tokens in cookies, use secure and HTTP-only cookies.
- Set Appropriate Token Expiry: Set token expiry times that balance security and user experience.
- Implement Refresh Token Rotation: Consider implementing refresh token rotation for enhanced security.
- Monitor Logout Events: Monitor and log logout events for security auditing.
- Provide Logout Confirmation: Inform users when they have successfully logged out.