Overview
Every product I had worked on rebuilt the same thing: signup, login, tokens, OAuth2. Each time slightly differently, each time with the same class of bugs.
This service is my answer. You deploy it once, self-host it with a single docker run, and talk to it over gRPC from any backend — business services stop touching credentials altogether.
What I built
- Token issuance and validation exposed over gRPC: consumer backends compile against a typed protobuf contract instead of re-implementing a fragile client each time.
- OAuth2 flows for third-party authentication.
- Account store on PostgreSQL — normalized schema, constraints enforced in the database rather than only in application code, migrations checked into the repo.
- Packaged as a single Docker image and configuration-complete through environment variables: self-hosting is a design constraint here, not a deployment afterthought.
- An HTTP surface published live for exploration and integration testing, while the proto file stays the actual contract.
Deep dive
2 min readEvery product I had worked on rebuilt the same thing: signup, login, tokens, OAuth2. Each time slightly differently, each time with the same class of bugs. This project is my answer — an auth service you deploy once, self-host with one docker run, and talk to over gRPC from any backend.
The problem
Authentication is the part of a system where "mostly correct" is not good enough, and also the part teams are most tempted to rush. I wanted a service that:
- issues and validates tokens for other services, so business backends never touch credentials;
- speaks OAuth2 for third-party flows;
- can be self-hosted by anyone — no vendor, no metered pricing, just a container and a Postgres.
Decision 1 — gRPC at the core
Auth is an internal, service-to-service concern. The consumers of this API are other backends, not browsers. gRPC gives me a typed contract (protobuf) that client services compile against, instead of a REST API where every consumer re-implements the same fragile client code. The HTTP surface you see in the live docs exists for exploration and integration testing; the contract is the proto file.
Decision 2 — Postgres as the account store
The account store is the part of an auth system you cannot get wrong. I keep it boring on purpose: a normalized Postgres schema, constraints in the database rather than only in application code, and migrations checked into the repo. Most of the difficult decisions in this project were schema decisions — which is true of most backend systems I've worked on.
Decision 3 — packaged for self-hosting from day one
"Self-hostable" is a design constraint, not a deployment afterthought. It forces the service to be configuration-complete through environment variables, to have no hidden dependencies, and to start from a single Docker image. That constraint kept the architecture honest: if it can't run on someone else's laptop, it's not done.
What I'd do differently
A case study without regrets is marketing. The honest list is in progress — ask me about token revocation.
Try it
- Live API docs: auth-service-go.onrender.com/docs (free-tier hosting — first load may take a few seconds to wake up)
- Source: github.com/MihajasoaAlain/auth-service-go