# ThunderID's Architecture section is an empty heading and its consent has a build flag

> An Apache-2.0 identity stack in Go with a TypeScript frontend, an MCP server for both securing agent tool calls and querying IAM, and a YAML, GitOps-shaped runtime. Its page has an Architecture heading with nothing under it, and the build exports a switch for running without consent.

**thunder-id/thunderid** — ThunderID is a high-performance, open-source identity stack designed for developers to secure and manage access for humans, AI agents, and machines through fully composable identity flows.

- Repository: https://github.com/thunder-id/thunderid
- Website: https://thunderid.dev
- Stars: 587 · Forks: 387
- Language: Go
- License: Apache-2.0
- Published: 2026-09-10 · Updated: 2026-09-10 · Language: en
- Canonical page: https://hysenlabs.com/projects/thunder-id-thunderid

## The Architecture heading has nothing under it

The page has a section order worth reading literally. It introduces the project, gives a getting-started set of three guides, then reaches a heading reading Architecture, and the next heading is Features. Nothing sits between them.

An `ARCHITECTURE.md` does exist at the root of the repository, so the content is presumably there rather than missing. But the page that a first-time reader lands on promises an architecture and then describes features instead, which is the kind of gap that costs someone an afternoon.

The features list is substantial enough to compensate. Humans, AI agents and machines are all first-class identity types, with hierarchical organisational units and groups. Authorization is hierarchical resources with derived permissions and role-based access across users, agents and applications. Identity management adds journeys for login, registration and recovery, orchestrated by more than twenty built-in executors covering password, passkey, one-time passcode, social login and consent, orchestrated either server-side or in the application, with a themeable end-user interface.

The developer surface is a console, REST APIs and SDKs, and the runtime is described as immutable, with YAML resource definitions for every entity and GitOps as the intended deployment model.

## Go in the backend, a Node workspace on top, version 0.0.0 in the manifest

The repository's recorded primary language is Go, and the Dockerfile confirms it by compiling a Go binary. Around that sits a Node and TypeScript workspace: a root `package.json` named for the root package, marked private, at version 0.0.0, with a pnpm lockfile, a workspace definition, a Turbo configuration and an `.nvmrc`.

That version is a placeholder. The real one lives in a `version.txt` at the root, and the Makefile reads it with a shell substitution into a `VERSION` variable alongside a binary name and a product name. Releases are v1.0.0 and v1.0.1, after a v1.0.0 release candidate. So the number a package manager sees and the number a release names are different values maintained in different files.

The build scripts are orchestration rather than compilation. Build, lint, test and typecheck all delegate to Turbo with filters, and there are targeted variants for docs and frontend. The test script deliberately excludes two directories with filters and caps concurrency at three; the lint script excludes the tests directory as well, which is a choice worth knowing about if you are looking for a reason a file is not linted.

End-to-end testing has its own preparation step that installs Playwright with its system dependencies before the suite runs.

Tooling constraints are declared through a `devEngines` block for the package manager rather than the older engines field, and every development dependency points at a shared catalog rather than carrying its own version.

## WITHOUT_CONSENT is exported by the build

One line in the Makefile sits above the targets and is worth stopping on: `export WITHOUT_CONSENT ?= false`.

Consent is one of the product's stated features. Agents get consent-aware access, authorization includes consent management with a user-facing review screen, and one of the twenty-odd journey executors is consent itself. So a build-time variable that turns consent off is not an unrelated flag; it is a switch on a feature the product advertises in three places.

The default is false, which means consent handling is on unless a caller overrides it, and the `?=` form means an environment variable wins over the Makefile. That is a reasonable arrangement for a self-hosted product where a specific deployment wants to skip a flow, and it is also the kind of switch that should be documented where the consent behaviour is described.

The visible page does not say what the flag changes. Nothing in the README, the feature list or the consent wording references it, so the honest position is that the switch exists in the build and its effect is not stated where the feature is marketed.

## Three linter binaries, because a shared one refuses to lint its own module

The Makefile installs golangci-lint three times, into three separate binary directories, and the comment above those targets explains why in more detail than most projects give for a build step.

Each tool under `tools/` is its own Go module with its own Go directive, and `go install` compiles a linter against whatever Go is on the PATH at the time. golangci-lint refuses to run when the Go it was built with is older than the module it is linting. So rather than sharing the backend's bin directory, each tool gets its own, on the reasoning that a binary built by the backend's job would carry the backend's Go version and then refuse to lint a module built for a newer one.

The three locations are the backend's own tools directory, a CLI tools directory, and an i18n extractor directory, each with its own `bin/tools` subdirectory and its own copy of the linter. The linter version is pinned, as is the mockery version used for mocks.

The same file reads the product version by shelling out to `cat` on the version file, and one target simply makes the build script executable as a preparation step, which is a small sign of a repository where shell does the orchestration that a task runner does elsewhere.

## The image rewrites its own deployment config and injects a localhost URL

The container build does not just copy files. It rewrites the deployment configuration with two `sed` invocations: the first replaces a hostname of `localhost` with `0.0.0.0`, and the second inserts a `public_url` of `https://localhost:8090` directly after that line.

So the image ships a configuration where the service binds on all interfaces and simultaneously advertises its public address as HTTPS on localhost at port 8090. For a local trial that is harmless and possibly convenient; for anything exposed, that value has to be found and edited, and it will be in an issuer or redirect URI somewhere in an OIDC flow.

