Flyte 2 Devbox is available today to run a full Flyte backend and UI locally. Preview Flyte 2 for production, hosted on Union.ai

Authentication and SSO

Flyte delegates authentication to an external OIDC identity provider (Okta, Google, Auth0, …). Two things are involved:

  1. Auth metadata — the runs service advertises which IdP to use, so SDK/CLI and browser clients can discover where to log in and get tokens. Configured under flyte-core-components.runs.authMetadata.
  2. Enforcement at the ingress — the load balancer validates those tokens (and challenges browsers with SSO) before requests reach Flyte. Configured with ingress annotations.

The runs service serves OAuth2 authorization-server metadata (/.well-known/oauth-authorization-server and the AuthMetadataService RPC) that proxies your external IdP. Clients that discover auth at this deployment are pointed at the IdP and obtain IdP-issued tokens, which the ingress then validates.

flyte-core-components:
  runs:
    authMetadata:
      # Your external OAuth2 authorization server (the IdP issuer URL).
      externalAuthServerBaseUrl: https://<your-idp>/oauth2/default
      # Public (PKCE) client advertised to the SDK/CLI via GetPublicClientConfig
      # for browser login. Register this app at your IdP with the localhost redirect.
      flyteClient:
        clientId: <public-client-id>
        redirectUri: http://localhost:53593/callback
        scopes:
          - openid
          - profile
          - offline_access

