Ralvapass · accounts for every app you run
A verifier can check a token. It can never make one.
Ralvapass signs tokens with a private key and publishes only the public one. Every other service checks them locally, so a stolen verifier gives an attacker nothing to mint with.
Forge a token
Ralvapass. The only place a token is made.
kept by the service, never published
eyJhbGciOiJFZERTQSIs….eyJpc3MiOiJpZGVudGl0eSIs….kQe3…
// header and payload, decoded
{ "alg": "EdDSA", "kid": "sample" }
{ "iss": "identity", "sub": "usr_sample", "aud": ["app-a"], "exp": … }What an attacker without the private key gets
- Nothing changedAccepted
- Audience changed to app-bRejected: signature invalid
- Expiry extended by a dayRejected: signature invalid
- One bit of the signature flippedRejected: signature invalid
- Signed legitimately for app-bRejected: audience is not app-a
{"kty":"OKP","crv":"Ed25519","kid":"sample","x":"…"}
- Signature matches the public key
- Issuer is
identity - Not expired
- Audience is
app-a
Accepted. The verifier never called the issuer.
The service signs with EdDSA and a kid, and a verifier fetches the public key from the JWKS (cacheable for 300 s). The issuer identity and the 15-minute lifetime are the service’s defaults; the keys, the application codes and the subject are sample.
The spec
One identity service, and every figure counted from its source.
8services
RPC services
7 for accounts, tokens and management, 1 for the operator console. Served over Connect and native gRPC.
real contracts v0.33.0 ·
^service91RPCs
Calls in the contract
68 across the 7 account and management services, 23 in the operator console’s.
real contracts v0.33.0 ·
^\s*rpc3transports
One port, three doors
Connect, native gRPC and REST, all over the same service. One conformance suite runs every scenario on each.
real
identity/transport/*55migrations
In the identity library
The service wraps the library and runs its migrations at startup. It adds no tables of its own for pools.
real identity v3.3.0 ·
migrations/*.sql15min
Access-token lifetime
The default. Verifiers check the signature locally, so a signed-in user carries on if the service is briefly down.
real
defaultAccessTTL300s
Public-key cache
The JWKS is served with a five-minute cache, and verifiers fetch it only for a key id they have not seen.
real
jwksCacheControl14REST paths
Public REST surface
The surface for browsers, the CLI and personal access tokens, described by one OpenAPI file.
real contracts v0.33.0 ·
paths:12releases
Tagged so far
The latest is v0.5.0-rc.1. Releases are cut by the owner, never by CI alone.
real
git tag
Every cell was counted from the source when this page was built. The build compares them with what the copy says and stops if they differ.
How it is counted
If the code changes, the page refuses to build.
A script reads the contracts and the library, writes the figures to a file that git ignores, and compares them with what the page claims. A drift is a failed build, not a stale number.
$ pnpm build gen-data counting from contracts v0.33.0 services 8 page says 8 ok rpcs 91 page says 91 ok migrations 55 page says 55 ok after a new proto lands, with the page unchanged: rpcs 93 page says 91 drift build failed: 1 claim no longer matches the source
How apps trust it
One token, checked everywhere, minted in one place.
A service gets its own token from Ralvapass, and every other service verifies it with public keys. Scroll and the diagram follows.
A service asks for a token
Its backend authenticates as a service account:
POST /oauth/tokenwithgrant_type=client_credentials, credentials by HTTP Basic or in the form body. No secret is shared with any other service.Ralvapass signs one
An EdDSA access token. The header names the signing key (
kid); the audience is the application’s code,app-a; the lifetime is 15 minutes by default.The service calls app-a
It sends the token as a bearer. app-a’s backend verifies it locally, with a public key: it never asks Ralvapass whether the caller is allowed.
Only for a key it has not seen, it fetches the JWKS
GET /.well-known/jwks.json, served with a 300-second cache. A verifier holds public keys only, so it can check a token and cannot make one.app-a accepts the call
Signature, issuer, expiry and audience all pass, so the request goes through. This is the figure above, with the wire drawn.
A service gets a token from Ralvapass and calls app-a, which verifies it locally. The five steps are listed beside this diagram.
Pools and applications
Who can sign in where is one setting, not three codebases.
A pool is a set of users sharing credentials and an email namespace. Each application selects exactly one pool. The three layouts below are the same mechanism.
- Layout 1Isolated
Isolated
A pool selected by exactly one application. Each app keeps its own people.
- Layout 2Shared group
Shared group
Two applications select one pool, so they share accounts; a third keeps its own.
- Layout 3One global pool
One global pool
Every application selects the same pool. Optional; nothing special about it.
- 01
One pool per application, as a hard rule.Two pools behind one login would need an email-ambiguity rule. If two groups must reach one app, they share a pool.
- 02
Email is unique within a pool.So an application plus an email resolves to at most one user. The same address in two pools is two separate accounts.
- 03
Being in the pool is not access.An application can be open to its pool or invite-only, with a membership for each user.
- 04
Repointing is explicit.Moving an application to another pool is an operation of its own, and it drops the application’s memberships.
The rules are the service’s. Pool and application names are sample.
What the token says
Roles stay in the service. They never ride in the token.
A token says who you are and which application it is for. What you may do is looked up server-side, so a role change takes effect without waiting for a token to expire.
- sub · uid · aud
Who, and for which application. An application that sees an
audother than its own rejects the token. Try “Change the audience” above. - iss · iat · exp · jti
Who signed it, and for how long. The issuer is
identityby default and the lifetime 15 minutes. - roles and permissions
Resolved server-side, never embedded. Each application has its own role catalog; backends ask the access service who holds what.
- resource rules
Stay in your application. The service never learns what a profile or an invoice is.
{ "keys": [ { "kty": "OKP", "crv": "Ed25519", "kid": "sample", "x": "…" } ] }If something goes wrong
Pull one thing. See exactly what moves.
Central accounts are only worth it if the damage is predictable. Pick an event and read what each part does next. Application names are sample.
- ralvapassuntouched
Nothing to change: the token was signed correctly.
- app-ausable
Accepts it until it expires, 15 minutes by default.
- app-buntouched
Rejects it: the audience is app-a.
It works until it expires (15 minutes by default) and only against the application it names. An optional revocation list can end it sooner.
| If this happens | What follows |
|---|---|
| app-a’s backend is breached | A verifier holds public keys only, so a breach there cannot mint a token. |
| An access token is stolen | It works until it expires (15 minutes by default) and only against the application it names. An optional revocation list can end it sooner. |
| A service-account secret leaks | Revoke the account and the next call fails: it is looked up again on every call. Until then the damage stays inside its own pool. |
| The database is read | A service-account secret is shown once and kept as a digest, so what is stored cannot be replayed as a credential. |
| Ralvapass is down | Nobody new signs in, but signed-in users carry on until their token expires (15 minutes by default): verifiers check against cached public keys. |
The trade-off, said plainly
One more service that everything depends on.
You trade a copy of accounts in every app for one dependency. If Ralvapass is down, nobody new signs in or refreshes. That is the cost, and here is what limits it.
0
new sign-ins while it is down
No sign-in, no refresh and no new service tokens until it is back.
15min
issued tokens keep working
Verifiers check against cached public keys, so signed-in users carry on until their access token expires (the default lifetime).
1
more service to run
With its own database, and 55 migrations applied at startup.
In return you stop carrying a copy of users, passwords and sessions in every app, a shared signing secret, and one role catalog per application kept in sync by hand.
Surfaces
Three doors, one service.
Browsers and typed clients
Connect, gRPC and gRPC-Web from one handler.
Service to service
Native gRPC from the same proto, built and tested from day one as insurance.
The public identity surface
Errors are RFC 9457 problem+json. The token endpoint speaks plain OAuth.
A behaviour that passes over one door and fails over another fails the build.
Questions
Short answers.
Do browsers call it?
Yes. REST is the public surface for browsers, the command line and personal access tokens. Backends use the RPC doors with a service account.
What happens when the service is down?
Users already signed in carry on: verifiers check the signature locally and cache the public key. Their access tokens last 15 minutes by default, after which refresh and new sign-ins wait for the service.
Can two applications share an account?
Yes, by selecting the same pool. Accounts belong to a pool, and an application selects exactly one.
Where is the code?
In a private repository. The figures on this page are counted from it, and the contracts and the identity library it uses, when the page is built.