Let clients run the device flow against the OIDC provider and send ID tokens - #8080
Open
santhosh-shanmugham wants to merge 2 commits into
Open
santhosh-shanmugham wants to merge 2 commits into
santhosh-shanmugham wants to merge 2 commits into
Conversation
santhosh-shanmugham
force-pushed
the
sshanmugham/client-idtoken-device-flow
branch
7 times, most recently
from
September 25, 2026 05:18
6f25862 to
2d41069
Compare
… tokens flyteadmin's built-in authorization server has no device authorization endpoint, so flytectl on a host without a browser has no way to log in as a user. flyteadmin does accept OIDC ID tokens from its userAuth provider over gRPC when they are sent with the IDToken scheme, the same path the console uses. This change lets the Go admin client use that path: - admin.deviceAuthorizationUrl (DeviceFlow) and admin.authorizationUrl (Pkce), each together with the existing admin.tokenUrl, run the flow against another authorization server, for example admin's userAuth OIDC provider. admin.clientId, admin.scopes and admin.audience are then used for the client registered there. Endpoints are configured explicitly, matching the existing tokenUrl override, so there is no discovery request and no new dependency. - admin.tokenType selects the scheme sent to admin: Bearer (default) or IDToken. With IDToken the id_token from the token response replaces the access token for the device, PKCE, refresh and ExternalCommand paths. The token type is stored on the cached token so refreshes keep working. Behaviour is unchanged unless the new settings are used. The deprecated authorizationServerUrl key is left as is. Signed-off-by: Santhosh Shanmugham <sshanmugham@waabi.ai>
santhosh-shanmugham
force-pushed
the
sshanmugham/client-idtoken-device-flow
branch
from
September 25, 2026 05:26
2d41069 to
7e36dad
Compare
santhosh-shanmugham
marked this pull request as ready for review
September 25, 2026 14:01
santhosh-shanmugham
requested a review
from davidmirror-ops
as a code owner
September 25, 2026 14:01
Tested the client against Dex and Keycloak. The only provider-specific step is getting flyteadmin's client id into the ID token audience, so spell out how for Dex and Keycloak and what the constraint means for providers that cannot add audiences to ID tokens. Signed-off-by: Santhosh Shanmugham <sshanmugham@waabi.ai>
This was referenced Sep 25, 2026
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Tracking issue
Related to the recurring question of running
flytectldevice flow with Flyte's built-in authorization server, for example How do I setup device code authentication on the community forum.Why are the changes needed?
Flyte's built-in authorization server (
authServerType: Self) does not implement the device authorization grant (RFC 8628), soflytectlcannot log a user in on a host without a browser unless the deployment moves to an external authorization server. That is a large change for a deployment that only needs headless CLI login.flyteadmin already accepts OIDC ID tokens from its
userAuth.openIdprovider over gRPC when they are sent with theIDTokenscheme (GetAuthenticationInterceptorfalls back toGRPCGetIdentityFromIDToken). Most OIDC providers, including Dex, implement the device grant. The Go admin client just cannot reach them today: it discovers endpoints only from flyteadmin's metadata, and it always sends the access token asBearer.What changes were proposed in this pull request?
Client-side only, in
flyteidl/clients/go/admin. No flyteadmin changes.admin.deviceAuthorizationUrl(forDeviceFlow) andadmin.authorizationUrl(forPkce). Each is used together with the existingadmin.tokenUrloverride, and when set the flow runs against those endpoints instead of the ones flyteadmin advertises, withadmin.clientId,admin.scopesandadmin.audienceused for the client registered there.tokenUrlandscopesare required in that mode. The values are the provider'sdevice_authorization_endpoint(RFC 8628 section 4),authorization_endpointandtoken_endpointfrom its OpenID Connect Discovery document. Endpoints are configured explicitly rather than discovered, which matches howtokenUrlalready works, needs no request at startup and no new dependency.admin.tokenType:Bearer(default) orIDToken. WithIDToken, theid_tokenfrom the token response replaces the access token and the token type is set so the request carriesIDToken <jwt>. Applied in the device flow, the PKCE code exchange, token refresh andExternalCommand. The type is stored on the cached token so the existing cache and refresh logic is unchanged.authorizationServerUrlkey and its Go field are untouched. It was deprecated when endpoint discovery from flyteadmin arrived and is ignored today; giving it new meaning would change behaviour for configs that still carry it from that era, so this uses a new key instead.tokenType: IDTokenwithauthType: ClientSecretis rejected (the client credentials grant issues no id_token). With an explicit endpoint,tokenUrlandscopesare required and aclientIdleft at the defaultflytepropellerlogs a warning. AtokenUrlon its own, which some configs still carry forClientSecret, changes nothing forPkceorDeviceFlow.tokenTypewith a token already cached needs no special handling: the first request fails withUnauthenticated, the auth interceptor purges the cached token as it does today, and the flow restarts with the new type.admin.scopes,admin.clientIdandadmin.audiencekeep being ignored forPkceandDeviceFlowunless the matching endpoint is set, so existing configs behave exactly as before.Example client config:
How was this patch tested?
Unit tests added:
oauth:NormalizeTokenType,PrepareToken(Bearer no-op, ID token substitution, missing id_token, nil).deviceflow: full device flow against a fake server withtokenType: IDToken(id_token becomes the token and is cached), Bearer unchanged, and missing id_token error.tokenorchestrator: refresh grant withtokenType: IDTokenreturns and caches the new id_token.admin:ExternalCommandwith default andIDTokentypes, unsupported type rejected,DeviceFlowandPkcewith explicit endpoints (endpoints, client id and scopes from config, audience and redirect from admin), missingtokenUrlorscopesrejected,ClientSecretwithIDTokenrejected, a staletokenUrlalone leavingDeviceFlowunchanged, and admin-only discovery unchanged.go test ./clients/go/admin/...passes,gofmt/go vetclean,golangci-lint run --new-from-rev=masterwith the flyteidl config reports no new issues,flytectlbuilds. Config flags regenerated withpflags.Size: about 120 lines of implementation, 350 lines of tests, 40 of docs, and a regenerated flags file.
End to end against two providers.
Keycloak 26.3 (local, realm with a confidential
flyteadminclient, a publicflytectlclient with the device grant enabled and an Audience mapper addingflyteadmin): the direct-grant ID token carriesaud: [flytectl, flyteadmin],azp: flytectl, refresh returns a newid_token, and the device grant issues codes to the public client.flytectlfrom this branch withdeviceAuthorizationUrl/tokenUrlpointed at Keycloak ran the device flow, the verification and consent were completed in a browser session, and the resulting ID token was sent to a real flyteadmin with theIDTokenscheme, which rejected it only on issuer,expected <its Dex issuer>, got http://localhost:18080/realms/flyte, confirming the token travelled as an ID token end to end.Dex: a
flytectlbuilt from this branch with the config above, against a flyteadmin on the self authorization server with Dex asuserAuth.openId. From a Linux host with no browser, the device flow printed the verification URL and code, the user approved on a phone, the ID token hadaudcontaining both the CLI client and flyteadmin's client id, andflytectl get projectsucceeded with the user's identity. A second run used the cached token with no prompt, and the refresh grant renewed it without interaction. Stockflytectlwith the same token viaExternalCommandfails withUnauthenticated ... Request unauthenticated with IDToken, which is the gap this closes.Labels