Skip to content

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:

  1. The current token is extracted from the authorization header
  2. The token is added to the blacklist with an expiry time matching the token's expiry
  3. Any subsequent requests using that token are rejected

All Devices Logout

When a user logs out from all devices:

  1. The user's token version is incremented in the database
  2. All tokens issued before the version increment are considered invalid
  3. 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 device
  • POST /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

  1. Always Logout: Always provide a logout option in your client application and encourage users to log out when they're done.
  2. Clear Client-Side Storage: When logging out, clear any client-side storage of tokens or sensitive data.
  3. Use Secure Cookies: If storing tokens in cookies, use secure and HTTP-only cookies.
  4. Set Appropriate Token Expiry: Set token expiry times that balance security and user experience.
  5. Implement Refresh Token Rotation: Consider implementing refresh token rotation for enhanced security.
  6. Monitor Logout Events: Monitor and log logout events for security auditing.
  7. Provide Logout Confirmation: Inform users when they have successfully logged out.