Security
Core Goals
-
Single Sign-On & Identity: Centralized login system using OAuth2/OIDC, acting as an "Auth0/Okta for microservices" for your ecosystem.
Audit & Observability: Every major action is logged to a central log service for transparency, debugging, and compliance.
MVP and iteration to Future Growth: You want this codebase to become a foundation you can iterate on—eventually supporting true zero-trust, fine-grained authorization, and scaling up securely.
How Does It Work?
1. Authentication & Gateway
-
Users sign in via a custom OAuth2/OIDC provider (
identity-backend), which issues JWT tokens for users and services. -
An
api_gatewayacts as the main entry point: handles user sessions, protects routes, and forwards jobs.
Components
- Identity service: Handles auth, issues JWT/OIDC.
- API Gateway with BFF: features for main web UI, pure REST endpoints for mobile apps, CLI tools, partner systems and etc:
- Gateway features: Auth, central entry point, request routing, REST endpoints
- BFF features: HTML rendering, session management, frontend-focused logic
- Logging centralized service: Collects logs from everywhere for centralized review.
“API Gateway”
-
Central auth and login flows
-
Central API entry point for all services
-
Routing to microservices (parser, worker, logger, identity, etc.)
-
Handles observability/logging for the entire platform
-
Accepts/forwards API calls from any kind of client (browser, CLI, mobile, other backend systems)
Sessions
The app uses Flask’s session mechanism to store a user’s authentication status (their id_token JWT) as a secure cookie after login.
- Using Flask sessions for auth makes traditional web workflows secure, simple, and familiar.
- Stateless JWTs are excellent for APIs or mobile clients, but they add significant complexity for browser-based user experiences, especially when you want easy login, logout, and session handling.
Using sessions in a browser is not bad—in fact, skipping sessions would hurt UX, security, and maintainability for web apps.
-
User Experience: Sessions let browsers manage login state automatically, so users don’t have to deal with tokens or repeated logins.
-
Security: Sessions protect against token leakage and common attacks (like XSS) better than storing JWTs in localStorage.
-
Maintainability: Server-side sessions make it easier to support logout, session expiry, and future security improvements without complex client logic.
What Happens When Session Cookie Has Expired?
-
User sends a request to a protected endpoint (e.g.,
/uploador/query-ui). -
Flask session no longer contains a valid
id_tokenbecause the cookie expired or was cleared. -
Inside
api_gateway.py, the functionrequire_login()runs:-
It looks for
id_tokenin the session. -
Finds none (or finds an expired token if you validate expiration).
-
-
Because no valid token is present,
require_login()returns a redirect response to/login. -
The user is redirected to the login page and must reauthenticate to get a new
id_tokenand session cookie. -
The OAuth2/OIDC flow starts over:
-
Generate new OIDC state.
-
Redirect to identity-backend’s authorize endpoint.
-
User logs in again.
-
New
id_tokenis issued and stored in a fresh session cookie.
-
Security & UX Notes:
-
Security:
Expired sessions prevent unauthorized access without valid tokens, enforcing fresh login. -
User Experience:
User must log in again, which can be disruptive but is necessary for security. -
Enhancements:
-
You could implement refresh tokens (currently not present), enabling seamless token renewal without full re-login.
-
Alternatively, extend session lifetime or show friendly "session expired" UI to inform the user.
-
In summary:
Expired cookie → no session token → redirect to login → fresh login → new session → access granted.
OAuth2/OIDC User Authentication Code
1. First Visit: User accesses http://localhost:5000
- Flask app in api_gateway.py handles this at @app.route("/") (the “home” page).
- Key line:
user = require_login()
if not isinstance(user, dict): return user - What does require_login() do?
- Checks if there’s an id_token (JWT) in the Flask session.
- If not, redirects to the login page (/login route).
1.1. No Session → Redirect to /login
- @app.route("/login") generates a new OIDC state and prepares OIDC parameters.
- Builds an authorization URL to the identity backend (OIDC provider):
/identity-backend/authorize?client_id=...&redirect_uri=...&response_type=code&scope=openid&state=... - Redirects the user to this URL.
- Logs info about this action (IP, UA, state, etc.).
1.2. User Sees Login Form (login.html)
- Rendered by identity-backend at the /authorize route.
- The form submits to /identity-backend/login (POST).
- User enters credentials (for MVP, it’s a hardcoded username and password in .env/config).
1.3. Successful Login → Authorization Code
- If creds are correct, identity-backend:
- Generates a one-time authorization code (UUID).
- Saves it in a SQLite table (with username, client, scope, timestamp).
- Redirects back to your API gateway’s /callback with
/callback?code=...&state=... - If login fails, shows an error.
1.4. API Gateway /callback: Code Exchange
- Handles the callback at @app.route("/callback").
- Checks that the state matches what was in session (CSRF protection).
- Exchanges the authorization code for an id_token by POSTing to /identity-backend/token:
- Sends client ID, client secret, code, and redirect URI.
- Receives a JWT id_token (with user claims, expiration, etc.).
JWT Verification and Session Storage
- Validates the JWT (issuer, audience, expiry, etc.).
- Stores the id_token in the user’s Flask session.
- Removes OIDC state.
- Extracts user info (sub, email, etc.).
- Logs login success.
1.5. User is Redirected to Home Page
- User now has a session (id_token in session).
- require_login() passes—so the home page loads.
- Shows:
- Jobs table (any files they or others have uploaded, depending on your access model).
- Logs table (recent platform activity).
- Navigation (Upload Docs, Query, Logs, Logout, etc.)
1.6. Now Authenticated, User Can:
- Upload files → triggers job queue, worker, parser.
- Ask questions using RAG.
- See logs.
- All actions are tracked/logged.
Great choice! Let’s deep dive into service-to-service security in your architecture, focusing on how your worker, parser, and logging services interact securely and how authorization is enforced between them.
Service-to-Service Security in Your System
1. Current Trust Model
-
Implicit Trust Inside Network:
Your microservices (worker, parser, logging) run on the same infrastructure or trusted network.
They do not currently authenticate requests between each other with JWTs or mutual TLS (mTLS).
Instead, they rely on network-level security (e.g., firewalls, private subnets). -
No JWT or API key checks:
Requests like:-
Worker → Parser
/parse -
Any service → Logging Service
/log
do not include explicit tokens or secrets.
-
-
Risk:
If any service endpoint is exposed publicly or compromised, malicious actors could submit fake jobs, spam logs, or extract data.
2. How Authorized Requests Are Currently Processed
-
API Gateway enforces user authentication (via JWTs in sessions) for all user-facing endpoints.
-
Microservices accept requests without validating a service token or client identity.
Example: In worker.py, the worker calls the parser via HTTP POST without any JWT or API key.
Similarly, logging calls are made with simple POST requests.
3. Gaps and Risks
-
No proof of identity between services.
-
No authorization scopes or roles enforced on microservice calls.
-
Potential for unauthorized access if services are exposed beyond trusted boundaries.
-
Difficult to audit or revoke compromised credentials because none are used.
4. Recommendations for Zero-Trust Expansion
-
Add JWT-based Service Authentication:
-
Issue service-to-service JWT tokens (using the same identity-backend or separate service identity).
-
Each service verifies JWT on incoming requests (signature, issuer, audience, scopes).
-
Tokens carry claims representing the calling service identity and permissions.
-
-
Use Mutual TLS (mTLS):
-
Secure HTTP with client certificates between services.
-
Prevent unauthorized clients at the network layer.
-
-
Enforce Authorization Policies:
-
Restrict actions by scopes/roles encoded in JWTs.
-
Implement fine-grained RBAC (Role-Based Access Control) per service endpoint.
-
-
Rotate Secrets and Tokens Regularly:
-
Automate token/key rotation to minimize risk if leaked.
-
-
Limit Service Exposure:
-
Keep internal APIs behind firewalls/VPCs.
-
Use API gateways or service meshes to control traffic flow.
-
5. Practical Next Steps in Your Codebase
-
Update microservices (worker, parser, logging) to accept and validate JWT bearer tokens.
-
Modify API gateway or identity-backend to issue short-lived service tokens.
-
Add middleware in Flask apps to verify incoming service tokens.
-
For initial MVP, you could start with shared API keys or secrets to authenticate services, then evolve to JWT.
Summary Table:
| Current State | Recommended Zero-Trust Practice |
|---|---|
| No service authentication | Service-issued JWTs validated by each service |
| Implicit network trust | mTLS or service mesh enforcing identity |
| No fine-grained authorization | Scoped tokens, RBAC enforced on service APIs |
| No secret/token rotation | Automated rotation and revocation |
Would you like me to help generate example code for JWT validation middleware in worker/parser or draft a token issuance flow for services next?
Comments
Post a Comment