Skip to main content

django-offline-auth

Reusable Django package for securely issuing offline authentication credentials to mobile applications.

The Django side is intentionally user-centric and device-agnostic.

It does not store device IDs, IMEI numbers, device ownership, Android private keys, or extra copies of user password verifiers.

The host application decides who can authenticate offline. django-offline-auth handles how their credentials are securely issued.

Architecture

Host Django Application
        │
        │ selects eligible users
        ▼
django-offline-auth
        │
        │ encrypts offline credentials
        ▼
Android Application
        │
        │ protects private key with Android Keystore
        ▼
Offline Authentication

Installation

pip install django-offline-auth

Add it to Django:

INSTALLED_APPS = [
    # ...
    "django_offline_auth",
]

Run migrations:

python manage.py migrate

Public API

from django_offline_auth.application import (
    disable_offline_auth,
    enable_offline_auth,
    is_enabled_offline_auth,
    issue_offline_credentials,
    rotate_offline_credentials,
)

Enable offline authentication:

enable_offline_auth(user)

Check whether it is enabled:

if is_enabled_offline_auth(user):
    print("Offline authentication is enabled")

Disable it:

disable_offline_auth(user)

Rotate the user's offline credential state:

rotate_offline_credentials(user)

credential_version is incremented when credentials are explicitly rotated or when offline authentication is disabled and later re-enabled.

Issuing Offline Credentials

The host application decides which users should be available offline:

snapshot = issue_offline_credentials(
    users=eligible_users,
    public_key=android_public_key,
)

The package:

  1. checks that each user is active and enabled for offline authentication;
  2. validates the user's Django password verifier;
  3. builds an offline credential payload;
  4. encrypts the payload with AES-256-GCM;
  5. wraps the AES key using the Android RSA public key;
  6. returns an encrypted synchronization snapshot.

Synchronization Request

The mobile application sends its RSA public key:

{
  "public_key": "MIIBIjANBgkqh..."
}

The host application must decide which users are eligible. Clients should never be allowed to submit arbitrary user IDs to determine who receives offline credentials.

Synchronization Response

Example:

{
  "protocol_version": 1,
  "credentials": [
    {
      "user_id": "1",
      "username": "mary",
      "envelope": {
        "key_algorithm": "RSA-OAEP-256",
        "content_algorithm": "A256GCM",
        "encrypted_key": "...",
        "nonce": "...",
        "ciphertext": "..."
      }
    }
  ],
  "failures": [
    {
      "user_id": "2",
      "username": "john",
      "reason": "unusable_password"
    }
  ]
}

A problem with one user does not have to fail the entire synchronization batch.

Possible user-level failures include:

unusable_password
unsupported_password_algorithm
invalid_password_verifier

Offline Flow

User enters username + password
        ↓
Android finds that user's encrypted credential
        ↓
Android Keystore private key unwraps the AES key
        ↓
AES-GCM decrypts the credential payload
        ↓
PBKDF2 verifies the password
        ↓
Success / Failure

A single Android installation may cache credentials for multiple users.

The client should look up the entered username first and verify only that user's credential.

Cryptography

Protocol version 1 uses:

Password verifier:
pbkdf2_sha256

Content encryption:
AES-256-GCM

AES key wrapping:
RSA-OAEP-SHA256

Private key storage:
Android Keystore

The Android private key must never leave Android Keystore.

The public key may be sent to Django during synchronization and does not need to be stored by the backend.

Credential Policy

Offline credentials:

  • use the user's current Django password verifier;
  • are encrypted before leaving Django;
  • are never returned as plaintext API data;
  • expire after a configurable period;
  • use a default lifetime of 24 hours;
  • are refreshed during successful synchronization.

If a password changes while a device is offline, the previous cached credential may remain valid until the next synchronization or credential expiry.

Authoritative Snapshot

The synchronization result is treated as the authoritative set of offline credentials.

If Android previously has:

Mary
John
Peter

and the latest successful synchronization returns only:

Mary

Android should keep/update Mary and remove John and Peter.

Configuration

Default credential lifetime:

DJANGO_OFFLINE_AUTH_CREDENTIAL_TTL_SECONDS = 24 * 60 * 60

Minimum RSA key size:

DJANGO_OFFLINE_AUTH_MIN_RSA_KEY_SIZE = 2048

An individual request may override the default TTL:

from datetime import timedelta

snapshot = issue_offline_credentials(
    users=eligible_users,
    public_key=public_key,
    ttl=timedelta(hours=6),
)

Security Rules

  • Never include Django password hashes in JWTs.
  • Never expose password verifiers as plaintext API data.
  • Never log passwords, verifiers, AES keys, or private keys.
  • Never persist another plaintext copy of user.password.
  • Never store Android private keys outside Android Keystore.
  • Always validate supplied public keys.
  • Always use HTTPS.
  • Use a fresh AES key and nonce for every credential.
  • Enforce credential expiry on the mobile client.
  • Keep decrypted verifier material inside native Android code.

Application-level encryption does not replace TLS.

Responsibilities

Host Django Application

Responsible for:

authentication
authorization
business rules
selecting eligible users
API integration

django-offline-auth

Responsible for:

OfflineAuth state
credential generation
credential versioning
expiry
password verifier validation
AES-GCM encryption
RSA-OAEP key wrapping
synchronization response

Android Application

Responsible for:

Android Keystore
private-key protection
encrypted local storage
credential decryption
offline password verification
expiry enforcement
attempt limiting
authoritative snapshot replacement

Protocol Version

Current protocol:

protocol_version = 1

Protocol v1 supports:

Password algorithm: pbkdf2_sha256
Key wrapping:       RSA-OAEP-256
Encryption:         A256GCM

Breaking changes to the wire format or cryptographic contract should increment protocol_version.

Metadata

Release files for django-offline-auth 0.1.2

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

Source distribution (sdist)

Source distribution for django-offline-auth 0.1.2
File Size Uploaded Provenance Actions
django_offline_auth-0.1.2.tar.gz 14.7 kB Without provenance Details for django_offline_auth-0.1.2.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for django-offline-auth 0.1.2
File Interpreter ABI Platform Actions
django_offline_auth-0.1.2-py3-none-any.whl Python 3 none any Details for django_offline_auth-0.1.2-py3-none-any.whl

Total release size: 31.9 kB

Release files / django_offline_auth-0.1.2.tar.gz

Download URL django_offline_auth-0.1.2.tar.gz
Size 14.7 kB
Tags Source
SHA-256 checksum
How to use checksums
8f5c2565b0198aec35321bb001749afd73d075e92eef2b468394f0bbdf52cc47
BLAKE2b-256 checksum
How to use checksums
bded3fb97afe26c7a20885ce552cd5bb928bc6c5fe843ee17b9cfb3a8e63d56b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.7

Release files / django_offline_auth-0.1.2-py3-none-any.whl

Download URL django_offline_auth-0.1.2-py3-none-any.whl
Size 17.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1eb12d3ca987a205396be22061002f4e32ac0d05b9b42ee50a423a35274c731c
BLAKE2b-256 checksum
How to use checksums
a12fbcfacf5bbacd520e5c83fdd337ab7636ffbef0da127242b2d254e3201ee1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.7

Security for django-offline-auth 0.1.2

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.

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