Two other details from the same file are worth noting. Security material is explicitly not baked into the image, with TLS, JWT and AES keys generated per deployment by a setup step, which is the right call. And the build stage branches on the target architecture to choose between amd64 and arm64 when invoking the build script, so one Dockerfile produces both.

There is also a second Dockerfile. The product image notes that the release pipeline uses a different one under the GitHub workflows directory and asks that the runtime stages be kept in sync, which is a manual step with a comment as its only safeguard.

## MCP shows up twice, as a thing to secure and as a way to reach IAM

The getting-started section offers three guided paths rather than one installation walkthrough: securing a business-to-consumer application, securing AI agents, and securing MCP. The third is a distinct guide with its own authorisation page, which means the project treats an MCP server as something that needs protecting.

The feature list then uses MCP in the opposite direction, listing an MCP server for managing and querying IAM from AI agents. So the same protocol appears as a workload under the product's protection and as a control surface over the product itself.

That pairing is the most interesting design claim in the feature list, because it forces one question to be answered twice: an agent calling IAM through MCP and an agent calling an MCP server that depends on IAM are the same traffic in different clothes. An implementation that treats them as separate features would be inconsistent, and the fact that both are documented as first-class suggests the authors saw the same thing.

The agent story is stated more broadly than the protocol. Agents are first-class identities with delegated authority, consent-aware access and traceability, and the project says it aims to expose IAM capabilities through interfaces agents can use safely and programmatically, with support for issuing verifiable credentials to agents. Post-quantum work is described as crypto-agile rather than finished, covering key types, signing methods and token protection across key management, credential issuance, assertions and service-to-service communication.

## Conformance is a badge, and the charter is a PDF at the root

The conformance section contains exactly one thing: a link to a GitHub Actions workflow named for OIDC conformance testing. No results, no report, no list of profiles.

That is how conformance claims are usually evidenced, a link to the pipeline that runs the test, and it is a reasonable thing to publish. It is not the same as publishing the outcome, so a reader who needs to know whether the implementation passes a particular conformance profile has to go and look. The standards in scope are broad: OAuth 2.1 and OpenID Connect with pushed authorisation requests and PKCE, verifiable credentials through OpenID4VCI for issuance and OpenID4VP for verification, WebAuthn and passkeys, and federation with Google, Microsoft, GitHub or any OIDC or SAML provider.

The root of the repository tells a similar story about governance. There is a technical charter as a PDF in the filename, dated in June 2026, sitting next to `MAINTAINERS.md`, `CONTRIBUTING.md`, `ADOPTERS.md`, `SECURITY.md` and an `AGENTS.md`, plus a `CLAUDE.md` and a `.agent/` directory. Documentation is split into a public `docs/` directory and a `docs-internals/` one, and there is a prose linter configured with Vale alongside a spell checker dictionary and a coderabbit configuration.

The last push is dated 2026-10-01, five weeks after the v1.0.1 release on 2026-08-25, and the repository is not archived.

## Conclusion

Use it if you need one identity layer covering people, agents and machines, and if you can accept that decentralized identity support is where this project is still maturing, since the relying-party side is described as an aim rather than a shipped path. Two things to inspect before deploying. The Makefile exports `WITHOUT_CONSENT`, so confirm what a build with that set actually changes for your consent flows. And the conformance claim is a workflow badge with no visible results, so run your own OIDC conformance check rather than trusting the link.

## FAQ

### What is ThunderID?

An Apache-2.0 open-source identity and access management stack written in Go, with a TypeScript frontend, covering humans, AI agents and machines as first-class identity types. It supports OAuth 2.1 and OpenID Connect, verifiable credentials, passkeys and identity provider federation.

### Does ThunderID support MCP?

In two directions. One getting-started guide is about securing an MCP server with authorisation, and the feature list includes an MCP server for managing and querying IAM from AI agents. So MCP appears both as a workload to protect and as a control surface over the identity system.

### What does the WITHOUT_CONSENT flag do in ThunderID?

The Makefile exports it with a default of false, overridable from the environment, so consent handling is on unless a build sets otherwise. Consent is advertised as a feature for both users and agents, and the visible documentation does not say what the flag changes.

### How is ThunderID versioned?

The release tags are v1.0.1 and v1.0.0 after a release candidate, while the root package manifest is a private workspace at version 0.0.0 and the Makefile reads the real version by shelling out to cat on a version.txt file at the root.

### Is ThunderID OIDC conformant?

The page links a conformance test workflow under GitHub Actions and publishes no results from it. Run the profile you need yourself rather than relying on the badge, since the page shows no outcome.

### How is ThunderID deployed?

Through a lightweight containerised runtime with YAML resource definitions for every entity and an immutable runtime, intended for GitOps. The container build rewrites the deployment configuration to bind on all interfaces and injects a public URL, and TLS, JWT and AES keys are generated per deployment rather than baked into the image.

## Sources

- [License: Apache-2.0](https://github.com/thunder-id/thunderid/blob/main/LICENSE)
- [Project website](https://thunderid.dev)
- [README](https://github.com/thunder-id/thunderid/blob/main/README.md)
- [Releases](https://github.com/thunder-id/thunderid/releases)
- [thunder-id/thunderid on GitHub](https://github.com/thunder-id/thunderid)

---

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