The redirectUri (default http://localhost:53593/callback) is the SDK/CLI’s local PKCE callback and must be registered as a redirect URI on the IdP application.

Okta note. PKCE needs an Okta native app client ID with the http://localhost:53593/callback redirect registered; the client_credentials (machine) flow needs a custom scope on the auth server. Adjust clientId / scopes for the flow you use.

Enforce auth at the ingress

Browsers and machine clients authenticate differently, and on a controller like AWS ALB a single ingress can’t combine cookie-OIDC (browser) and JWT (token) auth — so the chart can render up to three ingresses:

Ingress (values key) Purpose
ingress (httpAnnotations) Serves the console (/v2) and API; challenges browsers with cookie-OIDC SSO ( walkthrough below).
ingress.apiJwtIngress JWT-validates the flyteidl2.* API paths for requests carrying Authorization: Bearer (SDK / CLI / machine clients). Give it higher controller precedence than the http ingress so Bearer requests match it first.
ingress.wellknownIngress Serves the unauthenticated auth-discovery endpoints (/.well-known/oauth-authorization-server, AuthMetadataService) — clients need these before they hold a token, so give it the highest precedence to bypass auth.

Enable the JWT and discovery ingresses and supply your controller/JWT config via their annotations — e.g. on ALB: the ACM certificate-arn, the JWT-validation config, the Authorization: Bearer* match condition, and group.order values (lower = evaluated first) that put wellknownIngress first, then apiJwtIngress, then the http ingress:

ingress:
  create: true
  host: <flyte.example.com>
  # Browser SSO annotations on the main http ingress — see the walkthrough below.
  httpAnnotations: { }
  apiJwtIngress:
    enabled: true
    annotations: { }     # JWT validation + the `Authorization: Bearer*` match
  wellknownIngress:
    enabled: true
    annotations: { }     # highest controller precedence; no auth

Single sign-on for the console at the ALB

This is the browser cookie-OIDC SSO referenced above — it goes on the main http ingress’s httpAnnotations. You can put OIDC single sign-on in front of the console at the ALB, so that hitting https://<host>/v2 challenges the user to log in at your IdP before the request ever reaches the console. ALB’s native authenticate-oidc action handles the login and manages the session cookie; the Flyte binary is unchanged. SDK/machine clients that send an Authorization: Bearer token are matched by higher-precedence API rules and bypass the cookie flow.

Browser ──GET /v2──▶ ALB ──(no session)──▶ 302 ▶ IdP login
                       ▲                              │
                       └──── /oauth2/idpresponse ◀────┘  (ALB swaps code→token,
                              sets session cookie, forwards to the console)

Prerequisites

  • The AWS Load Balancer Controller managing your ingress (ingressClassName: alb).
  • An HTTPS listener with an ACM certificate covering your host — OIDC auth only applies to HTTPS rules.
  • An OIDC application at your IdP (confidential client, Authorization Code flow) with a client ID and secret.

1. Configure the OIDC app

On the IdP application:

  • Add the sign-in / redirect URI exactly (note the path — ALB’s callback is fixed):
    https://<your-host>/oauth2/idpresponse
  • Grant type Authorization Code; scopes at least openid email.
  • Assign the users/groups allowed into the console.

You’ll need the issuer plus the authorize/token/userinfo endpoints:

Okta (custom auth server) Google
issuer https://<domain>/oauth2/<id> https://accounts.google.com
authorize …/oauth2/<id>/v1/authorize https://accounts.google.com/o/oauth2/v2/auth
token …/oauth2/<id>/v1/token https://oauth2.googleapis.com/token
userinfo …/oauth2/<id>/v1/userinfo https://openidconnect.googleapis.com/v1/userinfo

For Okta, the discovery doc https://<domain>/oauth2/<id>/.well-known/openid-configuration is the source of truth.

2. Create the client Secret

In the same namespace as the ingress, with keys exactly clientID / clientSecret:

kubectl create secret generic flyte-console-oidc -n flyte \
  --from-literal=clientID='<client-id>' \
  --from-literal=clientSecret='<client-secret>'

3. Allow the LB controller to read the Secret

The AWS Load Balancer Controller’s service account must be able to get/list/ watch Secrets in the ingress namespace. The upstream Helm chart usually grants this cluster-wide, but hardened installs may not — if yours doesn’t, add a namespaced Role and RoleBinding:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: aws-lb-controller-oidc-secret
  namespace: flyte
rules:
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: aws-lb-controller-oidc-secret
  namespace: flyte
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: aws-lb-controller-oidc-secret
subjects:
- kind: ServiceAccount
  name: aws-load-balancer-controller   # adjust to your controller's SA
  namespace: kube-system

Without this you’ll see FailedBuildModel … secrets "flyte-console-oidc" is forbidden on the ingress and no auth rule is applied.

4. Add the auth annotations

Add the OIDC annotations to ingress.httpAnnotations in your values and helm upgrade. They apply to every rule on the HTTP ingress, gating the /v2 console:

ingress:
  httpAnnotations:
    # … existing ALB annotations (certificate-arn, scheme, listen-ports, …) …
    alb.ingress.kubernetes.io/auth-type: oidc
    alb.ingress.kubernetes.io/auth-scope: openid email
    alb.ingress.kubernetes.io/auth-on-unauthenticated-request: authenticate
    alb.ingress.kubernetes.io/auth-session-timeout: "604800"   # 7 days
    alb.ingress.kubernetes.io/auth-idp-oidc: '{"issuer":"<issuer>","authorizationEndpoint":"<authorize>","tokenEndpoint":"<token>","userInfoEndpoint":"<userinfo>","secretName":"flyte-console-oidc"}'

5. Verify

# Console redirects to the IdP:
curl -s -o /dev/null -D - https://<host>/v2 | grep -i '^location'
# → 302 … https://<issuer>/v1/authorize?client_id=…&redirect_uri=…%2Foauth2%2Fidpresponse…

# API path is NOT gated (a normal response, not a 302 to the IdP):
curl -s -o /dev/null -w '%{http_code}\n' -X POST \
  https://<host>/flyteidl2.project.ProjectService/ListProjects \
  -H 'Content-Type: application/json' -d '{}'

Then open https://<host>/v2 in a browser — you should be bounced through the IdP and back into the console.

Troubleshooting

Symptom Cause and fix
FailedBuildModel … secrets "…" is forbidden on the ingress The LB controller can’t read the Secret. Apply the RBAC in step 3.
Browser: 'redirect_uri' parameter must be a Login redirect URI The exact callback isn’t registered. Add https://<host>/oauth2/idpresponse (with that path) to the IdP app’s redirect URIs.
401 Authorization Required after a successful login The token exchange failed — almost always a wrong client secret or client_id. A trailing % on a secret copied from a terminal is the shell’s no-newline marker, not part of the secret; strip it.

Annotation reference

Annotation Purpose
auth-type: oidc Enable OIDC auth on the ingress’s HTTPS rules.
auth-idp-oidc JSON: issuer, authorizationEndpoint, tokenEndpoint, userInfoEndpoint, secretName (Secret with clientID/clientSecret).
auth-scope Space-separated scopes, e.g. openid email.
auth-on-unauthenticated-request authenticate (challenge), allow, or deny.
auth-session-timeout Session cookie lifetime in seconds.

The ALB callback path is fixed at /oauth2/idpresponse, and auth applies only to the annotated ingress’s HTTPS listener rules.

Run attribution (executed_by)

Once authentication happens at the edge, Flyte records who created each run (surfaced as executed_by in run metadata). The runs service does not re-validate tokens itself — it reads the identity from the headers the proxy forwards. After ALB authenticate-oidc those are:

  • X-Amzn-Oidc-Data — a signed JWT carrying the full claims (sub, email, given_name, family_name); used on the browser/cookie path.
  • X-Amzn-Oidc-Identity — the subject only; used when the data header is absent.
  • Authorization: Bearer <jwt> — the SDK/CLI path (proxy-agnostic, always honored). This token carries only the subject, so name and email are filled from the IdP’s userinfo endpoint when runs.authMetadata.externalAuthServerBaseUrl is set.

The defaults match ALB, so a standard ALB SSO deployment needs no extra configuration.

Trust boundary. The forwarded JWTs are decoded but not signature-verified by the runs service. That is safe only behind a trusted proxy that validates tokens and strips any client-supplied copies of these headers. If the service can be reached directly, set trustForwardedIdentityHeaders: false and executed_by is left unset rather than risk a spoofed identity.

Behind a non-ALB proxy (oauth2-proxy / Traefik)

The header names are configurable, so attribution works behind any auth proxy. For oauth2-proxy or Traefik forward-auth, which forward plain values instead of a JWT:

flyte-core-components:
  runs:
    trustForwardedIdentityHeaders: true   # default; gates header-derived attribution
    identityHeaders:
      claimsJwtHeader: ""                  # no JWT header on this path
      subjectHeader: X-Auth-Request-User   # ALB default: X-Amzn-Oidc-Identity
      emailHeader: X-Auth-Request-Email    # ALB default: unset (email is in the JWT)
Setting Header read ALB default oauth2-proxy / Traefik
claimsJwtHeader JWT with full claims X-Amzn-Oidc-Data (empty)
subjectHeader subject, plain value X-Amzn-Oidc-Identity X-Auth-Request-User
emailHeader email, plain value (unset) X-Auth-Request-Email

The same trust boundary applies: the proxy must validate identity and strip any client-supplied copies of these headers, with trustForwardedIdentityHeaders enabled.