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 toLOGINLLAMA_API_KEYenvironment variablebase_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 addressuser_agent(optional): Override auto-detected User-Agentrequest(optional): Explicit request object (Flask, Django, FastAPI)email_address(optional): User's email address for additional verificationgeo_country(optional): ISO country code (e.g., 'US', 'GB')geo_city(optional): City name for additional contextuser_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:
- Explicit
ip_addressanduser_agentkeyword arguments - Extract from
requestobject if provided - 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 fromX-LoginLlama-Signatureheadersecret: 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:
- X-Forwarded-For - Parses chain, takes first public IP (filters private IPs)
- CF-Connecting-IP - Cloudflare real client IP
- X-Real-IP - nginx proxy header
- True-Client-IP - Akamai/Cloudflare header
- 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
- Documentation: loginllama.app/docs
- Dashboard: loginllama.app/dashboard
- Issues: GitHub Issues
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
34826fb7d36c1644cc1c0ec7390dbd2780a79c598119b5662b20743e6dcfa205
|
|
| MD5 |
7671e5a5a4b036ed9734ae592ec7d522
|
|
| BLAKE2b-256 |
49af6504f87e888349aff10faafa2cf5ee9e551cc951612bea7f04cdb6d4e524
|
Provenance
The following attestation bundles were made for loginllama-2.0.0.tar.gz:
Publisher:
cicd.yml on joshghent/loginllama.py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
loginllama-2.0.0.tar.gz -
Subject digest:
34826fb7d36c1644cc1c0ec7390dbd2780a79c598119b5662b20743e6dcfa205 - Sigstore transparency entry: 787498673
- Sigstore integration time:
-
Permalink:
joshghent/loginllama.py@4a90e4b19c0a61604c30cc6f0caeee8943053aab -
Branch / Tag:
refs/heads/main - Owner: https://github.com/joshghent
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
cicd.yml@4a90e4b19c0a61604c30cc6f0caeee8943053aab -
Trigger Event:
push
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d645733c0a8165ad266c6a651584b5715111d097b94dd206f2cc55c311454f99
|
|
| MD5 |
d7e1516cbfcb968ae9640bcb3b722c32
|
|
| BLAKE2b-256 |
3181402ba76d01cdd867e9afe1fafd1a9d001353a33927311f6fe7a0a3fa66e9
|
Provenance
The following attestation bundles were made for loginllama-2.0.0-py3-none-any.whl:
Publisher:
cicd.yml on joshghent/loginllama.py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
loginllama-2.0.0-py3-none-any.whl -
Subject digest:
d645733c0a8165ad266c6a651584b5715111d097b94dd206f2cc55c311454f99 - Sigstore transparency entry: 787498675
- Sigstore integration time:
-
Permalink:
joshghent/loginllama.py@4a90e4b19c0a61604c30cc6f0caeee8943053aab -
Branch / Tag:
refs/heads/main - Owner: https://github.com/joshghent
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
cicd.yml@4a90e4b19c0a61604c30cc6f0caeee8943053aab -
Trigger Event:
push
-
Statement type: