๐ TRusTY: Building a Rust OIDC Server with FAPI 2.0
After exploring Rust for web development, I wanted to go further by tackling an ambitious technical challenge: building a complete OpenID Connect (OIDC) provider, compliant with the FAPI 2.0 security specifications. Not just an academic prototype - a real authentication server with the most demanding security standards from the financial sector.
๐ฏ Why Build an OIDC Server in Rust?
The origin: a team challenge
It all started with a challenge launched within my former DevOps team, responsible for the SSO of a major French bank. Between two sprints, we asked ourselves: “What if we built our own OIDC server from scratch in Rust?” A stimulating technical challenge combining technological exploration, infrastructure cost optimization, and complete stack mastery.
Field observations
In the Access Management world, we typically use established solutions: Keycloak, Auth0, Okta, or ForgeRock. These tools are mature, battle-tested in production, and cover a wide spectrum of use cases. However, after several years operating them in production, a few friction points emerged:
โข Configuration complexity: Hundreds of parameters, rich but sometimes heavy interfaces - the learning curve can be significant
โข Resource consumption: Some Java-based solutions can require 500 MB to 1 GB RAM per instance - a non-negligible cost in cloud environments multiplied by instances
โข Flow opacity: Understanding what’s happening under the hood during an authentication failure can require debug time
โข Extensibility: Customization via plugins/SPIs is powerful but requires mastering internal APIs
โข Vendor lock-in: Portability between solutions can prove complex depending on features used
The idea behind TRusTY? Take back control by building a modern, performant, and understandable OIDC server. A tool where every line of code is mastered, where flows are transparent. Rust provides exactly what’s needed: memory safety, native performance, and a mature web ecosystem with Axum.
๐ Architecture: DDD + Clean Architecture
Layered separation
Rather than piling code into monolithic controllers, I structured the project following Domain-Driven Design and Clean Architecture principles:
| |
Why this organization?
โ
Isolated tests: Business logic testable without starting the server
โ
Maintainability: Clear responsibilities, loose coupling
โ
Scalability: Easy to add PostgreSQL, Redis, or other backends
โ
Understanding: Architecture reflects OIDC business concepts
Domain entities
The system’s core relies on well-defined business entities:
User: Represents a user with their credentials (password hashed via bcrypt) and claimsClient: Registered client application (redirect_uris, JWKS, auth methods…)Session: User session with configurable lifetimeAuthorizationCode: Ephemeral code (90s TTL) for Authorization Code Flow exchangeAuthorizationSession: Authorization request context (state, nonce, PKCE…)
Each entity encapsulates its own business logic and validation rules. For example, AuthorizationCode automatically verifies:
- Expiration (90 seconds)
- Single use (no reuse)
- Binding to the correct client
- PKCE code_verifier validation
๐ FAPI 2.0: Financial-grade Security
Why FAPI 2.0?
FAPI (Financial-grade API) is a security profile developed by the OpenID Foundation to meet banking and financial sector requirements. FAPI 2.0 (released in 2023) goes even further with robust anti-theft and anti-replay mechanisms.
๐ Full specification: FAPI 2.0 Security Profile
Key implemented features:
| Feature | RFC/Spec | Purpose |
|---|---|---|
| PKCE (S256) | RFC 7636 | Prevent authorization code interception |
| PAR | RFC 9126 | Protect authorization parameters (server-side pre-registration) |
| private_key_jwt | RFC 7523 | Asymmetric client authentication (no shared secrets) |
| DPoP | RFC 9449 | Token binding to cryptographic key (anti-token theft) |
| RP-Initiated Logout | OIDC Logout | Client-initiated logout |
| Token Revocation | RFC 7009 | Explicit token revocation |
| Token Introspection | RFC 7662 | Token validation by Resource Servers |
DPoP in detail: the real innovation
The classic problem: Bearer access tokens are like cash - whoever has them can use them. If an attacker intercepts the token (man-in-the-middle, malware, XSS…), they can use it freely until expiration.
The DPoP solution (Demonstrating Proof-of-Possession):
- The client generates an ephemeral RSA key pair at session start
- With each token request, it signs a cryptographic proof (JWT) with its private key
- The server binds the access token to the public key fingerprint (
cnf.jktin token) - To use the token, the client must prove possession of the private key with each API call
Result: Even if an attacker steals the token, they can’t use it without the corresponding private key. The token becomes unusable out of context.
| |
PAR: Pushed Authorization Requests
Another important innovation: PAR (RFC 9126) allows pre-registering authorization parameters server-side before redirecting the user.
Classic flow (risky):
| |
๐ Parameters visible in URL, modifiable by attacker
PAR flow (secure):
| |
๐ Parameters protected server-side, short and secure URL
Advantages:
- Guaranteed parameter integrity
- No URL length limit
- Automatic expiration (90 seconds)
๐ ๏ธ Tech stack and implementation choices
Why Axum for the server?
After testing several Rust web frameworks (Actix-web, Rocket, Warp), I chose Axum for several reasons:
โ
Ergonomics: Elegant API with typed extractors, expressive routing
โ
Performance: Built on Tokio and Tower, production-optimized
โ
Ecosystem: Compatible with entire Tower ecosystem (middleware, rate limiting…)
โ
Type-safety: Compilation guarantees route and handler consistency
โ
Maintenance: Developed by Tokio team, long-term support assured
SQLite: pragmatic choice
For the MVP, I opted for SQLite over PostgreSQL/MySQL:
โ
Zero-configuration: No server to manage, embedded database
โ
Portable: Single file, easy to backup and migrate
โ
Sufficient performance: Up to 100k req/s reads, more than enough to start
โ
Dev ease: Fast tests, no Docker required
PostgreSQL migration planned: The Repository Pattern architecture allows easy switching to PostgreSQL for production (high availability, replication…).
Key dependencies
| |
๐ Implemented flows: full demonstration
1. Standard OIDC Flow (Authorization Code + PKCE)
The classic flow for a web application:
| |
2. FAPI 2.0 Flow with PAR + DPoP
The secure flow for critical applications:
| |
Note: Each API call with the token requires a new signed DPoP proof. Impossible to reuse an old proof (anti-replay protection via jti + temporal verification).
๐ก Demo client: testing in real conditions
To validate the implementation, I developed a single Python Flask client offering two authentication modes selectable from the home page:
Standard OIDC Mode
- Classic Authorization Code Flow
- PKCE S256
client_secret_postauthentication- Flask session for state management
- Test account: standard user
Secure FAPI 2.0 Mode
- Complete PAR flow
private_key_jwtauthentication with RSA keys- DPoP proof generation on each request
- Ephemeral DPoP keys per session
- Test account: FAPI user
๐ฏ Interactive demo available: I deployed the server and client on Railway - a great service offering native GitHub integration that allowed me to host the MVP for free for 1 month with their base plan. Perfect for getting started quickly without complex infrastructure!
Available environments:
How to test: Go to the home page, choose your mode (Standard or FAPI 2.0), and follow the complete flow. You’ll see generated tokens, DPoP proofs (in FAPI mode), and can inspect JWT claims.
๐ Comprehensive technical documentation
The project includes detailed documentation. While waiting for the GitHub repository opening for v1.0 (H1 2026), technical documentation is hosted directly on this blog:
Architecture & Stack
- Complete DDD structure (Domain, Application, Infrastructure, Presentation)
- Technology stack with justifications
- SQLite database schemas
- Docker configuration and deployment
OIDC & FAPI 2.0 Flows
- Mermaid diagrams of all flows
- Request/response examples
- DPoP and PAR validation details
- Error handling and edge cases
Standards compliance
| Specification | Status | Compliance level |
|---|---|---|
| OpenID Connect Core 1.0 | โ Implemented | Complete Authorization Code Flow |
| OAuth 2.0 PKCE (RFC 7636) | โ Implemented | S256 only (plain rejected) |
| PAR (RFC 9126) | โ Implemented | 90s TTL, in-memory storage |
| private_key_jwt (RFC 7523) | โ Implemented | RS256, JWKS validation |
| DPoP (RFC 9449) | โ Implemented | jkt binding, htm/htu/ath validation |
| FAPI 2.0 Security Profile | โ Implemented | MVP subset (no Hybrid Flow) |
| RP-Initiated Logout | โ Implemented | With user confirmation |
| Token Revocation (RFC 7009) | โ Implemented | Access + Refresh tokens |
| Token Introspection (RFC 7662) | โ Implemented | Validation for Resource Servers |
๐ฎ Roadmap: Next steps
Version 0.9.0 (Q1 2026)
- ๐ Refresh Token Flow: Token renewal without re-authentication
- ๐ Full SAML Support: Complete SAML 2.0 protocol implementation (IdP and SP)
- ๐ PostgreSQL Backend: Migration for high availability
- ๐งช Conformance Testing: OpenID Certification test suite
- ๐ Advanced Telemetry: Prometheus metrics, distributed tracing
Version 1.0.0 (Q2-Q3 2026) - Ideas under consideration
Priority 1 - Certification and tooling:
- โ OpenID Certification: Official compliance
- ๐๏ธ Admin Console: Management interface for OIDC provider settings and IdP/SP SAML configuration
Priority 2 - Authentication journeys:
- ๐ถ Authentication Journeys: First baseline implementation with standard modules (to be determined - MFA, conditional access, step-up auth…)
To explore based on priorities:
- ๐ WebAuthn/FIDO2: Passwordless authentication
- ๐ฑ CIBA (Client Initiated Backchannel Authentication): For mobile
โ ๏ธ Note: These features are ideas under consideration and not yet fully committed. Development happens with my available resources and time (I have a day job! ๐ ), so the roadmap may evolve based on feedback and priorities.
Exploratory features
- Internal/External client distinction: Differentiated management based on client type (not to be confused with Device Flow)
- JAR (JWT Secured Authorization Request - RFC 9101): Encapsulation of authorization parameters in a signed JWT (alternative/complement to PAR)
- mTLS (Mutual TLS): Mutual authentication at TLS level for highly secure clients
- Rich Authorization Requests (RAR - RFC 9396): Allows requesting fine-grained and structured authorizations beyond simple scopes. Example: instead of
scope=read, request{"type":"account","actions":["read","transfer"],"limits":{"max_amount":1000}}. Very useful for banking APIs where authorizations are complex.
๐ค Need feedback from Access Management experts
If you work in the Identity & Access Management field, your feedback would be valuable:
๐ Particular attention points:
- FAPI 2.0 flow compliance: Are the DPoP implementations (proof generation/validation, jkt binding) and PAR (storage, TTL, client_assertion validation) compliant with specs? Are there uncovered edge cases?
- Error handling and edge cases: Are OAuth/OIDC error codes appropriate? How do you handle timing attacks, replays, expired codes in your implementations?
- Token generation/storage security: JWTs are RS256-signed with key rotation. Is authorization code and refresh token storage in SQLite secure enough for an MVP? What are your production practices?
- Performance and scalability: With SQLite and in-memory PAR storage, the system is limited to a single node. What are recommended patterns for migrating to distributed PostgreSQL + Redis without full refactoring?
- Interoperability: Have you tested integration with third-party Resource Servers (Spring Boot, .NET, Node.js)? What problems do you typically encounter with ID tokens and DPoP access tokens?
๐ฌ How to submit your feedback:
For now, the project isn’t publicly open yet (planned for v1.0 in H1 2026). Meanwhile, you can send me your feedback, questions, or recommendations by email:
๐ง Email: lostyzen@gmail.com
๐ PGP Key: Download from keys.openpgp.org
๐ Fingerprint: 0205 9854 864D EE62 C2BB 455F D825 3521 EDD1 630B
Feel free to:
- Test the demos (DEV/PROD) and report bugs/inconsistencies
- Challenge the technical documentation
- Propose specific business use cases from your sector
- Report deviations from OpenID/FAPI specifications
๐ Resources and useful links
Official specifications
- OpenID Connect Core 1.0
- OAuth 2.0 PKCE (RFC 7636)
- Pushed Authorization Requests (RFC 9126)
- JWT Client Authentication (RFC 7523)
- DPoP (RFC 9449)
- FAPI 2.0 Security Profile
TRusTY Project
- ๐งช DEV Demo: Development version with logs
- ๐ PROD Demo: Stable production version
- ๐ Technical Documentation
- ๐ OIDC Flow Documentation
- ๐ GitHub Repository: Opening planned with v1.0 (H1 2026)
๐ Conclusion
Building an OIDC server from scratch in Rust is a serious technical challenge. But TRusTY is above all the result of nearly 10 years of experience in Access Management, gained particularly in the banking sector where security and compliance requirements are especially high. This expertise, I now make available to my clients to support them with their authentication and authorization challenges.
TRusTY isn’t production-ready yet (it’s an MVP!), but it already demonstrates that it’s possible to build a performant, understandable OIDC server compliant with the most demanding standards, based on deep knowledge of real-world challenges.
What you can do right now:
- Test the demos (DEV/PROD) to see flows in action
- Browse the detailed technical documentation hosted on this blog
- Share your feedback and use cases by email
What’s next? Continue implementing missing features, harden security, improve performance… and why not aim for official OpenID certification for v1.0!
Questions? Suggestions? Don’t hesitate to contact me or comment below. Community exchanges are essential for evolving this kind of project.
