Skip to main content

Python SDK for LoginLlama suspicious login detection

Project description

LoginLlama Python Client

Official Python SDK for LoginLlama - AI-powered login security and fraud detection.

Features

  • Automatic Context Detection: Auto-detects IP address and User-Agent from Flask, Django, FastAPI, and other frameworks
  • Multi-Source IP Extraction: Supports X-Forwarded-For, CF-Connecting-IP, X-Real-IP, True-Client-IP with private IP filtering
  • Middleware Support: Drop-in middleware for Flask, Django, and FastAPI
  • Type Hints: Fully typed for excellent IDE support
  • Webhook Verification: Built-in HMAC signature verification

Installation

pip install loginllama==2.0.0

Or with uv:

uv pip install loginllama==2.0.0

Requires Python 3.10 or higher.

Quick Start

With Middleware (Recommended)

The simplest way to use LoginLlama is with the middleware pattern, which automatically captures request context:

from loginllama import LoginLlama
from flask import Flask, request, jsonify

app = Flask(__name__)
loginllama = LoginLlama(api_token='your-api-key')

# Add middleware to auto-capture request context
@app.before_request
def setup_loginllama():
    loginllama.middleware()()

@app.route('/login', methods=['POST'])
def login():
    try:
        # IP and User-Agent are automatically detected!
        result = loginllama.check(request.form['email'])

        if result.status == 'error' or result.risk_score > 5:
            print(f"Suspicious login blocked: {result.codes}")
            return jsonify({'error': 'Login blocked'}), 403

        # Continue with login...
        return jsonify({'success': True})
    except Exception as error:
        print(f'LoginLlama error: {error}')
        # Fail open on errors
        return jsonify({'success': True})

Without Middleware

If you prefer not to use middleware, you can pass the request explicitly:

result = loginllama.check(
    request.form['email'],
    request=request
)

Or provide IP and User-Agent manually:

result = loginllama.check(
    'user@example.com',
    ip_address='203.0.113.42',
    user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64)...'
)

Framework Examples

Flask

from flask import Flask, request, jsonify
from loginllama import LoginLlama

app = Flask(__name__)
loginllama = LoginLlama()  # Uses LOGINLLAMA_API_KEY env var

# Use middleware for automatic detection
@app.before_request
def setup_loginllama():
    loginllama.middleware()()

@app.route('/login', methods=['POST'])
def login():
    result = loginllama.check(
        request.form['email'],
        geo_country='US',
        geo_city='San Francisco'
    )

    if result.risk_score > 5:
        return jsonify({'error': 'Suspicious login'}), 403

    return jsonify({'success': True})

Django

from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from loginllama import LoginLlama

loginllama = LoginLlama()

@csrf_exempt
def login_view(request):
    if request.method == 'POST':
        email = request.POST.get('email')

        # Pass request explicitly for auto-detection
        result = loginllama.check(email, request=request)

        if result.risk_score > 5:
            return JsonResponse(
                {'error': 'Suspicious login'},
                status=403
            )

        return JsonResponse({'success': True})

    return JsonResponse({'error': 'Method not allowed'}, status=405)

FastAPI

from fastapi import FastAPI, Request, HTTPException
from loginllama import LoginLlama
from pydantic import BaseModel

app = FastAPI()
loginllama = LoginLlama()

class LoginRequest(BaseModel):
    email: str
    password: str

@app.post('/login')
async def login(login_data: LoginRequest, request: Request):
    # Pass request explicitly for auto-detection
    result = loginllama.check(
        login_data.email,
        request=request
    )

    if result.risk_score > 5:
        raise HTTPException(
            status_code=403,
            detail='Suspicious login detected'
        )

    return {'success': True}

API Reference

LoginLlama(api_token=None, base_url=None)

Create a new LoginLlama client.

Parameters:

  • api_token (optional): Your API key. Defaults to LOGINLLAMA_API_KEY environment variable
  • base_url (optional): Custom API endpoint for testing
from loginllama import LoginLlama

loginllama = LoginLlama(api_token='your-api-key')

loginllama.check(identity_key, **options)

Check a login attempt for suspicious activity.

Parameters:

  • identity_key (required): User identifier (email, username, user ID, etc.)
  • ip_address (optional): Override auto-detected IP address
  • user_agent (optional): Override auto-detected User-Agent
  • request (optional): Explicit request object (Flask, Django, FastAPI)
  • email_address (optional): User's email address for additional verification
  • geo_country (optional): ISO country code (e.g., 'US', 'GB')
  • geo_city (optional): City name for additional context
  • user_time_of_day (optional): Time of login attempt

Returns: LoginCheck object

class LoginCheck:
    status: str  # 'success' or 'error'
    message: str
    codes: List[LoginCheckStatus]
    risk_score: int  # 0-10 scale
    environment: str
    meta: Optional[dict]

Detection Priority:

  1. Explicit ip_address and user_agent keyword arguments
  2. Extract from request object if provided
  3. Use context from middleware (if used)

Examples:

# Auto-detect from middleware context
result = loginllama.check('user@example.com')

# Pass request explicitly
result = loginllama.check('user@example.com', request=request)

# Manual override
result = loginllama.check(
    'user@example.com',
    ip_address='203.0.113.42',
    user_agent='Mozilla/5.0...'
)

# With additional context
result = loginllama.check(
    'user@example.com',
    email_address='user@example.com',
    geo_country='US',
    geo_city='San Francisco'
)

loginllama.middleware()

Returns middleware function that automatically captures request context using contextvars.

Flask:

@app.before_request
def setup_loginllama():
    loginllama.middleware()()

Django (middleware class):

class LoginLlamaMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
        self.loginllama = LoginLlama()

    def __call__(self, request):
        self.loginllama.middleware()(request)
        return self.get_response(request)

FastAPI:

@app.middleware("http")
async def loginllama_middleware(request: Request, call_next):
    loginllama.middleware()(request)
    response = await call_next(request)
    return response

verify_webhook_signature(payload, signature, secret)

Verify webhook signature using constant-time HMAC comparison.

Parameters:

  • payload: Raw webhook body (bytes or str)
  • signature: Value from X-LoginLlama-Signature header
  • secret: Webhook secret from LoginLlama dashboard

Returns: bool

from loginllama import verify_webhook_signature
from flask import Flask, request

app = Flask(__name__)

@app.route('/webhook', methods=['POST'])
def webhook():
    payload = request.get_data()
    signature = request.headers.get('X-LoginLlama-Signature')

    if not verify_webhook_signature(payload, signature, os.environ['WEBHOOK_SECRET']):
        return 'Invalid signature', 401

    event = request.get_json()
    # Handle event...
    return 'ok', 200

Login Status Codes

The SDK exports a LoginCheckStatus enum with all possible status codes:

from loginllama import LoginCheckStatus

# Example status codes:
LoginCheckStatus.VALID
LoginCheckStatus.IP_ADDRESS_SUSPICIOUS
LoginCheckStatus.KNOWN_BOT
LoginCheckStatus.IMPOSSIBLE_TRAVEL_DETECTED
LoginCheckStatus.NEW_LOGIN_LOCATION
# ... and more

Error Handling

The SDK will raise exceptions if required parameters are missing:

try:
    result = loginllama.check('user@example.com')
except ValueError as error:
    if 'IP address could not be detected' in str(error):
        # No IP available - pass ip_address or request explicitly
        # or use middleware()
        pass
except Exception as error:
    # Consider failing open on errors to avoid blocking legitimate users
    print(f'LoginLlama error: {error}')

Best Practice: Fail open on errors to avoid blocking legitimate users during API outages:

try:
    result = loginllama.check(email)
    if result.risk_score > 5:
        # Block suspicious login
        return jsonify({'error': 'Login blocked'}), 403
except Exception as error:
    print(f'LoginLlama error: {error}')
    # Fail open - allow login to proceed
    return jsonify({'success': True})

IP Detection

The SDK automatically detects IP addresses from multiple sources with priority fallback:

  1. X-Forwarded-For - Parses chain, takes first public IP (filters private IPs)
  2. CF-Connecting-IP - Cloudflare real client IP
  3. X-Real-IP - nginx proxy header
  4. True-Client-IP - Akamai/Cloudflare header
  5. Direct connection - REMOTE_ADDR, framework-specific attributes

Private IP Filtering: Automatically filters 10.x.x.x, 172.16-31.x.x, 192.168.x.x, 127.x.x.x, ::1, fc00::/7, fe80::/10

Type Hints

The SDK is fully typed with type hints:

from loginllama import (
    LoginLlama,
    LoginCheck,
    LoginCheckStatus,
    verify_webhook_signature
)

Contributing

Contributions are welcome! Please open an issue or submit a pull request on GitHub.

License

MIT License

Support

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

loginllama-2.0.0.tar.gz (24.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

loginllama-2.0.0-py3-none-any.whl (23.2 kB view details)

Uploaded Python 3

File details

Details for the file loginllama-2.0.0.tar.gz.

File metadata

  • Download URL: loginllama-2.0.0.tar.gz
  • Upload date:
  • Size: 24.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for loginllama-2.0.0.tar.gz
Algorithm Hash digest
SHA256 34826fb7d36c1644cc1c0ec7390dbd2780a79c598119b5662b20743e6dcfa205
MD5 7671e5a5a4b036ed9734ae592ec7d522
BLAKE2b-256 49af6504f87e888349aff10faafa2cf5ee9e551cc951612bea7f04cdb6d4e524

See more details on using hashes here.

Provenance

The following attestation bundles were made for loginllama-2.0.0.tar.gz:

Publisher: cicd.yml on joshghent/loginllama.py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file loginllama-2.0.0-py3-none-any.whl.

File metadata

  • Download URL: loginllama-2.0.0-py3-none-any.whl
  • Upload date:
  • Size: 23.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for loginllama-2.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d645733c0a8165ad266c6a651584b5715111d097b94dd206f2cc55c311454f99
MD5 d7e1516cbfcb968ae9640bcb3b722c32
BLAKE2b-256 3181402ba76d01cdd867e9afe1fafd1a9d001353a33927311f6fe7a0a3fa66e9

See more details on using hashes here.

Provenance

The following attestation bundles were made for loginllama-2.0.0-py3-none-any.whl:

Publisher: cicd.yml on joshghent/loginllama.py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page