OIDC is a standard, identity providers are not
Generic OIDC connections meet identity providers that read the same spec differently. Here are the per-connection compatibility settings we added, and why.
A generic OIDC connection is supposed to be the easy one. The customer's IT team hands over a client ID, a client secret, and a discovery URL ending in /.well-known/openid-configuration, and the rest is supposed to fall out of the spec. Then the token exchange comes back with an error and you're reading that provider's documentation instead of the RFC.
OpenID Connect is a real standard. It is also a standard full of MAYs, OPTIONALs, and registered alternatives, and identity providers pick different ones. Most of the compatibility work we've done on Generic OIDC connections has been the same move over and over: take an assumption that was hardcoded because one provider needed it, and turn it into a per-connection setting.
Providers disagree about how the client says hello
When WorkOS exchanges the authorization code for tokens, it has to authenticate itself to the provider's token endpoint. OpenID Connect Core defines five ways to do that: client_secret_basic, client_secret_post, client_secret_jwt, private_key_jwt, and none. If the client registered no method at all, the default is client_secret_basic.
That sounds like a settled question.
It isn't. HTTP Basic is the part of OAuth that has caused the most quiet interoperability pain, and the OAuth 2.1 draft says so in a note attached to the method itself:
"Note: This method of initially form-encoding the client identifier and secret, and then using the encoded values as the HTTP Basic authentication username and password, has led to many interoperability problems in the past. Some implementations have missed the encoding step, or decided to only encode certain characters, or ignored the encoding requirement when validating the credentials, leading to clients having to special-case how they present the credentials to individual authorization servers."
The two shared-secret methods are the same request with the credentials in a different place:
A provider that only registered the client for one of these will reject the other, and there's no way to guess correctly from the outside. So it's a field now: the Authentication method setting in a connection's Advanced settings, offering client secret basic (the default), client secret POST, and private key JWT.
Private key JWT is the interesting third option. Rather than presenting a shared secret, the client signs a JWT assertion with a private key whose public half the provider holds:
Selecting it makes WorkOS generate an RS256 signing key pair for that connection and hide the Client Secret field, since there's no longer a secret to store. The public half comes out as either an X.509 PEM certificate or a JWK, because providers disagree about that too. The cost is a slower setup. You swap a credential you can paste into a dashboard for one the customer's IT team has to register on their side, and switching the connection back to a secret-based method means issuing a new Client Secret.
Every provider must support RS256, and plenty of them use something else
OpenID Connect Core requires providers to support RS256 for ID token signatures:
"OPs MUST support signing ID Tokens with the RSA SHA-256 algorithm (an alg value of RS256), unless the OP only supports returning ID Tokens from the Token Endpoint (as is the case for the Authorization Code Flow) and only allows Clients to register specifying none as the requested ID Token signing algorithm."
The exception in that sentence covers more ground than the requirement does. And "must support" was never "will use". A provider is free to sign with ES256 or EdDSA if that's how the application was registered, and hardcoding RS256 in the verifier means every one of those connections dies at signature verification, with an error that tells the customer nothing useful.
So the signing algorithm is pinned per connection now, defaulting to RS256. The supported set covers RSA (RS256/384/512), RSA-PSS (PS256/384/512), ECDSA (ES256/384/512), EdDSA, and HMAC (HS256/384/512). Set it to match the id_token_signed_response_alg the provider is configured with.
The one hard constraint in that list is that HMAC and private key JWT can't coexist. Symmetric signatures are verified with the Client Secret, while the asymmetric families are verified against the provider's published JWKS, and private key JWT connections don't store a Client Secret at all. The dashboard and setup flow block that combination rather than letting it fail at login.
Some ID tokens don't carry the profile
OpenID Connect lets standard claims come back in either the ID token or the UserInfo response. Claims requested through the profile, email, address, and phone scopes are returned from the UserInfo endpoint when the flow issues an access token. And the spec explicitly permits a provider to leave claims out:
"For privacy reasons, OpenID Providers MAY elect to not return values for some requested Claims. It is not an error condition to not return a requested Claim."
WorkOS needs sub and email on every profile: sub becomes the idp_id, email becomes the email address. A provider that returns a minimal ID token and puts the rest behind UserInfo produces connections that authenticate successfully and then fail to build a profile.
Fetching the profile separately is one more request:
This behavior already existed for some provider-specific connection types, where it was hardcoded rather than exposed to the customer. On generic connections it's a Use userinfo endpoint checkbox now: enable it and WorkOS reads the profile from the endpoint advertised in the discovery document instead of from the ID token claims. Turn it on when the provider omits claims like email, given_name, or family_name from the token.
A correctness rule rides along with that extra request, and the spec states it more strongly than most people implement it:
"The sub Claim in the UserInfo Response MUST be verified to exactly match the sub Claim in the ID Token; if they do not match, the UserInfo Response values MUST NOT be used."
That's a defense against token substitution, and the reason the second fetch is a verification step rather than a plain data pull.
PKCE went from ignorable to mandatory
PKCE has been a Standards Track RFC since September 2015. It's a small mechanism. The client sends a hash of a per-request secret up front, then proves possession of the secret at the token endpoint, with code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier))) under S256:
RFC 7636 was written to be ignorable: servers MAY accept clients that don't implement it and fall back to plain OAuth 2.0. For years most identity providers did exactly that. OAuth 2.1, which replaces RFC 6749, closes the door. code_challenge is REQUIRED outside a narrow exception, and clients MUST use it while authorization servers MUST enforce it.
The practical trigger was providers starting to enforce it. When a server requires PKCE and the client omits the challenge, the authorization endpoint returns invalid_request, a failure that lands before the token exchange, on the customer's first login attempt.
This one is not really a knob in the same sense as the others. PKCE is the more secure flow, every current specification recommends it, and it belongs on. It sits in the same Advanced settings panel as the rest, but the guidance runs the other way: leave it enabled, and only turn it off if a specific provider chokes on it. That is an escape hatch, not a configuration decision.
Secrets expire, and resetting the connection was the old answer
The last change in the same release isn't a disagreement about the spec at all. It's about what happens six months after setup.
Client secrets expire. They get rotated on the customer's schedule, or after somebody leaves, or because the provider enforces a maximum lifetime. Until recently, updating one on a live connection meant resetting the connection and walking the customer's IT team back through the setup wizard, for a credential that had not conceptually changed at all. That is a support ticket and a login outage for a value that fits in a text field.
Client ID and Client Secret are now editable on an existing connection, from the Admin Portal, applying immediately. Two clicks, no wizard, no reset.
It's the same move as everything above, pointed at time rather than at spec ambiguity. The person holding the new secret is the customer's IT admin, and they should not need you in the loop to enter it.
The knob is the point
Every one of these settings exists because a reasonable implementation of the same specification behaved differently than the last one. That won't stop. A discovery document describes some of a provider's behavior, but nothing in the protocol tells you which client authentication method that particular application was registered with, or whether the profile claims made it into the token at all.
The alternative to a knob is a guess, and a wrong guess shows up as a customer's employees unable to log in on rollout day. Pushing the choice out to the connection hands the question to the person who can actually answer it: the one with the provider's admin console open in another tab.