Flask extension for Universal Verification Broker (UVB)
Project description
uvb-flask
Flask extension for Universal Verification Broker (UVB) authentication.
Installation
pip install uvb-flask
Quick Start
from flask import Flask, jsonify
from uvb_flask import UVBFlask, get_session, require_factors, uvb_required
app = Flask(__name__)
# Initialize UVB extension
uvb = UVBFlask(
app,
tenant_id='my-tenant',
uvb_url='http://localhost:8080',
exclude_paths=['/login', '/health', '/public']
)
# Access authenticated user
@app.route('/profile')
def profile():
session = get_session()
return jsonify({
'user_id': session.user_id,
'factors': session.factors_verified
})
# Require authentication with decorator
@app.route('/protected')
@uvb_required
def protected():
user_id = g.uvb_session.user_id
return jsonify({'message': f'Hello {user_id}'})
# Require specific MFA factors
@app.route('/admin/delete', methods=['POST'])
@require_factors('totp', 'webauthn')
def admin_delete():
return jsonify({'message': 'Admin action completed'})
if __name__ == '__main__':
app.run(port=3000)
API Reference
UVBFlask Extension
Direct initialization:
from flask import Flask
from uvb_flask import UVBFlask
app = Flask(__name__)
uvb = UVBFlask(
app,
tenant_id='my-tenant',
uvb_url='http://localhost:8080', # Optional, defaults to http://localhost:8080
api_key='optional-api-key', # Optional
cookie_name='uvb_session', # Optional, defaults to uvb_session
exclude_paths=['/login', '/health'] # Optional, defaults to []
)
Factory pattern with init_app:
from flask import Flask
from uvb_flask import UVBFlask
uvb = UVBFlask()
def create_app():
app = Flask(__name__)
uvb.init_app(
app,
tenant_id='my-tenant',
uvb_url='http://localhost:8080'
)
return app
Configuration from app.config:
app = Flask(__name__)
app.config['UVB_TENANT_ID'] = 'my-tenant'
app.config['UVB_URL'] = 'http://localhost:8080'
app.config['UVB_API_KEY'] = 'optional-api-key'
app.config['UVB_COOKIE_NAME'] = 'uvb_session'
app.config['UVB_EXCLUDE_PATHS'] = ['/login', '/health']
uvb = UVBFlask(app)
Session Access:
After successful authentication, g.uvb_session contains a UVBSession object:
from flask import g
@app.route('/user')
def user():
session = g.uvb_session
return {
'user_id': session.user_id,
'tenant_id': session.tenant_id,
'session_id': session.session_id,
'factors_verified': session.factors_verified,
'expires_at': session.expires_at.isoformat()
}
Decorators
@uvb_required
Require UVB authentication for a route.
from uvb_flask import uvb_required
@app.route('/profile')
@uvb_required
def profile():
user_id = g.uvb_session.user_id
return {'user_id': user_id}
@require_factors(*factors)
Require specific MFA factors for a route.
from uvb_flask import require_factors
@app.route('/sensitive', methods=['POST'])
@require_factors('totp', 'webauthn')
def sensitive():
return {'message': 'Sensitive operation completed'}
Helper Functions
get_session()
Get the UVB session from Flask's g object.
from uvb_flask import get_session
@app.route('/info')
def info():
session = get_session()
if session:
return {'user_id': session.user_id}
return {'error': 'Not authenticated'}, 401
Examples
Protecting Specific Routes
from flask import Flask
from uvb_flask import UVBFlask, uvb_required
app = Flask(__name__)
uvb = UVBFlask(app, tenant_id='my-tenant')
# Public routes (no authentication required)
@app.route('/health')
def health():
return {'status': 'ok'}
# Protected routes (authentication required)
@app.route('/api/data')
@uvb_required
def data():
return {'data': 'protected'}
Using Blueprints
from flask import Blueprint, g
from uvb_flask import uvb_required, require_factors
# API Blueprint
api = Blueprint('api', __name__, url_prefix='/api')
@api.route('/profile')
@uvb_required
def profile():
return {'user_id': g.uvb_session.user_id}
# Admin Blueprint with strict MFA
admin = Blueprint('admin', __name__, url_prefix='/admin')
@admin.before_request
@require_factors('totp', 'webauthn')
def require_admin_auth():
pass
@admin.route('/users')
def admin_users():
return {'users': [...]}
# Register blueprints
app.register_blueprint(api)
app.register_blueprint(admin)
Conditional MFA Requirements
from flask import request
from uvb_flask import get_session
@app.route('/transfer', methods=['POST'])
def transfer():
session = get_session()
if not session:
return {'error': 'Not authenticated'}, 401
amount = request.json.get('amount', 0)
# Require additional auth for large transfers
if amount > 10000:
if not all(f in session.factors_verified for f in ['totp', 'webauthn']):
return {
'error': 'Additional authentication required',
'required': ['totp', 'webauthn'],
'verified': session.factors_verified
}, 403
return {'message': 'Transfer initiated'}
Custom Error Handling
@app.errorhandler(401)
def unauthorized(error):
return {
'error': 'Unauthorized',
'message': 'Please authenticate to access this resource'
}, 401
@app.errorhandler(403)
def forbidden(error):
return {
'error': 'Forbidden',
'message': 'Insufficient permissions'
}, 403
Flask-RESTful Integration
from flask import Flask
from flask_restful import Api, Resource
from uvb_flask import UVBFlask, get_session, require_factors
app = Flask(__name__)
api = Api(app)
uvb = UVBFlask(app, tenant_id='my-tenant')
class UserProfile(Resource):
def get(self):
session = get_session()
if not session:
return {'error': 'Not authenticated'}, 401
return {'user_id': session.user_id}
class AdminResource(Resource):
method_decorators = [require_factors('totp', 'webauthn')]
def delete(self, user_id):
return {'message': f'User {user_id} deleted'}
api.add_resource(UserProfile, '/profile')
api.add_resource(AdminResource, '/admin/user/<string:user_id>')
Application Factory Pattern
from flask import Flask
from uvb_flask import UVBFlask
uvb = UVBFlask()
def create_app(config=None):
app = Flask(__name__)
if config:
app.config.from_object(config)
# Initialize extensions
uvb.init_app(
app,
tenant_id=app.config.get('UVB_TENANT_ID'),
uvb_url=app.config.get('UVB_URL')
)
# Register blueprints
from .api import api_bp
app.register_blueprint(api_bp)
return app
# Run the app
app = create_app()
if __name__ == '__main__':
app.run()
Testing
import pytest
from flask import Flask
from uvb_flask import UVBFlask
@pytest.fixture
def app():
app = Flask(__name__)
app.config['TESTING'] = True
app.config['UVB_TENANT_ID'] = 'test-tenant'
uvb = UVBFlask(app)
@app.route('/test')
def test_route():
return {'message': 'test'}
return app
@pytest.fixture
def client(app):
return app.test_client()
def test_authentication_required(client):
response = client.get('/test')
assert response.status_code == 401
def test_with_valid_token(client):
response = client.get(
'/test',
headers={'Authorization': 'Bearer valid-token'}
)
assert response.status_code == 200
Development
# Install development dependencies
pip install -e ".[dev]"
# Run tests
pytest
# Type checking
mypy uvb_flask
License
MIT
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 uvb_flask-0.2.1.tar.gz.
File metadata
- Download URL: uvb_flask-0.2.1.tar.gz
- Upload date:
- Size: 10.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f3dcee1f07a8c53d5a0ac0d4119347b0cd96963e55cb6020f15b2b1b1089272c
|
|
| MD5 |
2e4ef6c1e54232706e22a94543304f6b
|
|
| BLAKE2b-256 |
328b2214fdba2e81f202fb7f4953f1200ddc42d031dcc64716db716e3b5a498e
|
File details
Details for the file uvb_flask-0.2.1-py3-none-any.whl.
File metadata
- Download URL: uvb_flask-0.2.1-py3-none-any.whl
- Upload date:
- Size: 11.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
73fef38c4b7f86008c8ac9a9549ba9f270a07ed8835a394de957e28ef5f10b38
|
|
| MD5 |
67cab3e88f5e38b70cb08500bd05fca5
|
|
| BLAKE2b-256 |
7d074d080526e116e7f8a75cc13c68ef1b97fbfadae8aa47fc71664121b06012
|