# tsidp: an OIDC identity provider that runs on your tailnet

> tsidp turns Tailscale identities into an OpenID Connect issuer, so services on your tailnet can authenticate users without a hosted IdP. It is marked experimental, ships as a container, and gates its admin endpoints behind a Tailscale application capability grant.

**tailscale/tsidp** — A simple OIDC / OAuth Identity Provider (IdP) server for your tailnet.

- Repository: https://github.com/tailscale/tsidp
- Stars: 674 · Forks: 65
- Language: Go
- License: BSD-3-Clause
- Published: 2026-09-10 · Updated: 2026-09-10 · Language: en
- Canonical page: https://hysenlabs.com/projects/tailscale-tsidp

## The gap tsidp fills between a tailnet and OIDC clients

Most teams that run Tailscale already have an identity story: users log in with Google, GitHub, Microsoft, or a self-hosted IdP, and the tailnet reflects that. The problem appears one layer up. An internal Grafana, a self-hosted Git forge, an MCP server, or any application that speaks OpenID Connect expects an issuer URL, a discovery document, and a JWKS endpoint. Tailscale is a network, not an OIDC issuer, so the usual answer is to bolt on Keycloak or Auth0 and map users twice.

tsidp closes that gap by acting as the issuer itself. According to the README, it is an OIDC and OAuth identity provider server that integrates with your Tailscale network, letting you use Tailscale identities for authentication into OIDC-capable applications and for authenticated MCP client and server connections. The audience is narrow and specific: engineers who run a tailnet with MagicDNS and HTTPS enabled, who can create a Tailscale authentication key, and who are comfortable granting an application capability. If your users are not on the tailnet, tsidp has nothing to authenticate.

## How tsidp issues tokens: tsnet node, capability grants, STS

tsidp does not run beside your tailnet, it joins it. The container embeds tsnet, the Tailscale library, so the process registers itself as a node with the hostname you configure. The README states that -hostname becomes <hostname>.your-tailnet.ts.net, which is why MagicDNS and HTTPS are prerequisites: the issuer URL is a tailnet DNS name with a certificate the node provisions itself.

State lives in a directory. The -dir flag sets where tsnet and tsidp state is saved, and the compose example maps TS_STATE_DIR=/data onto a named volume. Lose that volume and the node re-registers with a new identity.

Access is not open by default. The README is explicit that access to the admin UI and the dynamic client registration endpoints is denied by default, and that you enable them with a Tailscale application capability grant on tailscale.com/cap/tsidp. The grant carries allow_admin_ui and allow_dcr booleans, plus users and resources lists for the Secure Token Service, which the compose file enables with TSIDP_ENABLE_STS=1 for OAuth token exchange. The same grant can inject extraClaims into the id_token, and includeInUserInfo copies that data into the /userinfo response. Two things stand out. Grants are evaluated per request, so the README notes you do not restart tsidp after editing ACLs. And the schema is a moving target: the README warns it is still in development and may change at any time.

## Installing tsidp with Docker Compose and checking the issuer

The recommended path is the pre-built image at ghcr.io/tailscale/tsidp, published when releases are tagged. Create a Tailscale auth key from the Keys page of the admin console and pick an existing tag or make a new one first. Then write a compose file. Note that TAILSCALE_USE_WIP_CODE=1 is required while the version is below 1.0.0, and TSIDP_ENABLE_STS=1 turns on OAuth token exchange.

```yaml
services:
  tsidp:
    container_name: tsidp
    image: ghcr.io/tailscale/tsidp:latest
    volumes:
      - tsidp-data:/data
    environment:
      - TAILSCALE_USE_WIP_CODE=1
      - TS_STATE_DIR=/data
      - TS_HOSTNAME=idp
      - TSIDP_ENABLE_STS=1
volumes:
  tsidp-data:
```

Save that as compose.yaml, then start the service and follow the logs. The README gives these two commands.

```bash
docker compose up -d
docker compose logs -f
```

The README says the first run can take a few minutes while the TLS certificate is generated, and the service may be unreachable until it is ready. Once it is up, visit https://idp.yourtailnet.ts.net in a browser to confirm the service is running. For a local image instead of the published one, the Makefile provides make docker-image; to run from source, clone the repository and use the README's go run . invocation with TAILSCALE_USE_WIP_CODE=1, TS_AUTHKEY and TSNET_FORCE_LOGIN=1 set.

## Registering the node without leaving the auth key in docker inspect

The compose example above leaves TS_AUTHKEY commented out, which means the node needs to be registered another way. The README offers two. The first is an OAuth client secret: create an OAuth client in the admin console with the Auth Keys Write scope, make sure the advertise tag is defined in your ACL tagOwners, set TS_AUTHKEY to the tskey-client- value, and set TS_ADVERTISE_TAGS, which the README marks as required when using OAuth client secrets.

The second addresses a real leak. Passing TS_AUTHKEY as a plain environment variable puts the key in docker inspect output and in the container's process environment. The README's alternative is a Docker secret mounted as a file, with TS_AUTHKEY_FILE pointing at it.

```yaml
services:
  tsidp:
    image: ghcr.io/tailscale/tsidp:latest
    environment:
      - TAILSCALE_USE_WIP_CODE=1
      - TS_STATE_DIR=/data
      - TS_AUTHKEY_FILE=/run/secrets/ts_authkey
    volumes:
      - tsidp-data:/data
    secrets:
      - ts_authkey
secrets:
  ts_authkey:
    file: ./ts_authkey.txt
volumes:
  tsidp-data:
```

## Granting access to the admin UI and DCR endpoints

Nothing is reachable until you write a grant. In the Access controls page, add an application capability for tailscale.com/cap/tsidp. The README's example is deliberately permissive and marked suitable only for testing: src and dst are both wildcards, allow_admin_ui and allow_dcr are true, users and resources are wildcards, and extraClaims carries sample booleans, strings, numbers and arrays. The README advises keeping extraClaims small and simple, which is the right instinct since those values land in the id_token.

Once the grant is saved, visit https://idp.yourtailnet.ts.net in a browser to confirm the service is running. The README notes that grants are applied per request, so no restart is needed after an ACL edit. That is a genuinely convenient property compared with IdPs that cache client registrations until a bounce.

## Where tsidp is the wrong tool

The README opens with a caution block: this is an experimental update of tsidp, it is under active development, and it may experience breaking changes. That is not boilerplate. The application capability schema is explicitly described as still in development and subject to change, and the environment variable TAILSCALE_USE_WIP_CODE=1 exists precisely because the version is below 1.0.0. If your identity provider is on the critical path for a compliance audit or an external customer login, this is the wrong layer to take that risk on.

There is a second boundary. tsidp authenticates Tailscale identities. Any user who is not on the tailnet, including contractors you have not invited, cannot log in through it. And the default posture means a misconfigured or missing grant produces a denial rather than an error message about a missing grant, which is the correct security default but a slow thing to debug the first time.

The project is a Tailscale community project, and the last push to the repository was on 2026-09-08, so the code is moving. The README does not document a rollback procedure for a schema change to the capability grant, which is worth knowing before you write a large extraClaims block.

## PocketID and the difference in approach

PocketID is the natural comparison for anyone searching for a self-hosted OIDC provider, and the two solve the same surface problem with different trust anchors. PocketID is a standalone IdP with its own user store and passkey-based login, so it works for people who are not on a Tailscale network at all. tsidp has no user store of its own: identities come from the tailnet, and the node that issues tokens is itself a tailnet node reachable at a ts.net hostname.

That difference decides the choice. If you need to authenticate users who are outside the tailnet, or you want an IdP that survives independently of your network membership, tsidp is not it. If every user is already on the tailnet and you would rather not maintain a second directory, tsidp removes a component instead of adding one. The MCP examples in the repository under examples/mcp-gateway and examples/mcp-server point at the second case.

## Licence and the cost of tracking an experimental release

tsidp is BSD-3-Clause, a permissive licence that places few conditions on redistribution or modification. The repository includes a license_test.go, which suggests the project checks licence headers in CI. Nothing in the licence itself creates an obligation that would block internal deployment; as always, this is a description of the licence text, not legal advice.

The upgrade cost is where the real budget goes. Releases are frequent and versioned below 1.0.0: v0.0.13 and v0.0.14 both landed on 2026-05-25, and v0.0.15 arrived on 2026-08-20. Pinning ghcr.io/tailscale/tsidp:latest in compose means you take whatever is published on the next pull, which is the wrong default for a service that mints identity tokens. Pin a specific tag, read the release notes before moving, and keep the tsidp-data volume intact across upgrades so the node does not re-register.

## Conclusion

Adopt tsidp if your users are already on a tailnet and you want OIDC clients and MCP connections to accept Tailscale identities without standing up a hosted IdP. Do not adopt it for production identity if you cannot absorb breaking changes, since the README labels the project experimental and its application capability schema is still in development. Before you commit, verify that MagicDNS and HTTPS are enabled on the tailnet, that you can set an application capability grant in the access controls page, and that your OIDC clients accept the issuer hostname you choose, which becomes <hostname>.your-tailnet.ts.net.

## FAQ

### Can you give me an example of an Identity Provider (IdP)?

tsidp itself is one: the README describes it as an OIDC and OAuth Identity Provider server that integrates with your Tailscale network, letting Tailscale identities authenticate into OIDC applications and MCP connections.

### What is OIDC and how does it work?

The README does not explain the OIDC protocol itself; it only states that tsidp is an OIDC and OAuth identity provider that applications supporting OpenID Connect can authenticate against. For the protocol mechanics, the repository is silent.

### What is an IdP used for?

In tsidp's case, an IdP issues identity tokens so that applications accepting OpenID Connect, and authenticated MCP client and server connections, can rely on Tailscale identities instead of a separate login.

## Sources

- [Issues](https://github.com/tailscale/tsidp/issues)
- [License: BSD-3-Clause](https://github.com/tailscale/tsidp/blob/main/LICENSE)
- [README](https://github.com/tailscale/tsidp/blob/main/README.md)
- [Releases](https://github.com/tailscale/tsidp/releases)
- [tailscale/tsidp on GitHub](https://github.com/tailscale/tsidp)

---

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