· 3 min read

When Your Identity Provider Lies About Group Claims

We wanted something simple. Anyone in the Platform-Admins directory group should get admin in our analytics platform. The platform speaks OIDC, the identity provider issues OIDC tokens, and the platform has a setting called groupsClaimName. Point one at the other and you're done. That was the plan, anyway.

I granted the admin role to the group, logged in, and got nothing. No error. Just an account with zero permissions, as if the group binding didn't exist.

Turn on the logs nobody wants to read

The platform validates the token and builds an identity from it, so I turned on trace logging for exactly those two steps. One logger for the OIDC token service, one for the principal builder. Log in once, then read.

The line I was looking for printed the raw claims the platform received from the IdP. Two things jumped out.

First, there was a groups-style claim, but its only value was an auto-generated group every federated user belongs to. Useless for admin, and dangerous if you ever bound a role to it.

Second, the directory group I actually cared about was present, but under a custom attribute, and it came through as a plain string:

"custom:groups": "Platform-Admins"

Not an array. A string. The next log line explained the rest:

groups is not a List. Actual type: String

The platform expects the groups claim to be a JSON array. It got a string, rejected it, and built my identity with an empty group list. No exception bubbled up to the UI because, from the platform's point of view, nothing went wrong. The user simply had no groups.

Why no server setting fixes this

I spent an hour looking for the knob that splits a comma string into a list. There isn't one, and after thinking about it, there shouldn't be. The claim's type is decided by whoever mints the token. Our IdP serializes custom attributes as strings no matter what the underlying attribute type is. The platform reading the token has no way to know whether "a,b,c" means three groups or one group with commas in the name. Guessing would be worse than failing.

So the fix has to happen where the token is built, not where it's read. Two options worked on paper:

  1. Create a native group in the IdP with the same name and add each admin to it. Native groups land in the standard groups claim as a real array, and the existing binding starts matching. The downside is you now maintain membership in two places.
  2. Add a pre-token hook that reads the string attribute, splits it, and writes a real array into the groups claim before the token is signed. More moving parts, but directory membership keeps flowing automatically.

Both push the array-shaping upstream of the platform, which is the only place it can correctly happen.

What we shipped

Neither option was in place the week we needed access, so admins got individual role bindings by username. Ugly, manual, and I left a comment in the config saying exactly why, so the next person doesn't "clean up" the per-user grants and lock everyone out.

The lesson stuck with me. When a federated login silently grants nothing, stop staring at the consumer's config. Print the token and check the type of each claim, not only the value. A string that should have been an array is invisible until you look at the shape, and it costs you an afternoon every time you forget.