Migrate KKP dashboard auth from implicit flow to OAuth authorization code flow#8053
Migrate KKP dashboard auth from implicit flow to OAuth authorization code flow#8053ahmadhamzh wants to merge 16 commits intokubermatic:mainfrom
Conversation
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:
Approvers can indicate their approval by writing |
d8f5946 to
2611b3b
Compare
2611b3b to
bb0252a
Compare
|
/retest |
2 similar comments
|
/retest |
|
/retest |
There was a problem hiding this comment.
Pull request overview
This PR migrates KKP Dashboard authentication from the legacy browser-side implicit flow to a backend-driven OAuth 2.0 Authorization Code flow with PKCE, moving token handling to secure cookies and adding dedicated v2 auth endpoints (/api/v2/auth/*) to better support multi-replica deployments and OIDC spec requirements (nonce validation).
Changes:
- Adds new API v2 authflow endpoints for login/callback/refresh/logout/status and extends OIDC issuer interfaces for PKCE + nonce support.
- Updates the Angular app to initiate login via
/api/v2/auth/login, handle cookie-based sessions, and refresh tokens on 401 responses. - Removes obsolete frontend implicit-flow/token/nonce plumbing (env OIDC URLs, token service, check-token interceptor, cookie DI tokens) and adjusts affected UI/components/tests.
Reviewed changes
Copilot reviewed 39 out of 39 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| modules/web/src/test/services/auth-mock.ts | Updates auth mock to match new logout response shape. |
| modules/web/src/environments/environment.ts | Removes implicit-flow OIDC environment fields. |
| modules/web/src/environments/environment.prod.ts | Removes implicit-flow OIDC environment fields. |
| modules/web/src/environments/environment.e2e.ts | Removes implicit-flow OIDC environment fields. |
| modules/web/src/environments/environment.e2e.mock.ts | Removes implicit-flow OIDC environment fields. |
| modules/web/src/environments/environment.e2e.local.ts | Removes implicit-flow OIDC environment fields. |
| modules/web/src/app/shared/model/Config.ts | Removes OIDC provider enum no longer used by frontend. |
| modules/web/src/app/shared/components/openstack-credentials/default/component.ts | Uses UserService for default username instead of auth token parsing. |
| modules/web/src/app/project/component.ts | Removes cookie-based “autoredirect” landing redirect logic. |
| modules/web/src/app/pages/frontpage/template.html | Login button now points directly to /api/v2/auth/login. |
| modules/web/src/app/pages/frontpage/component.ts | Simplifies frontpage login initiation for code flow. |
| modules/web/src/app/pages/api-docs/component.ts | Removes bearer-token injection; relies on cookie auth and simplifies back navigation. |
| modules/web/src/app/module.ts | Ensures auth init runs before user/datacenter/config initialization. |
| modules/web/src/app/core/services/user.ts | Switches user initialization gating from token service to auth status. |
| modules/web/src/app/core/services/user.spec.ts | Updates tests after removal of TokenService/cookie DI usage. |
| modules/web/src/app/core/services/token.ts | Removes legacy client-side JWT parsing/token storage service. |
| modules/web/src/app/core/services/auth/service.ts | Replaces implicit-flow logic with status/refresh/logout API calls + refresh scheduling. |
| modules/web/src/app/core/services/auth/service.spec.ts | Updates tests after removal of TokenService/cookie DI usage. |
| modules/web/src/app/core/services/auth/guard.ts | Route guard now redirects unauthenticated users to / instead of starting implicit login. |
| modules/web/src/app/core/module.ts | Removes TokenService, cookie DI provider, and CheckTokenInterceptor. |
| modules/web/src/app/core/interceptors/index.ts | Removes export of deleted CheckTokenInterceptor. |
| modules/web/src/app/core/interceptors/error-notifications.ts | Adds refresh-cookie-missing error text to the silenced error list and improves plain-text error handling. |
| modules/web/src/app/core/interceptors/check-token.ts | Removes legacy interceptor that redirected when token missing/expired. |
| modules/web/src/app/core/interceptors/auth.ts | Implements 401-triggered refresh + retry using cookie-based session. |
| modules/web/src/app/core/components/user-panel/component.ts | Logout now uses backend response redirect instead of frontend OIDC-logout composition. |
| modules/web/src/app/core/components/user-panel/component.spec.ts | Adjusts logout test expectations for new redirect-based behavior. |
| modules/web/src/app/config.ts | Removes cookie DI token/constants used by the legacy implicit flow. |
| modules/web/cypress/intercept/root.ts | Removes nonce cookie setup from e2e helpers. |
| modules/api/pkg/provider/auth/types/types.go | Extends OIDC issuer Exchange for optional PKCE verifier; adds nonce to token claims. |
| modules/api/pkg/handler/v2/routing.go | Wires OIDCIssuerVerifier into v2 routing. |
| modules/api/pkg/handler/v2/routes_v2.go | Registers the new authflow routes under v2. |
| modules/api/pkg/handler/v2/authflow/routing.go | Adds router install for `/auth/login |
| modules/api/pkg/handler/v2/authflow/handler.go | Implements authorization code + PKCE flow, secure state cookie, nonce validation, refresh, logout redirect, and cookie chunking. |
| modules/api/pkg/handler/test/fake_auth.go | Updates fake issuer Exchange signature for PKCE-compatible interface. |
| modules/api/pkg/handler/routing.go | Adds OIDCIssuerVerifier to routing params for v2 authflow wiring. |
| modules/api/pkg/handler/auth/oidc.go | Adds nonce claim extraction and PKCE verifier support to token exchange. |
| modules/api/hack/run-api.sh | Updates dev run script to use a different OIDC authenticator client ID. |
| modules/api/cmd/kubermatic-api/options.go | Adds provider field for OIDCIssuerVerifier. |
| modules/api/cmd/kubermatic-api/main.go | Passes OIDCIssuerVerifier into handler routing params. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| clearNamedCookie(w, idTokenCookieName, "/", secureMode) | ||
| clearNamedCookie(w, refreshTokenCookieName, "/api/v2/auth", secureMode) | ||
| // Clear potential chunks (token-1, token-2, etc.) | ||
| for i := range maxNumOfTokenCookies { |
| nonce := apimachineryrand.String(apimachineryrand.IntnRange(10, 15)) | ||
| state := apimachineryrand.String(apimachineryrand.IntnRange(10, 15)) | ||
|
|
| setNamedCookie(w, idTokenCookieName, tokenValue, "/", tokenMaxAge, oidcConfig.CookieSecureMode) | ||
| } else { | ||
| // Chunked cookies: token-1, token-2, etc. | ||
| chunks := chunkString(tokenValue, maxCookieSize) |
| tokenValue, err := a.tokenExtractor.Extract(r) | ||
| if err != nil { | ||
| http.Error(w, "missing token cookie", http.StatusBadRequest) | ||
| return | ||
| } |
| // 8. Redirect to frontend landing page. | ||
| userInfo, err := a.userProvider.UserByEmail(r.Context(), claims.Email) | ||
| if err != nil { | ||
| fmt.Println("failed to get user info", err) |
| } | ||
| } | ||
| if rawNonce, found := claims["nonce"]; found { | ||
| oidcClaims.Nonce = rawNonce.(string) |
| return next.handle(req).pipe( | ||
| catchError((error: HttpErrorResponse) => { | ||
| // Only handle 401s for API requests, and don't retry the refresh call itself. | ||
| if (error.status === HttpStatusCode.Unauthorized && !req.url.startsWith(this._refreshUrl)) { |
| func (a *authHandler) loginHandler() http.Handler { | ||
| return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
| oidcConfig := a.oidcIssuerVerifier.OIDCConfig() | ||
|
|
||
| nonce := apimachineryrand.String(apimachineryrand.IntnRange(10, 15)) | ||
| state := apimachineryrand.String(apimachineryrand.IntnRange(10, 15)) | ||
|
|
||
| codeVerifier := oauth2.GenerateVerifier() | ||
|
|
| this._expiresAt = response?.expires_at ?? 0; | ||
| const msUntilExpiry = this._expiresAt * SECONDS_TO_MS - Date.now(); | ||
| if (msUntilExpiry <= this._refreshBufferMs) { | ||
| this._refreshToken(resolve); | ||
| return; | ||
| } |
| // Fallback: check if token cookie is readable (non-HttpOnly environments like e2e tests). | ||
| // In production the cookie is HttpOnly so this returns false, relying on the status endpoint. | ||
| return !!this._cookieService.get('token'); |
What this PR does / why we need it:
Re-submission of #7960 with production fixes applied.
Introduces OAuth Authorization Code flow with PKCE and replaces the in-memory state store with a signed and encrypted cookie, allowing the flow to work reliably across multiple replicas.
Fixes incorrect redirect URI handling behind TLS-terminating ingress by deriving the correct scheme and host from the configured redirect URI instead of the request.
Implements proper OIDC nonce handling by including it in the auth request and validating it against the ID token to meet the OIDC specification.
Which issue(s) this PR fixes:
Fixes #7940
What type of PR is this?
/kind feature
Does this PR introduce a user-facing change? Then add your Release Note here:
Documentation:
Test issue: