Skip to content

Commit 76968e6

Browse files
authored
fix: better error handling in oauth flows (#862)
1 parent 9eab039 commit 76968e6

File tree

5 files changed

+123
-8
lines changed

5 files changed

+123
-8
lines changed

lib/build/index2.js

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/build/oauth2providerprebuiltui.js

Lines changed: 15 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/ts/recipe/authRecipe/components/feature/authPage/authPage.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,8 @@ const AuthPageInner: React.FC<AuthPageProps> = (props) => {
197197
}
198198
},
199199
() => {
200-
return clearQueryParams(["loginChallenge"]);
200+
clearQueryParams(["loginChallenge"]);
201+
setError("SOMETHING_WENT_WRONG_ERROR");
201202
}
202203
);
203204

lib/ts/recipe/oauth2provider/components/features/oauth2LogoutScreen/index.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import SuperTokens from "../../../../../superTokens";
2525
import UI from "../../../../../ui";
2626
import { useUserContext } from "../../../../../usercontext";
2727
import { getQueryParams, getTenantIdFromQueryParams, useRethrowInRender } from "../../../../../utils";
28-
import { SessionContext } from "../../../../session";
28+
import { doesSessionExist, SessionContext } from "../../../../session";
2929
import OAuth2Provider from "../../../recipe";
3030
import { OAuth2LogoutScreenTheme } from "../../themes/oauth2LogoutScreen";
3131
import { defaultTranslationsOAuth2Provider } from "../../themes/translations";
@@ -75,7 +75,16 @@ export const OAuth2LogoutScreen: React.FC<Prop> = (props) => {
7575
userContext
7676
);
7777
} catch (err: any) {
78-
rethrowInRender(err);
78+
if (!(await doesSessionExist(userContext))) {
79+
void SuperTokens.getInstanceOrThrow()
80+
.redirectToAuth({
81+
userContext,
82+
redirectBack: false,
83+
})
84+
.catch(rethrowInRender);
85+
} else {
86+
rethrowInRender(err);
87+
}
7988
}
8089
}, [logoutChallenge, navigate, props.recipe, userContext, rethrowInRender]);
8190

test/end-to-end/oauth2provider.test.js

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import {
4040
waitForSTElement,
4141
isOauth2Supported,
4242
setupBrowser,
43+
getGeneralError,
4344
} from "../helpers";
4445
import fetch from "isomorphic-fetch";
4546

@@ -48,6 +49,7 @@ import {
4849
TEST_SERVER_BASE_URL,
4950
SIGN_OUT_API,
5051
TEST_APPLICATION_SERVER_BASE_URL,
52+
SOMETHING_WENT_WRONG_ERROR,
5153
} from "../constants";
5254

5355
// We do no thave to use a separate domain for the oauth2 client, since the way we are testing
@@ -120,6 +122,36 @@ describe("SuperTokens OAuth2Provider", function () {
120122
await removeOAuth2ClientInfo(page);
121123
});
122124

125+
it("should clear invalid/expired loginChallenge from the url and show an error", async function () {
126+
const { client } = await createOAuth2Client({
127+
scope: "offline_access profile openid email",
128+
redirectUris: [`${TEST_CLIENT_BASE_URL}/oauth/callback`],
129+
tokenEndpointAuthMethod: "none",
130+
grantTypes: ["authorization_code", "refresh_token"],
131+
responseTypes: ["code", "id_token"],
132+
});
133+
134+
await setOAuth2ClientInfo(page, client.clientId);
135+
136+
await Promise.all([
137+
page.waitForNavigation({ waitUntil: "networkidle0" }),
138+
page.goto(`${TEST_CLIENT_BASE_URL}/oauth/login`),
139+
]);
140+
141+
let loginButton = await getOAuth2LoginButton(page);
142+
await loginButton.click();
143+
144+
await waitForUrl(page, "/auth");
145+
146+
await Promise.all([
147+
page.waitForNavigation({ waitUntil: "networkidle0" }),
148+
page.goto(page.url().replace("loginChallenge=", "loginChallenge=nooope")),
149+
]);
150+
151+
const error = await getGeneralError(page);
152+
assert.strictEqual(error, SOMETHING_WENT_WRONG_ERROR);
153+
});
154+
123155
it("should successfully complete the OAuth2 flow", async function () {
124156
const { client } = await createOAuth2Client({
125157
scope: "offline_access profile openid email",
@@ -165,6 +197,67 @@ describe("SuperTokens OAuth2Provider", function () {
165197
await waitForUrl(page, "/oauth/callback");
166198
});
167199

200+
it("should handle invalid logoutChallenge in the OAuth2 Logout flow", async function () {
201+
const postLogoutRedirectUri = `${TEST_CLIENT_BASE_URL}/oauth/login?logout=true`;
202+
203+
const { client } = await createOAuth2Client({
204+
scope: "offline_access profile openid email",
205+
redirectUris: [`${TEST_CLIENT_BASE_URL}/oauth/callback`],
206+
postLogoutRedirectUris: [postLogoutRedirectUri],
207+
accessTokenStrategy: "jwt",
208+
tokenEndpointAuthMethod: "none",
209+
grantTypes: ["authorization_code", "refresh_token"],
210+
responseTypes: ["code", "id_token"],
211+
skipConsent: true,
212+
});
213+
214+
await setOAuth2ClientInfo(
215+
page,
216+
client.clientId,
217+
undefined,
218+
undefined,
219+
undefined,
220+
JSON.stringify({
221+
post_logout_redirect_uri: postLogoutRedirectUri,
222+
})
223+
);
224+
225+
await Promise.all([
226+
page.waitForNavigation({ waitUntil: "networkidle0" }),
227+
page.goto(`${TEST_CLIENT_BASE_URL}/oauth/login`),
228+
]);
229+
230+
let loginButton = await getOAuth2LoginButton(page);
231+
await loginButton.click();
232+
233+
await waitForUrl(page, "/auth");
234+
235+
await toggleSignInSignUp(page);
236+
const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email: getTestEmail() });
237+
await signUp(page, fieldValues, postValues, "emailpassword");
238+
239+
await waitForUrl(page, "/oauth/callback");
240+
241+
// Logout
242+
const logoutButton = await getOAuth2LogoutButton(page, "redirect");
243+
await logoutButton.click();
244+
245+
await waitForUrl(page, "/auth/oauth/logout");
246+
247+
await Promise.all([
248+
page.waitForNavigation({ waitUntil: "networkidle0" }),
249+
page.goto(page.url().replace("logoutChallenge=", "logoutChallenge=nooope")),
250+
]);
251+
252+
// Click the Logout button on the provider website
253+
const stLogoutButton = await waitForSTElement(page, "[data-supertokens~=button]");
254+
await stLogoutButton.click();
255+
256+
// Ensure the we get redirected to the auth page
257+
await waitForUrl(page, "/auth/");
258+
await waitForSTElement(page);
259+
});
260+
168261
it("should successfully complete the OAuth2 Logout flow", async function () {
169262
const postLogoutRedirectUri = `${TEST_CLIENT_BASE_URL}/oauth/login?logout=true`;
170263

0 commit comments

Comments
 (0)