Self-hosted service
tailscale/tsidp avatar
tailscale/tsidp

tsidp: turning a tailnet into an OIDC identity provider

A simple OIDC / OAuth Identity Provider (IdP) server for your tailnet.

667 stars58 forksGoBSD-3-Clause

At a glance

What is it?
tsidp is a community, experimental OIDC and OAuth identity provider that issues tokens for Tailscale identities, so applications and MCP clients can authenticate against the tailnet instead of a separate directory. It is small, Docker-first, and still pre-1.0, which shows in the capability schema and the state directory handling.
Who is it for?
Adopt tsidp if every user you need to authenticate is already on one tailnet, you want OIDC endpoints reachable only over that tailnet, and you accept the README's own caution that this is an experimental community project with a capability schema that may change. Do not adopt it as a general-purpose IdP for external customers, multiple identity sources, or anything where a breaking change to the grants schema would be an outage.
Can I use it commercially?
Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 8 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

The gap tsidp fills between a tailnet and OIDC-only applications

Tailscale already answers who a device is. It does not, on its own, answer who a user is to a third-party application that only speaks OpenID Connect. That is the gap. tsidp runs as a node on the tailnet and exposes OIDC and OAuth endpoints, so an application that supports OIDC can be pointed at the tsidp issuer and accept Tailscale identities. The README frames the audience narrowly: applications that support OpenID Connect, plus authenticated MCP client and server connections. The topics list on the repository repeats the same set (idp, mcp, oauth, oauth2, oidc, tailscale). This is not a general directory service. It is a bridge for people who already run a tailnet and want the identity they already have to be usable by software that expects a standard issuer. The prerequisite list is short and tells you most of what you need to know about scope: a tailnet with MagicDNS and HTTPS enabled, an authentication key, preferably Docker, and the ability to set an application capability grant.

How the pieces fit: tsnet node, state directory, capability grant

tsidp is a Go program that joins the tailnet as a node using tsnet, which is why the compose example sets TS_STATE_DIR to a mounted volume and TS_HOSTNAME to idp. The hostname determines the address: the README states it becomes idp.your-tailnet.ts.net. Persistent state lives in that directory, so losing the volume means re-registering the node. Configuration splits into two planes. One plane is environment and flags: TAILSCALE_USE_WIP_CODE, TS_STATE_DIR, TS_HOSTNAME, TSIDP_ENABLE_STS, TS_AUTHKEY or TS_AUTHKEY_FILE, TS_ADVERTISE_TAGS, and the -dir, -hostname, -port and -local-port flags. The other plane is the tailnet ACL, where a grant for the app capability tailscale.com/cap/tsidp controls what the server will actually let you do. The grant carries allow_admin_ui, allow_dcr, users and resources for the token service, an extraClaims object that is placed into the id_token, and includeInUserInfo to also return those claims from the /userinfo endpoint. The README notes that app capability grants are evaluated per request and take effect immediately, so changing access does not require restarting tsidp. That is a real operational difference from IdPs where a policy change means a reload or a rolling restart.

Getting it running with Docker Compose

The recommended path is the published image at ghcr.io/tailscale/tsidp. The README gives a compose file with container_name tsidp, a named volume tsidp-data mounted at /data, and environment entries for TAILSCALE_USE_WIP_CODE=1, TS_STATE_DIR=/data, TS_HOSTNAME=idp and TSIDP_ENABLE_STS=1. Save it as compose.yaml, then run docker compose up -d and follow the start with docker compose logs -f. The README warns that on a first run the TLS certificate may take a few minutes to generate, and the service may be unreachable until it is ready. Once it is up, browsing to https://idp.yourtailnet.ts.net is the check. Two alternatives to a plain auth key are documented. For automatic node registration with an OAuth client, create a client with the Auth Keys write scope, set TS_AUTHKEY to the tskey-client value, and set TS_ADVERTISE_TAGS, which the README marks as required in that mode. To keep the key out of docker inspect output, mount it as a Docker secret and set TS_AUTHKEY_FILE to the mounted path instead of TS_AUTHKEY. If you would rather not use the image, make docker-image builds your own container, and the Go path is TAILSCALE_USE_WIP_CODE=1 TS_AUTHKEY=... TSNET_FORCE_LOGIN=1 go run . from a clone of the repository.

The grant is the real configuration surface, and it is still moving

