# obligator: an opinionated OpenID Connect server for self-hosters

> obligator is an MIT licensed Go OpenID Connect provider built for people who host their own services, with anonymous client authentication, multi-domain login, passwordless email and a flat-file or SQLite backend, all driven by a JSON config. It suits a self-hoster who wants a small identity server, and it is a poor fit for an enterprise that needs a documented, audited and admin-staffed identity platform.

**lastlogin-net/obligator** — Simple and opinionated OpenID Connect server designed for self-hosters

- Repository: https://github.com/lastlogin-net/obligator
- Stars: 842 · Forks: 25
- Language: Go
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/lastlogin-net-obligator

## An identity server for the person who runs the services

obligator is described as a relatively simple and opinionated OpenID Connect provider server designed for self-hosters.

The word opinionated is doing real work in that sentence. This is not a general purpose identity platform with a plugin architecture and an admin console. It is one author's implementation of one particular approach to the problem, and the README is explicit about the reasoning behind it.

The motivation section makes the position clear. There are lots of great open source OIDC servers out there, and the author built obligator because he needed a specific combination of features he did not find in any of the others. The list of those features is the map of the project: simple to deploy and manage with a static executable and either flat-file or SQLite storage, support for anonymous OAuth2 client auth, authentication to multiple domains at once, passwordless email login, runtime configuration through an API, forward auth, trusted headers, and upstream social login providers such as GitLab, GitHub and Google.

So the audience is someone who runs services and wants them all behind one login. A self-hoster with a dozen small applications, a static site, a wiki and a tool or two, who would rather not hand-register an OAuth2 client for each one and who wants to log in once and be logged in everywhere.

The project is MIT licensed and written in Go, with the last push to the repository on 2026-10-01. There is a Hacker News discussion linked from the README, and community support is on the IndieBits forums.

## Email is the identity and everything else is delegated

The design section states the overarching philosophy: identities are built on email. Email is not perfect, the README admits, but it is the globally unique federated identity that works today.

From that, the purpose follows. The purpose of obligator is to validate that a user controls an email address as simply as possible, and to communicate that to the application the user is attempting to log in to.

The validation itself has two routes. It can be done directly through SMTP, or delegated to upstream OIDC and some plain OAuth2 providers. That second route is what makes the GitLab, GitHub and Google support in the feature list work: rather than obligator sending a confirmation email to an address that already lives in an upstream identity system, it can ask the upstream provider whether the user controls that address.

The passwordless email login follows from the same principle. Since passwords are relatively difficult to use securely, the way to add an email identity is to send a confirmation code to the email address. There is no password to store, no password reset flow to get wrong, and no password database to breach.

The design consequence is worth stating plainly, because it is a real constraint rather than an implementation detail. obligator can only vouch for email addresses. It is not an identity provider for organisations with directory-backed identities, certificate based clients or hardware keys, and the upstream delegation route is what connects it to those worlds.

## Anonymous client auth removes the registration treadmill

The feature the README spends the most space on is anonymous OAuth2 client auth, and the problem it solves is one every self-hoster recognises.

Normally in OAuth2, and therefore OIDC, an app must pre-register with the provider. The README describes the friction this creates, especially when self-hosting an open source application: app developers are forced to either share a single client ID for all their users, sharing their client secret, which essentially makes it pointless, or each user must separately register their own instance.

Instead, any OAuth2 client can authenticate anonymously with an obligator instance, with the client_id equal to the domain of the client and the client_secret left blank.

Security is maintained by two rules. Only approved email addresses are permitted unless public is set to true in the config. And the client_id URI must be a prefix of the redirect_uri, with the client_id displayed to the user when consenting to the login.

The argument for the second rule is the strongest part of the README. It guarantees that the user approves the ID token being sent to the domain shown, and the author goes further, noting that this can actually be more secure than pre-registration, citing past attacks where users were tricked into authorizing apps because the pre-registered information looked convincing. By forcing the user to decide whether they trust the actual domain where the ID token will be sent, and by displaying no logo that can be faked, security is improved.

The README also compares this to OIDC Dynamic Client Registration, which is the official specification for achieving something similar. The problem with that route is that client apps must support dynamic client registration, and many do not, whereas anonymous auth requires no special features on the client side.

