{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"\ud83d\udd10 Aetoskia Auth Server \u2014 Central Identity for the Aetos Platform","text":"
The Aetoskia Auth Server is the central identity service of the Aetos Platform ecosystem. It issues, validates, and introspects short-lived JWT access tokens against a single shared user store backed by MongoDB, so every service can trust one issuer instead of shipping its own auth logic.
Doc model: this wiki is written for humans \u2014 how\u2011to guides, examples, and deployment recipes. The authoritative API contracts live in the code (GSDFC docstrings) and the interactive OpenAPI reference under docs/api/.
jwtlib or a generated OpenAPI dependency/introspect gives services a live active check rather than trusting expiry alone/register Create a user account POST /login Authenticate and issue a JWT GET /me Current user (Bearer) POST /logout Stateless logout (Bearer) POST /introspect Service-to-service token verification GET /health Health check"},{"location":"#documentation-structure","title":"\ud83d\udcc1 Documentation Structure","text":"Section What you'll find Centralized Auth The identity model and how the ecosystem trusts the server How to Use Register, login, and call endpoints with tokens Platform Integration How services authenticate requests Deployment Environment, Docker, and CI/CD Development Local setup, tests, and regenerating docs"},{"location":"#related-resources","title":"\ud83d\udd17 Related Resources","text":"docs/api/openapi.jsonaetos/auth-server images\u00a9 Aetoskia Internal \u2014 auth-server 0.0.5
The Aetoskia Auth Server acts as the single identity provider for every application in the Aetos Platform ecosystem. The model is deliberately small and uniform.
"},{"location":"01_centralized_auth/#the-identity-model","title":"\ud83e\uddec The identity model","text":" \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 Aetoskia Auth Server \u2502\n \u2502 users collection (MongoDB) \u2502\n \u2502 HS256 JWT issuer \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u2502 /login \u2192 JWT\n \u2502 /introspect \u2190 verification\n \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 \u2502 \u2502\n App A (FastAPI) App B (service) CLI/web clients\n verifies via verifies via\n jwtlib / OpenAPI jwtlib introspect\n dependency\n JWT_SECRET using HS256 and carry sub (the username) plus an exp claim.users collection; passwords are bcrypt-hashed and never leave the server.jwtlib (decode + signature) or ask the server via /introspect for a live answer.A valid access token is an HS256 JWT with exactly two claims:
{\n \"sub\": \"alice\",\n \"exp\": 1750000000\n}\n sub \u2014 the username of the authenticated user.exp \u2014 UTC epoch seconds of expiry (60 minutes by default).There are no scopes or roles inside the token; authorization decisions belong to the consuming service, while identity and authenticity belong here.
"},{"location":"01_centralized_auth/#how-the-ecosystem-trusts-the-server","title":"\ud83e\udd1d How the ecosystem trusts the server","text":"/login and receive a token.jwtlib or remotely via /introspect).get_current_user or the generated dependency \u2014 never by parsing JWTs by hand.This keeps authentication centralized while leaving each service in control of its own authorization rules.
"},{"location":"01_centralized_auth/#benefits","title":"\ud83d\udca1 Benefits","text":"JWT_SECRET rotation strategy protects the whole platform.This page covers the plain HTTP usage of the auth server: creating users, logging in, and calling protected endpoints with a bearer token.
"},{"location":"02_how_to_use/#register-a-user","title":"\ud83d\udc64 Register a user","text":"curl -X POST http://localhost:8000/register \\\n -H \"Content-Type: application/json\" \\\n -d '{\"username\": \"alice\", \"email\": \"alice@aetoskia.com\", \"password\": \"s3cret!\"}'\n username \u2014 3 to 50 characters (required).email \u2014 valid email (optional).password \u2014 at least 6 characters (required; stored hashed).Response 201 Created:
{\n \"username\": \"alice\",\n \"email\": \"alice@aetoskia.com\",\n \"is_active\": true\n}\n The password is never returned.
"},{"location":"02_how_to_use/#log-in-to-get-a-token","title":"\ud83d\udd11 Log in to get a token","text":"curl -X POST http://localhost:8000/login \\\n -H \"Content-Type: application/json\" \\\n -d '{\"username\": \"alice\", \"password\": \"s3cret!\"}'\n Response 200 OK:
{\n \"access_token\": \"<jwt>\",\n \"user\": {\n \"username\": \"alice\",\n \"email\": \"alice@aetoskia.com\",\n \"is_active\": true\n }\n}\n Invalid credentials return 401 with {\"detail\": \"Invalid credentials\"}.
Send the token as a bearer token:
curl http://localhost:8000/me \\\n -H \"Authorization: Bearer <jwt>\"\n Response 200 OK with the current user profile. A missing or invalid token returns 401 with WWW-Authenticate: Bearer.
curl -X POST http://localhost:8000/logout \\\n -H \"Authorization: Bearer <jwt>\"\n Logout is stateless \u2014 the server returns a confirmation and the client must discard the token:
{ \"message\": \"Successfully logged out. Please discard your token on the client.\" }\n"},{"location":"02_how_to_use/#token-lifecycle-quick-reference","title":"\ud83e\udded Token lifecycle quick reference","text":"Event Endpoint Result Create identity POST /register user profile Obtain token POST /login access_token + user Verify (self) GET /me user profile or 401 End session POST /logout discard-token confirmation Verify (other services) POST /introspect active + user Interactive examples are available in the API Reference site (Swagger UI), rendered from docs/api/openapi.json.
This page is for service authors wiring authentication into Aetos applications. It covers both supported integration paths.
"},{"location":"03_platform_integration/#path-1-jwtlib-client-py-jwt","title":"\ud83e\udde9 Path 1:jwtlib client (py-jwt)","text":"Services that depend on the py-jwt library can authenticate requests without an OpenAPI generator. Set the auth server base URL:
export JWT_SERVER=http://auth.aetoskia.com\n Then verify incoming requests:
from jwtlib.introspection import authenticate_request\n\nauthorized = await authenticate_request(\n should_skip_authentication, # e.g. allow public paths\n method,\n path,\n authorization_token, # \"Bearer <jwt>\" or None\n)\n authenticate_request POSTs {\"token\": \"<jwt>\"} to {JWT_SERVER}/introspect (3 second timeout) and treats {\"active\": true, \"user\": {...}} as valid. Transport errors surface as AuthServiceUnavailable.
For zero-request validation (offline token decode), jwtlib also exposes the token payload helpers used by the auth server itself \u2014 see the py-jwt documentation.
openapi-first generated dependencies","text":"Services declared with an OpenAPI-first contract can generate their FastAPI dependencies from the spec. Declare a bearer security scheme pointing at the auth server:
securitySchemes:\n HTTPBearer:\n type: http\n scheme: bearer\n x-server-url: https://auth.aetoskia.com\n x-introspect-path: /introspect\n The generated dependency then POSTs {\"token\": token} to the introspection path and returns the user on active == true, raising 401 on invalid or inactive tokens and 503 when the auth server is unreachable.
/introspect contract","text":"Field Type Description request token (string) The JWT to verify response active (bool) Whether the token is valid and active response user (PublicUser | null) The profile when active The endpoint always answers 200 \u2014 validity is expressed through active. IntrospectResponse never raises, so the caller can act on the tri-state (valid / invalid / user missing) without exception handling.
For first-party services, mount the auth router's dependency directly:
from fastapi import Depends\nfrom jwtlib import PublicUser\nfrom jwt import get_current_user\n\n@app.get(\"/profile\")\nasync def profile(current_user: PublicUser = Depends(get_current_user)):\n return current_user\n"},{"location":"03_platform_integration/#golden-rules","title":"\u26a0\ufe0f Golden rules","text":"jwtlib or introspect before handling the request.active: false as unauthenticated, even if the JWT decodes.jwtlib client so revocation and issuer changes stay centralized.How the auth server is configured and shipped.
"},{"location":"04_deployment/#environment-variables","title":"\u2699\ufe0f Environment variables","text":"Variable Required Default PurposeMONGO_HOST yes \u2014 MongoDB host MONGO_USER no \u2014 MongoDB username MONGO_PASS no \u2014 MongoDB password MONGO_PORT no 27017 MongoDB port MONGO_DB_NAME no auth Database name JWT_SECRET no* superstrongsecretkey Token signing secret (*set in production!) Credentials and the token secret live in the environment / Deploy secrets \u2014 see .env.example for the shape. .env is gitignored.
cp .env.example .env # fill in MONGO_HOST, JWT_SECRET\nuvicorn main:app --reload --port 8000\n The health endpoint is available at GET /health.
The Dockerfile is a multi-stage build on python:3.13-slim:
requirements.txt from the private pip index using build args PIP_USERNAME, PIP_PASSWORD, PIP_REPO_URL.curl, exposes port 8000, and runs uvicorn main:app. A HEALTHCHECK curls /health..drone.yml ships on git tag events (arm64):
docker build with pip credentials (secrets) \u2192 aetos/auth-server:$TAG and :latest.$REGISTRY_HOST/aetos/auth-server:*.auth-server.The deployed container runs with --restart always, maps host 9003 \u2192 8000, resolves private-pi to 192.168.1.111 for Mongo, and receives MONGO_* + JWT_SECRET from secrets.
https://auth.aetoskia.com Internal staging http://server-pi:9002 Local development http://localhost:8000"},{"location":"04_deployment/#read-next","title":"\ud83d\udcda Read Next","text":"/introspect contract.Working on the auth server itself.
"},{"location":"05_development/#repository-layout","title":"\ud83d\udcc2 Repository layout","text":"Path Purposemain.py FastAPI app factory, environment wiring, lifespan, OpenAPI customization jwt/ The auth routes package (router, get_current_user) generate_spec.py Regenerates the committed OpenAPI spec docs/api/ API reference (Swagger UI embed + openapi.json) docs/lib/ Generated library reference (docforge) docs/wiki/ This hand-written wiki tests/ Async route tests against an in-memory Mongo mock"},{"location":"05_development/#setup","title":"\ud83d\udd27 Setup","text":"python -m venv .venv\n.venv/Scripts/pip install -r requirements.txt\n Dependencies are installed from the private pip index (see requirements.txt and the Dockerfile). Core runtime packages:
py-jwt==0.0.4 \u2014 provides jwtlib (applications logic, models, security, introspection)mongo-ops==0.1.3 \u2014 MongoDB persistence layerfastapi, uvicorn, python-jose, passlib, bcrypt, pymongoRun the suite (no network or Mongo required \u2014 an in-memory mock is used):
.venv/Scripts/pytest --asyncio-mode=auto\n Coverage spans the full HTTP flow: register \u2192 login \u2192 wrong-password 401 \u2192 /me with and without a token \u2192 stateless logout.
The committed docs/api/openapi.json is produced offline:
set MONGO_HOST=127.0.0.1\npython generate_spec.py\n MONGO_HOST only needs to be set for the import; no connection is opened.
The site is generated by docforge and served per kind under site/{kind}:
doc-forge build \\\n --api --openapi-spec docs/api/openapi.json \\\n --mkdocs --wiki \\\n --module jwt \\\n --site-name \"Aetoskia Auth Server\"\n --api renders openapi.json with Swagger UI.--mkdocs renders the library reference from the jwt package docstrings (nested under docs/lib/jwt/, matching docforge.nav.yml).--wiki builds this wiki.docforge.nav.yml.The jwt module is rendered without --module-is-source so the library output stays nested under docs/lib/jwt/, matching the committed docs/mkdocs.lib.yml nav.
Preview locally:
doc-forge serve --api\ndoc-forge serve --lib\ndoc-forge serve --wiki\n"},{"location":"05_development/#read-next","title":"\ud83d\udcda Read Next","text":"