Installing tsidp gets you a running process. It does not get you a usable one. The README states plainly that access to the admin UI and the dynamic client registration endpoints is denied by default, and that the application capability schema is still in development and may change at any time. So the first real step after startup is editing the grants section of the tailnet access controls and adding an app entry under tailscale.com/cap/tsidp. The documented example is deliberately permissive, with src and dst both set to wildcard, and the README labels it suitable only for testing. In production you would narrow src and dst to the clients that need to reach the IdP and the applications that need to consume it. The extraClaims block is the part to think about hardest. The README recommends keeping it small and simple, and the example mixes booleans, strings, numbers and arrays, including a Go-style time layout string as a value. Those claims land in the id_token, and optionally in /userinfo. Anything you put there becomes part of a token that other systems will parse and trust. The schema instability is the sharpest edge here: a grant shape that works today is explicitly not guaranteed to work after an upgrade.

Where tsidp is the wrong tool

The README opens with a caution block: this is an experimental update of tsidp, under active development, and may experience breaking changes. It is also labelled a community project rather than a first-party product. Take both at face value. If you need an IdP that federates with upstream directories, supports multiple credential types, or gives you a stable configuration contract across upgrades, tsidp does not offer that, and the release history does not suggest a 1.0 is imminent. The version sequence in the material runs v0.0.13, v0.0.14, v0.0.15, with two of those three dated the same day, and the compose file still requires TAILSCALE_USE_WIP_CODE=1 with the comment that it is needed while the version is below 1.0.0. Another boundary is the identity source itself. tsidp authenticates Tailscale identities, so anyone who needs access has to be reachable through the tailnet and covered by the capability grant. There is no documented path for external users, social login, or a second identity provider. Single-instance state is a further constraint: state lives in one directory on one volume, and the material describes no replication or high-availability story. If the container or volume is lost, recovery means re-registering the node and reissuing tokens.

Keycloak and similar IdPs solve a different problem

The obvious comparison is a full identity provider such as Keycloak. The difference is not features, it is where identity is stored and who administers it. Keycloak maintains its own user store, or federates to LDAP and SAML sources, and gives you a management console, per-realm configuration, and a documented upgrade path. It is designed to serve users who are not on your network and to survive version upgrades without you rewriting policy. tsidp does the opposite: it holds no user directory, defers identity entirely to the tailnet, and exposes OIDC so that other software can consume that identity. Its access control is the tailnet ACL, not an application database. That makes it much smaller to operate, and it means the security boundary is the one you already maintain for your network. The trade is that every policy question becomes a Tailscale grant question, and the grant schema is the piece the README says may change. A team already comfortable administering tailnet ACLs will find that natural. A team that expects a realm export and a migration script between versions will not.

Upgrade and licence considerations before you commit

Upgrades are a container image pull, but the cost is not in the pull. It is in re-checking the capability grant against the new release, because the README states the schema may change at any time and the project is pre-1.0. Any extraClaims you have added to the id_token are part of that surface, and downstream applications that read those claims will need to be re-verified after an upgrade. The Go path has the same exposure: the flags listed in the README include -dir, -hostname, -port and -local-port, and flag names are not covered by a stability promise. Pin the image tag rather than following latest, and read the release notes for each version before moving. On licensing, the repository is BSD-3-Clause, a permissive licence that generally allows modification and redistribution provided the copyright notice and licence text are retained. That is a summary of the identifier, not legal advice; if you plan to redistribute a modified tsidp or embed it in a product, have counsel read the actual licence file in the repository.

Editorial conclusion

Adopt tsidp if every user you need to authenticate is already on one tailnet, you want OIDC endpoints reachable only over that tailnet, and you accept the README's own caution that this is an experimental community project with a capability schema that may change. Do not adopt it as a general-purpose IdP for external customers, multiple identity sources, or anything where a breaking change to the grants schema would be an outage. Before deploying, verify three things: that MagicDNS and HTTPS are enabled on the tailnet, that the tag you plan to advertise is defined in tagOwners, and that a grant for tailscale.com/cap/tsidp has been set, because the admin UI and dynamic client registration endpoints are denied by default.

Official sources

  1. Issues
  2. License: BSD-3-Clause
  3. README
  4. Releases
  5. tailscale/tsidp on GitHub
Community notes

Community notes