## Multi-domain login and the public demo instance

The multi-domain feature is explained with a question most people have asked. When you log in to Gmail on a new computer, you are also automatically logged in to YouTube, even though youtube.com has no access to the cookies of google.com.

The answer, as the README explains, is that logging in on accounts.google.com makes a quick redirect to youtube.com with a URL parameter to set up the cookies there. The author wants the same functionality for all the domains protected by his OIDC server, so he built it into obligator.

That is a small feature with a large blast radius, and it is worth thinking about before enabling it. One login sets a session across every domain you protect, which is the convenience, and also means a compromise of one of those domains can affect the others if the trust relationship is one way. The feature exists because the author wanted it, and the README is candid about the mechanism rather than dressing it up.

There is a public instance of obligator running at lastlogin.net, with a discovery document at the well-known openid-configuration path, and you can use it with any OIDC client. The README walks through testing it with openidconnect.net: enter the discovery document URL, use the discovery document to populate the fields, set the client_id to https://openidconnect.net/ since it is a prefix of the redirect_uri that site uses, leave or remove the client_secret, then save and start the flow.

The README also points at the official OpenID conformance suite as an excellent tool, which is the right place to look if you want to know how much of the specification the implementation actually covers.

## The API is a unix socket, unauthenticated and undocumented

The API section is the weakest part of the project, and the README is honest about it.

Currently the API is only offered through unix sockets. The stated reason is that this reduces the chance that it accidentally gets exposed, which is important because it is not authenticated in any way.

Then comes the sentence that should stop a self-hoster before they build anything on it: there is not any documentation, and the API is in flux, so refer to the source code for usage.

The example given assumes you ran the Docker command, and it is a single curl over the socket.

```bash
curl --unix-socket obligator_docker/obligator_api.sock dummy-domain/oauth2-providers
```

So the shape of the risk is clear. The runtime configuration feature, configurable at runtime with an API, is the answer to a chicken and egg problem: you need to change configuration, and changing it requires the server to be running. The answer is a local socket that any process on the machine can talk to.

On a single tenant machine that is a reasonable trade. On a shared host, where another user or another container can reach the socket file, it is not. The mitigation is not documented, and the reasonable reading of the README is that the socket directory needs to be protected by filesystem permissions and the host needs to be dedicated to the service.

One thing the repository layout does show is the scope of the codebase, and it is a pleasant surprise for a project of this size. The root directory holds separate files for each concern, including add_identity_email.go, add_identity_oauth2.go, add_identity_fedcm.go, add_identity_gaml.go, fedcm.go, indieauth.go, proxy.go, qr.go, jose.go, cluster.go and domains.go, alongside api.go, handler.go, oidc.go, oauth2.go, database.go and obligator.go. Several of those filenames point at capabilities the README does not describe in detail, including FedCM, IndieAuth, a proxy and QR code support.

## Running it: a JSON config file or a container

The running section offers two routes, and both are short.

The first is a config file passed with the -config option, containing JSON. The shape is an oauth2_providers array for the upstream providers and an smtp object for the mail path.

```json
{
  "oauth2_providers": [
    {
      "id": "google",
      "name": "Google",
      "uri": "https://accounts.google.com",
      "client_id": "<google oauth2 client_id>",
      "client_secret": "<google oauth2 client_secret>",
      "openid_connect": true
    }
  ],
  "smtp": {
    "server": "smtp.fastmail.com",
    "username": "<smtp-username>",
    "password": "<smtp-password>",
    "port": 587,
    "sender": "auth@example.com",
    "sender_name": "Example"
  }
}
```

Note that openid_connect is a per provider flag, which is what lets a plain OAuth2 provider such as a GitLab instance sit alongside a true OIDC one, and that the SMTP block carries the sender identity separately from the username, because the From address on a login email is what the user actually reads.

The second route is Docker, described as the easiest way to get started if you are already using it.

