Skip to main content

Bazis Authing

PyPI version Python Versions License

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

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

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

Behavior:

  1. 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..."
      }
    }
  ]
}
  1. 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:

  1. 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
  2. POST /api/v1/authing/password/token/ — get JWT token for Swagger/API

    • Accepts username and password via OAuth2PasswordRequestForm
    • Returns JWT token directly without cookie
    • Used for authentication in Swagger UI

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 authentication
  • set_error(code, detail) — saves authentication error
  • data_reset() — clears storage
  • response_set_cookie(response) — adds cookie to response

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/
Cookie: bazis_auth=auth_token_here
Content-Type: application/json

{
  "username": "user1",
  "password": "password123"
}

Response (successful authentication):

HTTP 303 See Other
Location: /api/v1/authing/auth/?token=updated_token
Set-Cookie: bazis_auth=updated_token; Path=/; HttpOnly

Step 3: Client follows redirect and gets user data:

GET /api/v1/authing/auth/?token=updated_token
Cookie: bazis_auth=updated_token

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.

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

  1. Initialization:

    • Client requests /auth/ without cookie
    • System creates temporary token and returns it in meta
    • Client saves this token
  2. Authentication:

    • Client sends credentials with token in cookie
    • System validates credentials via Django authenticate()
    • On success — saves user_id in AuthStore
    • Sets updated cookie and redirects
  3. Token Retrieval:

    • Client requests /auth/ with cookie
    • System extracts user_id from AuthStore
    • Generates JWT token via user.jwt_build()
    • Returns user data and token

Security

  • HttpOnly cookie — protection against XSS attacks
  • Temporary tokens — limited lifetime for auth cookie
  • Encrypted storage — cookie data is encrypted
  • 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.

Support

If you have questions or issues:


Made with ❤️ by Bazis team

Metadata

Release files for bazis-authing 2.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for bazis-authing 2.5.0
File Size Uploaded
bazis_authing-2.5.0.tar.gz 129.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for bazis-authing 2.5.0
File Interpreter ABI Platform
bazis_authing-2.5.0-py3-none-any.whl Python 3 none any Details

Total release size: 161.8 kB

Release files / bazis_authing-2.5.0.tar.gz

Download URL bazis_authing-2.5.0.tar.gz
Size 129.8 kB
Tags Source
SHA-256 checksum
How to use checksums
fb019fd37a51f1478b7814eeca232f31b5c09d0b4564d43023de1f08e8cd12db
BLAKE2b-256 checksum
How to use checksums
def0fe1dd13e2a032a98926ec6b441cdbfff98faecadb278a9e58fcddecbc842
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.5.0-py3-none-any.whl

Download URL bazis_authing-2.5.0-py3-none-any.whl
Size 32.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4313291341bda9636da465b280777a2950c15e1b3c64525900aca0b02d531ce2
BLAKE2b-256 checksum
How to use checksums
568d8483122995f3d1ee2ab7082f608161f70dc3a2d51946b6463829c6f6dc25
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 history Release notifications | RSS feed

2.6.0

2 release files

This release

2.5.0 This release

2 release files

2.4.0

2 release files

2.3.1

2 release files

2.3.0

2 release files

2.2.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page