diff --git a/coderd/externalauth.go b/coderd/externalauth.go index 774a5f860397d..b1b7acc8bc449 100644 --- a/coderd/externalauth.go +++ b/coderd/externalauth.go @@ -268,8 +268,9 @@ func (api *API) externalAuthCallback(externalAuthConfig *externalauth.Config) ht redirect := state.Redirect if redirect == "" { - // This is a nicely rendered screen on the frontend - redirect = fmt.Sprintf("/external-auth/%s", externalAuthConfig.ID) + // This is a nicely rendered screen on the frontend. Passing the query param lets the + // FE know not to enter the authentication loop again, and instead display an error. + redirect = fmt.Sprintf("/external-auth/%s?redirected=true", externalAuthConfig.ID) } http.Redirect(rw, r, redirect, http.StatusTemporaryRedirect) } diff --git a/site/src/pages/ExternalAuthPage/ExternalAuthPage.tsx b/site/src/pages/ExternalAuthPage/ExternalAuthPage.tsx index d42004b758914..8cc955b596d4c 100644 --- a/site/src/pages/ExternalAuthPage/ExternalAuthPage.tsx +++ b/site/src/pages/ExternalAuthPage/ExternalAuthPage.tsx @@ -6,16 +6,21 @@ import { } from "api/api"; import { usePermissions } from "hooks"; import { type FC } from "react"; -import { useParams } from "react-router-dom"; +import { useParams, useSearchParams } from "react-router-dom"; import ExternalAuthPageView from "./ExternalAuthPageView"; import { ApiErrorResponse } from "api/errors"; import { isAxiosError } from "axios"; +import Box from "@mui/material/Box"; +import Button from "@mui/material/Button"; +import { SignInLayout } from "components/SignInLayout/SignInLayout"; +import { Welcome } from "components/Welcome/Welcome"; const ExternalAuthPage: FC = () => { const { provider } = useParams(); if (!provider) { throw new Error("provider must exist"); } + const [searchParams] = useSearchParams(); const permissions = usePermissions(); const queryClient = useQueryClient(); const getExternalAuthProviderQuery = useQuery({ @@ -72,6 +77,35 @@ const ExternalAuthPage: FC = () => { !getExternalAuthProviderQuery.data.authenticated && !getExternalAuthProviderQuery.data.device ) { + const redirectedParam = searchParams?.get("redirected"); + if (redirectedParam && redirectedParam.toLowerCase() === "true") { + // The auth flow redirected the user here. If we redirect back to the + // callback, that resets the flow and we'll end up in an infinite loop. + // So instead, show an error, as the user expects to be authenticated at + // this point. + // TODO: Unsure what to do about the device auth flow, should we also + // show an error there? + return ( + + + + Attempted to validate the user's oauth access token from the + authentication flow. This situation may occur as a result of an + external authentication provider misconfiguration. Verify the + external authentication validation URL is accurately configured. + +
+ +
+ ); + } window.location.href = `/external-auth/${provider}/callback`; return null; }