```bash
mkdir obligator_docker/
cp obligator_storage.json obligator_docker/

docker run --user $(id -u):$(id -g) --rm -it -v $PWD/obligator_docker:/data -v $PWD/obligator_docker:/api -p 1616:1616 anderspitman/obligator:latest -database-dir /data -api-socket-dir /api -domain example.com -port 1616
```

The two volume mounts are the storage directory and the API socket directory, and the same directory serves both, which is why the storage file is copied in first. Running with your own user and group IDs is what keeps the files in that directory owned by you rather than by root.

Static executables for various platforms are also available from the releases page, which is the route to take if you would rather not run a container.

## Beta releases, a small dependency set, one maintainer

The release history is short and the labels are consistent. All three listed releases are marked beta: 0.3.0 on 2024-08-26, 0.2.0 on 2024-04-02 and 0.1.0 on 2023-10-11.

So the project has three tagged versions, all of them prereleases, and the newest tag is more than two years old relative to the last push on 2026-10-01. If you are evaluating stability, the honest reading is that this is a project with a working implementation, a public instance, a demo flow and conformance suite support, but no stable release and a single author driving it.

The dependency list supports the impression of a compact, self-contained service. The Go module requires the JWT library jwx, sqlx and the SQLite driver go-sqlite3, the QR code generator skip2/go-qrcode, and an IP geolocation library, ip2location, which fits a login service that wants to record where a sign-in came from. The module targets Go 1.20.

The one dependency worth flagging is the SQLite driver, because go-sqlite3 is a cgo binding. A static executable built without cgo will not work as advertised, so the build setup for the static binaries is a detail to check rather than assume.

The MIT licence is the permissive one, with the LICENSE file in the repository root, and the project is published as a Docker image under the anderspitman namespace.

None of this makes the project unsuitable for a self-hoster, and the community support on the IndieBits forums suggests it is not abandoned. It does mean you should plan on reading the source, as the README itself instructs for the API, and on accepting that you are running someone else's single maintainer view of how an identity server should work.

## Conclusion

obligator suits a self-hoster who wants to run login for their own services and is tired of the OAuth2 client registration treadmill, since a client can present its own domain as the client_id with a blank secret and the user approves the destination at consent time. It does not suit an organisation that needs a documented administration API, a support commitment, or an identity product that can be pointed at by third parties without review. Before you deploy it, read the API section carefully, because the README states the API is unauthenticated, undocumented and in flux, and note that the newest release tag is 0.3.0 from 2024-08-26 while the last push to the repository was on 2026-10-01, so you are tracking a project whose releases lag its commits.

## FAQ

### What is obligator?

obligator is a relatively simple and opinionated OpenID Connect provider server written in Go and designed for self-hosters. Its philosophy is that identities are built on email, and it supports anonymous OAuth2 client auth, multi-domain authentication, passwordless email login and runtime configuration through an API.

### How does anonymous OAuth2 client authentication work in obligator?

Any OAuth2 client can authenticate with an obligator instance using the domain of the client as the client_id and a blank client_secret. Security comes from two rules: only approved email addresses are permitted unless public is set to true, and the client_id URI must be a prefix of the redirect_uri and is displayed to the user at consent time.

### How do I start obligator with Docker?

Create a directory, copy obligator_storage.json into it, and run the published container with your own user and group IDs, mounting the directory as both the database directory and the API socket directory and passing -domain and -port. The README calls Docker the easiest way to get started.

### Is the obligator API documented?

No. The README states that there is not any documentation, that the API is in flux, and that you should refer to the source code for usage. The API is only offered through unix sockets and is not authenticated in any way.

### Is there a public obligator instance I can test against?

Yes, there is a public instance at lastlogin.net with a discovery document at the well-known openid-configuration path, and the README walks through using it with openidconnect.net by setting the client_id to a prefix of that site's redirect_uri.

## Sources

- [Issues](https://github.com/lastlogin-net/obligator/issues)
- [lastlogin-net/obligator on GitHub](https://github.com/lastlogin-net/obligator)
- [License: MIT](https://github.com/lastlogin-net/obligator/blob/main/LICENSE)
- [README](https://github.com/lastlogin-net/obligator/blob/main/README.md)
- [Releases](https://github.com/lastlogin-net/obligator/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/lastlogin-net-obligator
