Bazis Authing
Extension package for Bazis providing a flexible authentication system with support for various login methods.
Quick Start
# Install the package
uv add bazis-authing
# Configure in settings.py
BAZIS_AUTH_KINDS = [
'bazis.contrib.authing.services.password', # Username/password authentication
]
# Register routes in main router.py
from bazis.core.routing import BazisRouter
router = BazisRouter(prefix='/api/v1')
from bazis.contrib.authing.routes import router as authing_router
from bazis.contrib.authing.services.password.router import router as password_router
router.register(prefix='/authing', arg=authing_router)
# the router of every service in BAZIS_AUTH_KINDS
router.register(prefix='/authing', arg=password_router)
Table of Contents
- Description
- Requirements
- Installation
- Core Components
- Usage
- How It Works
- Examples
- Extension
- License
- Links
Description
Bazis Authing is an extension package for the Bazis framework that provides a flexible and extensible authentication system. The package includes:
- Universal
/auth/endpoint — central point for checking authentication status and getting available login methods - Built-in password authentication — ready-to-use module for username/password authentication
- AuthStore — cookie-based authentication state storage system
- Support for multiple authentication methods — easily add OAuth, SAML, LDAP, and other providers
- JWT integration — automatic JWT token generation after successful authentication
This package requires bazis and bazis-users packages to be installed.
Requirements
- Python: 3.12+
- bazis: latest version
- bazis-users: latest version
- PostgreSQL: 12+
- Redis: For caching
Installation
Using uv (recommended)
uv add bazis-authing
Using pip
pip install bazis-authing
Core Components
/auth/ Endpoint
Central endpoint for authentication operations.
URL: GET /api/v1/authing/auth/
Purpose:
- Check current authentication status
- Get list of available login methods
- Get JWT token for authenticated users (a signed-in authorization store gives it once: the store is deleted, the next request with its token is not authenticated)
Behavior:
- If user is NOT authenticated → returns 401 error with list of available login methods:
{
"errors": [
{
"status": 401,
"code": "UNAUTHORIZED",
"title": "User is not authorized.",
"detail": "User is not authorized.",
"meta": {
"actions": [
{
"code": "password",
"name": "Login/Password",
"url": "/api/v1/authing/password/",
"method": "POST"
}
],
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}
]
}
- If user is authenticated → returns user data and JWT token:
{
"user_id": "123e4567-e89b-12d3-a456-426614174000",
"username": "user1",
"first_name": "John",
"last_name": "Doe",
"email": "user1@example.com",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"logout_actions": [
{
"url": "/api/v1/authing/logout/",
"method": "POST"
}
]
}
Authentication Methods
Password Authentication
Built-in module for username and password authentication.
Module code: password
Endpoints:
-
POST /api/v1/authing/password/ — web authentication with redirect
- Accepts username and password
- On successful authentication, sets cookie and redirects to
/auth/ - On failed authentication, returns error
-
POST /api/v1/authing/password/token/ — get JWT token for Swagger/API
- Accepts
usernameandpasswordviaOAuth2PasswordRequestForm - Returns JWT token directly without cookie
- Used for authentication in Swagger UI
- Accepts
Request schema (POST /password/):
{
"username": "user1",
"password": "password123"
}
Response schema (POST /password/token/):
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer"
}
AuthStore
Cookie-based authentication state storage system.
Location: bazis.contrib.authing.service.AuthStore
Purpose:
- Store temporary authentication data in encrypted cookie
- Track multi-step authentication progress
- Pass errors between requests
Main methods:
login(user, request, auth_type)— registers successful user authenticationset_error(code, detail)— saves authentication errordata_reset()— clears storageredirect_to_auth()— the answer of a login: 303 to/auth/with the cookie, without the store token in the URLresponse_set_cookie(response)— adds the store cookie to a response (HttpOnly, SameSite=Lax, Secure unlessBAZIS_AUTH_COOKIE_SECUREis off, for the lifetime of the store)claim()— the signed-in user id, once (deletes the store); used by/auth/
AuthStoreTokenRequired:
Dependency for endpoints requiring authentication token in cookie:
from bazis.contrib.authing.service import AuthStoreTokenRequired
from fastapi import Depends
@router.post('/password/')
def password_auth(auth_store: AuthStoreTokenRequired = Depends()):
# auth_store guaranteed to contain valid token
pass
Usage
Project Setup
1. Add to settings.py:
# settings.py
# List of available authentication methods
BAZIS_AUTH_KINDS = [
'bazis.contrib.authing.services.password', # Username/password
# Add other methods here
]
2. Register routes:
# router.py
from bazis.core.routing import BazisRouter
router = BazisRouter(prefix='/api/v1')
# Register authentication routes
from bazis.contrib.authing.routes import router as authing_router
from bazis.contrib.authing.services.password.router import router as password_router
router.register(prefix='/authing', arg=authing_router)
# the router of every service in BAZIS_AUTH_KINDS
router.register(prefix='/authing', arg=password_router)
Basic Password Authentication
Typical web authentication flow:
Step 1: Client requests authentication status:
GET /api/v1/authing/auth/
Response (if not authenticated):
{
"errors": [
{
"status": 401,
"code": "UNAUTHORIZED",
"meta": {
"actions": [
{
"code": "password",
"name": "Login/Password",
"url": "/api/v1/authing/password/",
"method": "POST"
}
],
"token": "auth_token_here"
}
}
]
}
Step 2: Client performs authentication:
POST /api/v1/authing/password/
Authorization: Bearer auth_token_here
Content-Type: application/json
{
"username": "user1",
"password": "password123"
}
Response (successful authentication):
HTTP 303 See Other
Location: /api/v1/authing/auth/
Set-Cookie: bazis_auth=auth_token_here; HttpOnly; Max-Age=60; Path=/; SameSite=lax; Secure
Step 3: Client follows redirect with the same token and gets user data (once: the store is deleted, a new login needs a new store):
GET /api/v1/authing/auth/
Authorization: Bearer auth_token_here
Response:
{
"user_id": "123e4567-e89b-12d3-a456-426614174000",
"username": "user1",
"email": "user1@example.com",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Getting JWT Token
For direct JWT token retrieval (e.g., for mobile apps or Swagger):
POST /api/v1/authing/password/token/
Content-Type: application/x-www-form-urlencoded
username=user1&password=password123
Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer"
}
This token can be used in the Authorization: Bearer <token> header for all subsequent requests.
Logout
POST /api/v1/authing/logout/
Authorization: Bearer auth_token_here
Deletes the authorization stores of the request (bearer token, cookie or query parameter)
and the bazis_auth cookie; answers 204 and is idempotent. The session JWT is stateless:
the client drops it.
How It Works
System Architecture
┌─────────────┐
│ Client │
└──────┬──────┘
│
│ 1. GET /auth/ (no cookie)
▼
┌─────────────────────┐
│ /auth/ endpoint │◄─── Checks user status
└──────┬──────────────┘
│
│ 2. Returns 401 + available actions
▼
┌─────────────┐
│ Client │
└──────┬──────┘
│
│ 3. POST /password/ (with credentials)
▼
┌─────────────────────┐
│ Password endpoint │◄─── Authenticates user
└──────┬──────────────┘
│
│ 4. Sets cookie + redirects to /auth/
▼
┌─────────────────────┐
│ /auth/ endpoint │◄─── Returns user data + JWT
└──────┬──────────────┘
│
│ 5. Returns user + token
▼
┌─────────────┐
│ Client │
└─────────────┘
Authentication Process
-
Initialization:
- Client requests
/auth/without cookie - System creates temporary token and returns it in meta
- Client saves this token
- Client requests
-
Authentication:
- Client sends credentials with token in cookie
- System validates credentials via Django
authenticate() - On success — saves
user_idin AuthStore - Sets updated cookie and redirects
-
Token Retrieval:
- Client requests
/auth/with cookie - System extracts
user_idfrom AuthStore - Generates JWT token via
user.jwt_build() - Returns user data and token
- Client requests
Security
- Single-use store —
/auth/gives the session of a signed-in store once - Temporary tokens — a store lives
BAZIS_AUTH_COOKIE_LIFETIMEseconds (default 600), a signed-in oneBAZIS_AUTH_CLAIM_LIFETIME(default 60); the cookie lives as long - Cookie attributes — HttpOnly, SameSite=Lax, Secure (
BAZIS_AUTH_COOKIE_SECURE=falsefor development over plain HTTP) - No tokens in redirects — the login redirects and the Google state do not carry the
store token; only
/google-auth-init/?bazis_auth=<store token>has it in the URL - Known limitation (login CSRF) — the Google init is a GET with the store token, so an
attacker can send a victim
/google-auth-init/?bazis_auth=<attacker's store>: a victim who already consented in Google signs the attacker's store in without a prompt, and the attacker takes the session. Planned fix: a POST init with the store token as the bearer token that returns a one-time ticket for the window URL - JWT tokens — for further API operations
Examples
Client Application Usage Example
// JavaScript client for authentication
class AuthClient {
constructor(baseUrl) {
this.baseUrl = baseUrl;
this.authToken = null;
this.jwtToken = null;
}
async checkAuth() {
const response = await fetch(`${this.baseUrl}/authing/auth/`, {
credentials: 'include'
});
if (response.ok) {
const data = await response.json();
this.jwtToken = data.token;
return { authenticated: true, user: data };
}
const error = await response.json();
this.authToken = error.errors[0].meta.token;
return {
authenticated: false,
actions: error.errors[0].meta.actions
};
}
async loginWithPassword(username, password) {
const response = await fetch(`${this.baseUrl}/authing/password/`, {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
'Cookie': `bazis_auth=${this.authToken}`
},
body: JSON.stringify({ username, password }),
redirect: 'follow'
});
if (response.ok) {
return await this.checkAuth();
}
throw new Error('Authentication failed');
}
async apiRequest(url, options = {}) {
return fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${this.jwtToken}`
}
});
}
}
// Usage
const auth = new AuthClient('https://api.example.com/api/v1');
// Check authentication status
const status = await auth.checkAuth();
if (!status.authenticated) {
// Perform login
await auth.loginWithPassword('user1', 'password123');
}
// Now you can make requests to protected endpoints
const response = await auth.apiRequest('/api/v1/protected/resource/');
Authentication Testing
import pytest
from bazis_test_utils.utils import get_api_client
from bazis.contrib.users import get_user_model
User = get_user_model()
@pytest.mark.django_db(transaction=True)
def test_password_authentication_flow(sample_app):
# Create user
user = User.objects.create_user(
'testuser',
email='test@example.com',
password='testpass123'
)
# Step 1: Get status (not authenticated)
response = get_api_client(sample_app).get('/api/v1/authing/auth/')
assert response.status_code == 400
data = response.json()
assert 'errors' in data
error = data['errors'][0]
assert error['code'] == 'UNAUTHORIZED'
auth_token = error['meta']['token']
actions = error['meta']['actions']
# Check available login methods
assert len(actions) == 1
assert actions[0]['code'] == 'password'
# Step 2: Perform authentication
response = get_api_client(sample_app, auth_token).post(
'/api/v1/authing/password/',
json_data={
'username': 'testuser',
'password': 'testpass123',
},
)
assert response.status_code == 200
# Step 3: Verify received data
data = response.json()
assert data['user_id'] == str(user.id)
assert data['username'] == 'testuser'
assert data['email'] == 'test@example.com'
assert 'token' in data # JWT token
# Step 4: Test invalid credentials
response = get_api_client(sample_app, auth_token).post(
'/api/v1/authing/password/',
json_data={
'username': 'testuser',
'password': 'wrongpassword',
},
)
assert response.status_code == 400
Extension
Adding New Authentication Method
You can add your own authentication method (OAuth, SAML, LDAP, etc.):
1. Create a module (e.g., myapp/auth_oauth.py):
from bazis.core.routing import BazisRouter
from django.utils.translation import gettext_lazy as _
AUTH_CODE = 'oauth'
router = BazisRouter(prefix='/oauth', tags=[_('OAuth Authentication')])
def get_login_action():
"""Returns login method information for /auth/"""
return {
'code': AUTH_CODE,
'name': 'OAuth Login',
'url': '/api/v1/authing/oauth/',
'method': 'GET',
}
def get_logout_actions():
"""Returns logout actions (optional)"""
return {
'url': '/api/v1/authing/oauth/logout/',
'method': 'POST',
}
@router.get('/')
def oauth_login():
# OAuth authentication logic
pass
2. Add to settings.py:
BAZIS_AUTH_KINDS = [
'bazis.contrib.authing.services.password',
'myapp.auth_oauth', # Your module
]
3. Register routes:
# myapp/router.py
from bazis.core.routing import BazisRouter
from . import auth_oauth
router = BazisRouter()
router.include_router(auth_oauth.router)
Now your authentication method will automatically appear in the list of available methods in /auth/!
License
Apache License 2.0
See LICENSE file for details.
Links
- Bazis Documentation — main repository
- Bazis Users — user management package
- Bazis Authing Repository — package repository
- Issue Tracker — report bugs or request features
Support
If you have questions or issues:
- Check Bazis documentation
- Search existing issues
- Create a new issue with detailed information
Made with ❤️ by Bazis team
Metadata
Release files for bazis-authing 2.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| bazis_authing-2.6.0.tar.gz | 137.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| bazis_authing-2.6.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 173.4 kB
Release files / bazis_authing-2.6.0.tar.gz
| Download URL | bazis_authing-2.6.0.tar.gz |
|---|---|
| Size | 137.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
744870d14fa083dbe89d95a896a38bab5bfbf63f778e3bc67aa2c195a11c1f9a
|
|
BLAKE2b-256 checksum How to use checksums |
58ed3c00bdd464980261a40377f8fde8f721adaa2552b01604df1414a956d32f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / bazis_authing-2.6.0-py3-none-any.whl
| Download URL | bazis_authing-2.6.0-py3-none-any.whl |
|---|---|
| Size | 35.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8dff19b40431ffd83f19dc32c803e9de51601d431aa4799cc7217581d368eb4e
|
|
BLAKE2b-256 checksum How to use checksums |
1d02cb8c5ef05e0ae4a8a9c303bf2dd6adb6b997e1f7388cedc57c1f07b6ec68
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|