Skip to main content

vs-security

JWT-based authentication and authorization library for FastAPI services in the Viveka Sutra platform. Drop it in, configure a secret key, and your endpoints are protected in minutes.


Installation

pip install vs-security

With FastAPI guard support (recommended):

pip install "vs-security[fastapi]"

Dependencies: pyjwt, bcrypt, pydantic, vs-common


How It Works

Login Request
     │
     ▼
VsAuthManager.authenticate()
     │
     ▼
VsAuthProvider (e.g. VsUsernamePasswordAuthProvider)
     │  verifies credentials, returns VsAuthContext
     ▼
VsJWTProvider.generate_token()
     │  mints access + refresh JWT pair
     ▼
VsTokenPair  ──────────────────────────────────┐
                                               │
Protected Route Request (Bearer token)         │
     │                                         │
     ▼                                         │
VsSecurity (FastAPI dependency)                │
     │  verifies token, checks roles           │
     ▼                                         │
VsAuthContext available via get_auth_context() ◄┘

Quick Start

1. Initialize at startup

from vs_security.auth.vs_jwt_provider import VsJWTProvider
from vs_security.auth.vs_auth_manager import VsAuthManager
from vs_security.auth.vs_username_password_provider import VsUsernamePasswordAuthProvider
from vs_security.guard.vs_security_factory import VsSecurityFactory

jwt_provider = VsJWTProvider(config=config, token_store=token_store)

auth_manager = VsAuthManager(jwt_provider=jwt_provider)
auth_manager.register("username_password", VsUsernamePasswordAuthProvider(
    jwt_provider=jwt_provider,
    user_loader=my_user_loader,   # see below
))

VsSecurityFactory.init(
    secret_key=config.get("auth.secret_key"),
    algorithm="HS256",
)

2. Implement a user loader

async def my_user_loader(username: str):
    identity = await identity_repo.find_by_username(username)
    if not identity:
        return None
    context = VsAuthContext(
        user_id=identity.user_id,
        username=identity.username,
        roles=identity.roles,
        provider="email",
    )
    return context, identity.hashed_password  # hashed_password is bcrypt hash

3. Add a login endpoint

from vs_security.schema.vs_credentials import VsUsernamePasswordCredentials
from vs_security.error.vs_auth_error import VsAuthenticationError

@post("/auth/login")
async def login(body: LoginRequest):
    try:
        token_pair = await auth_manager.authenticate(
            "username_password",
            VsUsernamePasswordCredentials(username=body.username, password=body.password),
        )
        return {"access_token": token_pair.access_token, "refresh_token": token_pair.refresh_token}
    except VsAuthenticationError as e:
        raise HTTPException(status_code=401, detail=str(e))

4. Protect routes

from vs_security.guard.vs_security_factory import VsSecurityFactory
from vs_security.guard.vs_security import get_auth_context

# Protect all routes in a controller
@controller("/llm", guards=[VsSecurityFactory.get()])
class LlmController:

    @get("/data")
    async def get_data(self):
        context = get_auth_context()   # available anywhere after guard runs
        return {"user_id": str(context.user_id)}

5. Role-based access

# Only users with "admin" role can access
@controller("/admin", guards=[VsSecurityFactory.with_roles(["admin"])])
class AdminController:
    ...

# Or check roles manually inside a handler
context = get_auth_context()
if not context.has_role("admin"):
    raise HTTPException(status_code=403)

Configuration

Set these in your config.ini (read by VsBaseConfig):

Key Default Description
auth.secret_key required JWT signing secret
auth.algorithm HS256 JWT algorithm
auth.access_expiry_minutes 15 Access token lifetime
auth.refresh_expiry_days 7 Refresh token lifetime

Token Lifecycle

# Refresh an access token
new_pair = await jwt_provider.refresh_token(body.refresh_token)

# Revoke a single refresh token (logout)
await jwt_provider.revoke_token(user_id, body.refresh_token)

# Revoke all tokens (logout everywhere)
await jwt_provider.revoke_all_tokens(user_id)

Token revocation requires a VsTokenStore implementation. Implement the interface and pass it to VsJWTProvider:

class VsTokenStore(ABC):
    async def save(self, user_id: UUID, refresh_token: str) -> None: ...
    async def get_all(self, user_id: UUID) -> List[str]: ...
    async def delete(self, user_id: UUID, refresh_token: str) -> None: ...
    async def delete_all(self, user_id: UUID) -> None: ...

Custom Auth Providers

Extend VsAuthProvider to support OAuth, API keys, or any other credential type:

from vs_security.auth.vs_auth_provider import VsAuthProvider
from vs_security.schema.vs_auth_context import VsAuthContext
from vs_security.schema.vs_credentials import VsCredentials

class MyApiKeyCredentials(VsCredentials):
    api_key: str

class ApiKeyAuthProvider(VsAuthProvider):
    async def authenticate(self, credentials: VsCredentials) -> VsAuthContext:
        # validate api_key, return VsAuthContext
        ...

auth_manager.register("api_key", ApiKeyAuthProvider())

Error Reference

Exception HTTP Status When
VsInvalidCredentialsError 401 Wrong username or password
VsTokenExpiredError 401 JWT has expired
VsTokenRevokedError 401 Refresh token was revoked
VsAuthenticationError 401 Base auth failure
VsInsufficientRolesError 403 User lacks required roles

VsSecurity maps these automatically to HTTP responses — you only need to catch them in your login/refresh endpoints.


VsAuthContext Reference

Available inside any protected route via get_auth_context():

context.user_id          # UUID
context.username         # str
context.roles            # List[str]
context.provider         # str  (e.g. "email")

context.has_role("admin")              # bool
context.has_any_role("admin", "mod")   # bool
context.has_all_roles("admin", "mod")  # bool

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

vs_security-0.1.1.tar.gz (10.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

vs_security-0.1.1-py3-none-any.whl (12.1 kB view details)

Uploaded Python 3

File details

Details for the file vs_security-0.1.1.tar.gz.

File metadata

  • Download URL: vs_security-0.1.1.tar.gz
  • Upload date:
  • Size: 10.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for vs_security-0.1.1.tar.gz
Algorithm Hash digest
SHA256 0605dbb4407effdc5b187f824a47548729bc1e967c0d372d00bae5ab8d90be95
MD5 3bfd13613997ea1e0e2cdd7bfd8fd3ae
BLAKE2b-256 4d7ed440d2c51f28feedd1a732fc39f6c65bddfc07d3c83eaa596024cd0b65ad

See more details on using hashes here.

File details

Details for the file vs_security-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: vs_security-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 12.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for vs_security-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 c8fe8011e12111ae4ca86ac451b139f398a2eb98dbed9d3b8675b9b9b3aec23f
MD5 89885397c5040a47bda9a467b95ad7db
BLAKE2b-256 16fb7ef47846d96943526fd0104273116a60c57e3c1cc07972b1390711058b47

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page