Skip to content

Commit 87d0cf5

Browse files
nbudinclaude
andcommitted
Fix OAuth callback login failures
Two changes to address users landing on a blank /oauth/callback page: 1. Guard the GraphQLNotAuthenticatedErrorEvent handler so it's a no-op when the current page is /oauth/callback. A NOT_AUTHENTICATED error from any in-flight GraphQL query was able to call initiateAuthentication() mid-exchange, overwriting the PKCE data in sessionStorage and sending the user back to the OAuth server in a redirect loop. 2. Add a "Try logging in again" button to the OAuthCallback error UI so that when the exchange fails (e.g. missing PKCE state after a stale page load), users get a one-click path to restart the login flow rather than a dead-end error message. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 422332f commit 87d0cf5

4 files changed

Lines changed: 26 additions & 3 deletions

File tree

app/javascript/Authentication/OAuthCallback.tsx

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,18 @@ function OAuthCallback() {
3232
handleCallback();
3333
}, [authenticationManager]);
3434

35+
const handleRetry = async () => {
36+
setProcessing(true);
37+
setError(null);
38+
try {
39+
const { redirectUrl } = await authenticationManager.initiateAuthentication('/');
40+
window.location.href = redirectUrl.toString();
41+
} catch (e) {
42+
setError(e instanceof Error ? e.message : 'Authentication failed');
43+
setProcessing(false);
44+
}
45+
};
46+
3547
if (processing) {
3648
return (
3749
<div className="container mt-4">
@@ -50,9 +62,14 @@ function OAuthCallback() {
5062
<div className="alert alert-danger">
5163
<h4>{t('authentication.oauthCallback.authenticationError')}</h4>
5264
<ErrorDisplay stringError={error} />
53-
<Link className="btn btn-primary mt-3" to="/">
54-
{t('authentication.oauthCallback.returnToHome')}
55-
</Link>
65+
<div className="mt-3 d-flex gap-2">
66+
<button className="btn btn-primary" onClick={handleRetry}>
67+
{t('authentication.oauthCallback.retryLogin')}
68+
</button>
69+
<Link className="btn btn-outline-secondary" to="/">
70+
{t('authentication.oauthCallback.returnToHome')}
71+
</Link>
72+
</div>
5673
</div>
5774
</div>
5875
);

app/javascript/packs/application.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ const bootstrapPromise: Promise<Bootstrap> = (async () => {
5858
const client = buildBrowserApolloClient(authenticityTokensManager, authManager);
5959

6060
window.addEventListener(GraphQLNotAuthenticatedErrorEvent.type, async () => {
61+
// Don't interrupt an in-progress OAuth exchange on the callback page itself
62+
if (window.location.pathname === '/oauth/callback') {
63+
return;
64+
}
6165
const { redirectUrl } = await authManager.initiateAuthentication(window.location.href);
6266
window.location.href = redirectUrl.toString();
6367
});

locales/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,7 @@
633633
"oauthCallback": {
634634
"authenticationError": "Authentication Error",
635635
"completingLogin": "Completing login...",
636+
"retryLogin": "Try logging in again",
636637
"returnToHome": "Return to Home"
637638
},
638639
"passwordInput": {

locales/es.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
"oauthCallback": {
7474
"authenticationError": "Authentication Error",
7575
"completingLogin": "Completing login...",
76+
"retryLogin": "Try logging in again",
7677
"returnToHome": "Return to Home"
7778
},
7879
"passwordInput": {

0 commit comments

Comments
 (0)