Everyone who has ever integrated login "by hand" knows where it hurts: misconfigured OAuth2, leaking tokens, and a third-party IdP that charges per seat or removes a feature in the next release. We got tired of that and built Curupira.

Why on earth build your own IdP
Authentication is one of those things nobody wants to reinvent until they have to. Our use case was multi-tenant: multiple applications, each with its own users, roles, and rules. The ready-made options were either expensive per seat or locked us into decisions we did not control. Building your own IdP means taking on serious responsibility, but it gave us what was missing: full control over keys, tokens, and security policies. Curupira is an OAuth2 + OpenID Connect Identity Provider, production-ready, with the default issuer at auth.dwcorp.com.br and an MIT license.
Rust was not a hype-driven choice
Writing an IdP in Rust may look like showing off until you remember what an IdP does: it handles secrets, validates tokens, and runs on the critical path of every authenticated request. Here, memory safety and the absence of runtime surprises are worth the cost. The stack is Rust + Cargo, with PostgreSQL through SQLx—which means compile-time-checked queries and versioned migrations. If a query breaks, it breaks during the build, not at three in the morning in production.
Mandatory PKCE and zero security shortcuts
The choice that defines Curupira the most: PKCE is mandatory. It is not optional, and there is no flag to turn it off. The Authorization Code flow with PKCE S256 (RFC 7636) is used for everyone, whether the client is public or confidential. On top of that, we use Argon2id for passwords, CSRF protection, and API key protection for token and introspection endpoints. The project follows RFC 9700, the OAuth2 security BCP—in other words, current best practices, not what was considered acceptable in 2015. The idea is simple: make the secure path the only path.
True multi-tenancy: one RSA key per application
Multi-tenant is often just a marketing adjective. In Curupira, it is architecture: each application has its own RSA key pair. JWTs are signed with RS256, with key rotation. This means that compromising or rotating one application's key does not affect the others. On top of that, there is RBAC with application-specific roles and role groups, so you can model permissions without turning every tenant into a special case. OIDC discovery, ID tokens, UserInfo, introspection, and logout are all included.
FusionAuth-compatible, with three layers of logging
A pragmatic decision: Curupira was designed to be compatible with FusionAuth. This reduces friction for people coming from that ecosystem and gives us a familiar vocabulary instead of making up our own. Observability was not left out either—there are three logging layers: login attempts, event logs, and audit logs. When something goes wrong during authentication, it is possible to reconstruct what happened. Starting with v0.8, there is also an administrative dashboard built with Nuxt.js (Vue) and @nuxt/ui, because managing tenants and keys from the command line gets tiring quickly.
What is running today and what comes next
Let us be honest: Curupira is an evolving product. Phases 0 and 1—infrastructure and tenant management—are complete. Phases 2 through 5 are on the roadmap: application, user, and role management; logging and monitoring; MFA; email verification; and webhooks. It is a phased roadmap precisely because we would rather deliver each layer properly than push everything out at once and hope for the best.
Authentication is boring when it works and catastrophic when it does not. Building Curupira was a bet that having this critical piece under our control was worthwhile—with Rust, mandatory PKCE, and one key per tenant. So far, the bet is holding.



