Overview
Injectable FastAPI authentication and authorization against OIDC providers, built almost
entirely on the Python standard library. armasec-lite is a dependency-minimal
reimplementation of armasec 3.0.3 with
the same public API: it validates bearer tokens against your identity provider's JWKS,
checks scopes and issuer/audience claims, and plugs into a route with a single
Depends(), all with three runtime dependencies instead of upstream's ten.
Quickstart
uv add armasec-lite
"""Secure a single route against one OIDC domain."""
import os
from armasec_lite import Armasec
from fastapi import Depends, FastAPI
app = FastAPI()
armasec = Armasec(
domain=os.environ.get("ARMASEC_DOMAIN"),
audience=os.environ.get("ARMASEC_AUDIENCE"),
)
@app.get("/stuff", dependencies=[Depends(armasec.lockdown("read:stuff"))])
async def check_access():
return {"message": "Successfully authenticated!"}
More usage patterns, including multiple domains, match_keys, and the plugin system, are
in examples/.
Why
Upstream armasec carries ten runtime dependencies. Two of them (pytest, respx) are
test tools forced into every production install: armasec==3.0.3 declares pytest<9,>=6
and respx as runtime requirements, which is exactly the kind of constraint that made this
project's own lockfile unsatisfiable when we tried to depend on upstream for benchmarking
against a pytest>=9.1 toolchain. armasec-lite ships three runtime dependencies:
fastapi, cryptography, and pydantic. pydantic is kept rather than dropped because
fastapi requires it and imports it unconditionally, so it is installed and loaded in
every deployment regardless of what this project declares; keeping it as a direct
dependency preserves model_dump(), response_model=, and except pydantic.ValidationError
for anyone migrating from upstream, instead of breaking all three for a saving of zero
bytes.
| Upstream dependency | Replacement |
|---|---|
python-jose[cryptography] |
jwt.py (stdlib parsing, cryptography primitives) |
httpx |
urllib.request |
pydantic |
kept: fastapi requires it, so it costs nothing |
py-buzz |
exceptions.py (~50 LOC) |
snick |
textwrap |
auto-name-enum |
enum.Enum |
pluggy |
importlib.metadata entry points (~55 LOC) |
respx |
monkeypatch of the internal HTTP layer |
pytest (runtime) |
moved to a [test] extra |
typer |
dropped with the CLI |
fastapi |
kept |
| (new) | cryptography |
Migrating from armasec
This is the complete list of behavior differences from upstream armasec 3.x. Migrating from 2.x is out of scope and untested; a 2.x consumer should upgrade to 3.x first and confirm their application works there.
Requires action from an integrator
| Difference | What breaks | Fix |
|---|---|---|
Import name is armasec_lite |
Every import line | A scoped sed over the project's own sources |
verify_issuer defaults to True |
Routes 401 when the provider's discovery issuer does not exactly match the iss it mints, most often over a trailing slash |
Correct the provider, or DomainConfig(verify_issuer=False) |
| Errors no longer derive from py-buzz | except buzz.Buzz stops catching ArmasecError |
Catch armasec_lite.exceptions.ArmasecError |
The pytest fixtures live behind the [test] extra |
A ported test suite cannot import the fixtures from a plain install, because upstream forced pytest into every install and this does not |
Depend on armasec-lite[test] |
| The OIDC loader cache is process-wide | Tests that expect per-instance provider state now share it | openid_config_loader.clear_cache(), or the mock_openid_server fixture, which calls it automatically |
| The CLI is not included | armasec console script is gone |
Out of scope; see below |
exp, nbf and iat must be finite numbers, and JSON Infinity/NaN are refused outright |
A token carrying "exp": 1e400, or a payload containing the non-standard JSON constants, was accepted upstream and is now a 401 |
Nothing, unless a provider is minting such tokens, in which case fix the provider: an exp of infinity is a token that never expires |
Requires no action, listed so the API diff is complete
| Difference | Why it is safe |
|---|---|
TokenDecoder gained an optional jwks_refresher keyword argument |
Purely additive; the first positional argument is still JWKs, so existing construction sites are unaffected |
| Models remain pydantic | model_dump(), model_validate(), response_model= and pydantic.ValidationError all keep working, as upstream |
JWK no longer requires n and e |
Strictly more permissive. Upstream fails to parse a JWKS document containing an EC or OKP key; this parses it |
handle_errors re-raises an ArmasecError subclass unchanged instead of re-wrapping it |
Deliberate, and a deviation from py-buzz. Re-wrapping would let the PayloadMappingError block in TokenDecoder.decode swallow a genuine AuthenticationError and turn a 401 into a 500. Anything that is not already an ArmasecError is still wrapped, so the contract holds for every foreign error |
DomainConfig.domain is required and must be non-empty, where upstream defaults it to "" |
An empty domain builds the discovery URL https:///.well-known/openid-configuration and fails at request time with an error that names neither the domain nor the configuration. The only caller that relied on the empty default, Armasec.__init__, checks for the keyword before constructing, so Armasec() still raises its own 422 |
DomainConfig.algorithm is checked against the supported set at construction |
Strictly earlier failure. A typo previously configured a route that refused every token it was ever shown, with a message about the token |
UnknownKeyIdError is a new public exception type |
An AuthenticationError subclass carrying the same 401. Nothing that caught AuthenticationError stops catching it; it exists so the library can tell "no key matched" apart from every other 401 and refresh the JWKS in response |
The RFC 7515 unsecured JWS shape returns InvalidAlgorithmError |
Still a 401, still an AuthenticationError subclass. Only the error type and message are more specific, which is what makes an alg: none attempt legible in a log |
What is not included
The armasec CLI (device-code login, token cache) is not part of this package. It is
already shipped as an extra upstream, it needs typer, rich, loguru, pendulum and
pyperclip, and a stdlib rewrite of it is roughly as much work as the rest of this
project combined. This library validates tokens; it does not issue, refresh, or otherwise
participate in any OIDC client-side flow. A CLI can ship later as a separate distribution
if there is demand for one.
Security
Signature verification delegates entirely to cryptography; nothing here hand-rolls RSA,
ECDSA, EdDSA, or HMAC primitives. The verification order implemented in
armasec_lite/jwt.py (structural checks, then the algorithm allowlist, then crit header
validation, then signature verification with the JWK-type-versus-algorithm check inside it,
and only afterward claim validation) is a security property, not an implementation detail,
and is documented with its rationale directly in that module. Two orderings carry the
security of the module: the algorithm is decided from the caller's allowlist before any key
is touched, and nothing in the payload is read until the signature has verified.
tests/unit/test_jwt_attacks.py is the
enumeration of what is defended: alg: none, algorithm confusion, disallowed algorithms,
kid mismatch, tampered headers and payloads, malformed ECDSA signature lengths,
unrecognized crit entries, expired and not-yet-valid tokens, and audience/issuer
mismatches.
Documentation
Full docs, including architecture, the request lifecycle, the caching and threading model, and the migration guide, are at https://docs.vantagecompute.ai/developer/armasec-lite/.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file armasec_lite-0.1.3.tar.gz.
File metadata
- Download URL: armasec_lite-0.1.3.tar.gz
- Upload date:
- Size: 832.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
29cbd33717fa56ee8078b218dd28004b0d5ba60f06b55f7cc89ff5d5084e3a25
|
|
| MD5 |
24c92bf4f7a3f986d54d25b2e486af13
|
|
| BLAKE2b-256 |
241000c87f052a87c0a101b327114707042a41c6425e26bc8e0fb711c0a9913b
|
Provenance
The following attestation bundles were made for armasec_lite-0.1.3.tar.gz:
Publisher:
release.yml on vantagecompute/armasec-lite
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
armasec_lite-0.1.3.tar.gz -
Subject digest:
29cbd33717fa56ee8078b218dd28004b0d5ba60f06b55f7cc89ff5d5084e3a25 - Sigstore transparency entry: 2720162907
- Sigstore integration time:
-
Permalink:
vantagecompute/armasec-lite@3953fae275153a13610d49d43417b92612a391b8 -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/vantagecompute
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@3953fae275153a13610d49d43417b92612a391b8 -
Trigger Event:
push
-
Statement type:
File details
Details for the file armasec_lite-0.1.3-py3-none-any.whl.
File metadata
- Download URL: armasec_lite-0.1.3-py3-none-any.whl
- Upload date:
- Size: 75.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
aaf320d33da0d6cc914c92274822f4fe83cc9a2b13ff9fa6a07a33f7c6ffd006
|
|
| MD5 |
4341980d3dd2d01fce893a81ede99881
|
|
| BLAKE2b-256 |
1a4d34fd58b881ccc5c61c1adbc773c54c2cbde0bd2d56c3a9055b48c0646a71
|
Provenance
The following attestation bundles were made for armasec_lite-0.1.3-py3-none-any.whl:
Publisher:
release.yml on vantagecompute/armasec-lite
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
armasec_lite-0.1.3-py3-none-any.whl -
Subject digest:
aaf320d33da0d6cc914c92274822f4fe83cc9a2b13ff9fa6a07a33f7c6ffd006 - Sigstore transparency entry: 2720163028
- Sigstore integration time:
-
Permalink:
vantagecompute/armasec-lite@3953fae275153a13610d49d43417b92612a391b8 -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/vantagecompute
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@3953fae275153a13610d49d43417b92612a391b8 -
Trigger Event:
push
-
Statement type: