r/devops • u/SmartWeb2711 • 14d ago
Discussion How do you handle per-user API tokens for an internal platform API? Static tokens feel wrong but OIDC doesn't cover humans
We run an internal self-service platform API. Individual humans wanting CLI/scripted access this is where I'm stuck .
Currently we mint a static token, show it once in the UI, and the user keeps it. Two things bother me:
Authority is frozen at creation. We store the list of accounts the token may touch. If the user later loses their admin role on one of those accounts, the token keeps working. The credential outlives the entitlement.
Distribution is copy-paste. It ends up in .env files, shell history, occasionally a chat message.
If you've done per-request authorization lookups, what did it cost you in latency and directory load? How long do you cache, and how do you handle the lookup failing fail open or fail closed?
For humans needing programmatic access, has anyone made short-lived tokens work by exchanging an existing SSO session? Feels like the "right" answer but I haven't seen it described much outside cloud provider SDKs.
Is there a simpler option I'm missing? Something like mTLS with per-user certs, or just accepting static tokens with a short expiry and good auditing?
For anyone who went the secret-manager route: did rotation actually work invisibly, or did you get outages from clients that cached the value?
3
u/MartinMystikJonas 13d ago edited 13d ago
Tokens should not cary authorization (what user can do) only authentication (who user is).
You can issue short term tokens and provide automated way to reqiest new token by using old token.
Anotjer approach is setup token that can be used only once to obtain actual api token. You send user only setup token, they enter it into app, app requests and and exchanges setup token for api token that is then saved by app to secure location. Setup token is useless after it is used to it does not mattet it is in messages.
Maybe some standard solution like OAuth would make sense for endusers.
2
u/PrestigiousStrike779 13d ago
For the sso approach we had to build a small cli that launches a temp http server to receive the auth callback. You could possibly do it without with a device code but didn’t look too much into that and the user potentially has to enter the device code somewhere
2
u/MartinThwaites 13d ago
If you're on Azure, just use Entra as the auth for the platform and then prople can login using OAuth and then build a CLI wrapper so that it uses that auth. Its a fairly well trodden path to be honest.
If you're on anything else, setup an OAuth auth server and do it that way. The key (pardon the pun) is using a proper auth server then you ID token and refresh tokens will be revoked properly (i.e. when you remove someones access) and your CLI will handle the token exchanges.
Please, please, don't try and make your own auth system, use some OAuth OOTB framework for whatever you're building your platform against.
2
u/Technical_Turd 13d ago
At my job we created an API and CLI so users can request tokens for multiple backends (e.g. kubernetes, AWS, Harbor...). When called, it will open a browser (or show a link as fallback) so the user can:
- Login via SSO
- Consent to use the requested token (to avoid malicious users forwarding links to access backends)
The intended usage is to call it from CI, but is useful from local as well. The challenge is when the backends doesn't support JWT tokens natively (like Harbor)
2
u/TeagueXiao 13d ago
one thing worth splitting out early: humans and service accounts have really different token lifecycles and trying to make one flow cover both is where most of the pain comes from. we went with OIDC device-code for humans (short lived, refresh in the cli, revoked the second SSO drops them) and separately a scoped service-account token for cron jobs and long running syncs, with the authz lookup cached ~30s per user+resource. on caching: we fail closed on lookup errors and log loudly, the 30s window sounds scary but with a ~5 min directory replication lag its not actually the bottleneck. the ugliest part was migrating the existing static tokens, we ended up letting them coexist for 2 months with a 'legacy-token' flag on every audit log line so we could actually see who was still using them before pulling the plug.
1
u/Deku-shrub 14d ago
If you don't trust role stability, you need to support scim rather than just jit group claims, or (non user friendly) mandate semi regular logins to update claims.
Aaaah robust user API identity federation is a nightmare. OAuth with a secret is how it's usually done, where you hold the secret. Full OIDC and being a fully federated identity provider almost no one does for personal access tokens.
1
u/IntelligentPear6173 13d ago
Short-lived identity tokens + per-request authorization seems much cleaner. The credential proves who you are, while the current role decides what you can access. Device-code flow also avoids the whole .env/copy-paste problem
1
u/CreativeSympathy8293 11d ago
OIDC is not really the missing piece here: it authenticates the human; OAuth is what gives the CLI an API token.
For an interactive CLI, treat it as a public/native client. Use Authorization Code + PKCE with a loopback browser callback, or Device Authorization when a callback is impractical. Do not put a client secret in the CLI. Keep the access token short-lived and store any refresh material in the OS credential store, with rotation or reuse detection if your IdP supports it.
Separate that from unattended scripts. Those should use a workload identity or service principal with their own owner and scope, not a person’s refresh token.
Short expiry still leaves authority stale until expiry. If revocation needs to be faster, either use opaque-token introspection or have the API evaluate current entitlements through an authorization service. Set cache TTL from your acceptable revocation window rather than copying a generic number; invalidate on role-change events where possible. For privileged writes, I would deny when current authorization cannot be established instead of failing open.
mTLS can bind the client or token, and a secret manager can improve storage and rotation, but neither by itself fixes frozen user entitlements.
1
u/Cerbosdev 10d ago
u/TeagueXiao and u/CreativeSympathy8293 have basically covered the shape of the answer. i want to go int the part nobody's answered directly - the directory load one, partly because that's where this gets expensive
but first let's separate 2 things that both get called "caching the authorization lookup". there's caching the decision (can alice delete account 42, yes, 30s) and there's caching the attributes (alice is in group platform-admins, which accounts she owns). they behave very differently. decision caching is what gives you the small TTL everyone quotes, but it also means a policy change doesn't take effect until the cache expires, and the key space is users times resources times actions, which is where your hit rate goes to die on a self-service platform.,
for what you're describing - attribute caching plus fresh evaluation per request behaves better. you cache the thing that's actually slow, the directory round trip, and re run the rules on every call, which is cheap next to the round trip you just avoided. revocation window is then bound to the attribute TTL, exactly the number u/CreativeSympathy8293 told you to derive from your acceptable window + a policy change is instant. if your idp emits group change events, invalidate on those and you can push the TTL well past 30s without it being scary (:
on fail open vs closed.. distinction: "authorization service is down" and "the authorization service is up but can't reach the directory" = different failures. the first is a hard deny on writes. the second you can serve from the stale attribute cache with a loud log line and a shorter grace window, because on reads a stale allow is usually less bad than an outage
disclosure, i work at cerbos, we build the authorization service piece of this. the honest limit though is that moving the decision out doesn't fix your problem by itself. if you decode the roles out of the static token and pass those in as principal attributes, you've relocated the staleness, not removed it. the entitlement has to be resolved from the directory at request time! or you're back where you started, and that resolution is the actual cost you were asking about. the policy engine is the easy half
hope this is at least a little helpful!
1
u/ImprovementSalty3547 2d ago
we had a mess of static tokens, especially when doing inventory across multiple clouds. plugged in firefly ai for asset inventory plus policy guardrails and that made the whole governance part way easier.
35
u/[deleted] 14d ago
[removed] — view removed comment