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 — the state of a sign-in, kept in the cache under a store token
- 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/ — deprecated, removed in 3.0: the token endpoint of bazis-users (
POST BAZIS_OPENAPI_TOKEN_URL, which Swagger UI uses) under this path- Accepts
usernameandpasswordviaOAuth2PasswordRequestForm - Returns JWT token directly without cookie
- Accepts
Request schema (POST /password/):
{
"username": "user1",
"password": "password123"
}
The token endpoint answers {"access_token": "...", "token_type": "bearer"}.
AuthStore
The state of a sign-in: the data lives in the default cache (auth_store_<key>), the
client holds only the store token (a JWT signed with SECRET_KEY, with the random key as
sub and no expiration), as the bearer token, the cookie or the query parameter
BAZIS_AUTH_COOKIE_NAME.
Location: bazis.contrib.authing.service.AuthStore
Purpose:
- Store temporary authentication data in the cache
- 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), the token endpoint of
bazis-users (BAZIS_OPENAPI_TOKEN_URL, default /api/openapi-token/):
POST /api/openapi-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): a list of actions"""
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.7.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 | Provenance | Actions |
|---|---|---|---|---|
| bazis_authing-2.7.0.tar.gz | 139.3 kB | Without provenance | Details for bazis_authing-2.7.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| bazis_authing-2.7.0-py3-none-any.whl | Python 3 | none | any | Details for bazis_authing-2.7.0-py3-none-any.whl |
Total release size: 175.4 kB
Release files / bazis_authing-2.7.0.tar.gz
| Download URL | bazis_authing-2.7.0.tar.gz |
|---|---|
| Size | 139.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
84b0c24a752ea2e169ac26a882c271bdc492da389cb9691b92da3e5851411f18
|
|
BLAKE2b-256 checksum How to use checksums |
0d732e20922d1c689f1fd160ce0024dbb56f33215b228f5987a6278e3303ee5f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","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.7.0-py3-none-any.whl
| Download URL | bazis_authing-2.7.0-py3-none-any.whl |
|---|---|
| Size | 36.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
205d93ca17b4401798f2290fbc4ab66eb18532571344134d7f504c91c6e1c4ae
|
|
BLAKE2b-256 checksum How to use checksums |
8989f07a17437128962e2532bdfd9bae1a4948b0051aad49ad85a099dfc1109d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","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}
|
Security for bazis-authing 2.7.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.