Skip to content

Frontend/auth updates#1727

Merged
isabeleliassen merged 21 commits into
csg-org:mainfrom
InspiringApps:frontend/auth-updates
Jul 21, 2026
Merged

Frontend/auth updates#1727
isabeleliassen merged 21 commits into
csg-org:mainfrom
InspiringApps:frontend/auth-updates

Conversation

@jsandoval81

@jsandoval81 jsandoval81 commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Requirements List

  • Deploy backend updates if testing pre-merge

Description List

  • Moved auth-related helper functions into their own utility module
  • Separated the AuthCallback pages into separate pages per userType/compact
  • Updated the login state parameter to be a random-value CSRF protection
  • Added PKCE to the login flows
  • Updated the Cognito app clients to accept the new separate redirect_uri landing pages

Testing List

  • yarn test:unit:all should run without errors or warnings
  • yarn serve should run without errors or warnings
  • yarn build should run without errors or warnings
  • Code review
  • Testing
    • Deploy the backend updates to your sandbox / test environment
    • Login to each compact, ensuring that correct logins are successful with no regressions
    • Ensure user sessions remain valid in the browser, with no regressions to page refreshes or auto logout

Closes #1641

Summary by CodeRabbit

  • New Features
    • Added dedicated OAuth callback routes/pages for staff (JCC, Cosmetology, Social Work) and licensee.
    • OAuth redirect/callback URLs now support role- and app-specific callback paths.
    • Hosted sign-in and password-reset flows now include CSRF validation and PKCE protection, with improved redirect/error handling.
  • Bug Fixes
    • Improved hosted and local UI callback URL generation, including correct production vs local behavior.
  • Tests
    • Updated and expanded OAuth/callback assertions and Postman redirect URI expectations.

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

OAuth authentication is reorganized around shared CSRF and PKCE utilities. Cognito clients, hosted-login URLs, and frontend routes now use application-specific callback paths with dedicated callback pages and shared token exchange handling.

Changes

OAuth callback flow

Layer / File(s) Summary
Backend callback contracts
backend/common-cdk/..., backend/*/stacks/..., backend/*/tests/..., backend/*/docs/postman/...
Cognito UI clients accept configurable callback paths, and application-specific redirect URLs are applied across stacks, tests, and Postman collections.
Authentication utility foundation
webroot/src/app.config.ts, webroot/src/utils/auth.ts, webroot/tests/helpers/setup.ts, webroot/tsconfig.json, webroot/vue.config.js, webroot/src/styles.common/...
Authentication exports move into @utils/auth, which provides CSRF state, PKCE, Cognito configuration, hosted-login URI generation, auto-logout configuration, and shared auth-error styling.
Authentication import migration
webroot/src/components/..., webroot/src/models/..., webroot/src/network/..., webroot/src/router/..., webroot/src/store/...
Frontend modules and tests import authentication values from the new utility module while retaining application configuration imports for non-auth constants.
PKCE-protected login initiation
webroot/src/pages/PublicDashboard/..., webroot/src/pages/MfaReset*/*
Login and licensee MFA reset flows generate CSRF and PKCE values before constructing hosted-login URLs.
Callback handling and routing
webroot/src/pages/AuthCallback/..., webroot/src/router/routes.ts
A shared callback mixin validates state, exchanges authorization codes with PKCE, and supports dedicated callback pages for staff and licensee contexts.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant PublicDashboard
  participant Cognito
  participant AuthCallback
  PublicDashboard->>Browser: Generate CSRF state and PKCE challenge
  Browser->>Cognito: Request hosted login with state and code challenge
  Cognito-->>AuthCallback: Redirect with authorization code and state
  AuthCallback->>Cognito: Exchange code with PKCE verifier
  Cognito-->>AuthCallback: Return tokens
Loading

Possibly related PRs

Suggested reviewers: landonshumway-ia, jlkravitz, isabeleliassen, rmolinares

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is related but too generic to clearly describe the main auth change. Use a concise, specific title like 'Add PKCE and CSRF protection to Cognito auth flows'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The description matches the template structure and covers requirements, changes, testing, and the linked issue.
Linked Issues check ✅ Passed [#1641] The PR adds PKCE, CSRF nonce validation, split callback pages, shared auth helpers, and updated callback allowlists as requested.
Out of Scope Changes check ✅ Passed The changes shown are all tied to auth flow refactoring and callback handling, with no clear unrelated scope creep.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (6)
webroot/src/pages/AuthCallback/StaffCosmo/StaffCosmo.vue (1)

8-20: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extracting shared error markup.

This template is duplicated verbatim across StaffCosmo.vue, StaffJcc.vue, and (per the stack outline) StaffSocialWork.vue/LicenseeJcc.vue. Each new page only needs to render this same error card, so extracting a shared AuthCallbackError component (or a slot in a common wrapper) would reduce duplication and centralize future error-copy changes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webroot/src/pages/AuthCallback/StaffCosmo/StaffCosmo.vue` around lines 8 -
20, Extract the duplicated error card markup from StaffCosmo.vue, StaffJcc.vue,
StaffSocialWork.vue, and LicenseeJcc.vue into a shared AuthCallbackError
component or common wrapper. Preserve the existing isError conditional,
translations, styling classes, and DashboardPublic router link, then replace
each page’s inline markup with the shared component.
backend/social-work-app/docs/internal/postman/postman-collection.json (1)

164-167: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Exercise the PKCE and CSRF flow in these requests.

The authorize requests omit state, code_challenge, and code_challenge_method; the token request omits code_verifier. Update the collection’s pre-request setup and exchange request so Postman validates the security contract introduced here.

Also applies to: 240-247, 312-319

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/social-work-app/docs/internal/postman/postman-collection.json` around
lines 164 - 167, Update the Postman authorization-flow requests and shared
pre-request setup to generate and send a CSRF state and PKCE code challenge with
code_challenge_method, then persist the matching code_verifier for the flow. Add
the stored state, challenge parameters, and verifier to the corresponding token
exchange requests, including the sections identified by the repeated
authorize/token request blocks.
webroot/src/pages/AuthCallback/_mixins/handler.mixin.ts (3)

138-138: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove commented-out toNative export.

The commented-out // export default toNative(MixinAuthCallbackHandler); is dead code. The direct class export on line 140 is correct for use with mixins() in subclasses like StaffCosmo.ts.

🧹 Proposed cleanup
-
-// export default toNative(MixinAuthCallbackHandler);
-
 export default MixinAuthCallbackHandler;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webroot/src/pages/AuthCallback/_mixins/handler.mixin.ts` at line 138, Remove
the commented-out toNative export near MixinAuthCallbackHandler, keeping the
active direct class export unchanged for mixins() consumers.

84-84: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add type annotations for cognitoAuthDomain and cognitoClientId parameters.

Both getTokens and fetchCognitoTokens leave cognitoAuthDomain and cognitoClientId untyped, defaulting to any. Adding string annotations would improve type safety and consistency with the typed authType parameter.

🔧 Proposed fix
-    async getTokens(appMode: AppModes, authType: AuthTypes, cognitoAuthDomain, cognitoClientId): Promise<void> {
+    async getTokens(appMode: AppModes, authType: AuthTypes, cognitoAuthDomain: string, cognitoClientId: string): Promise<void> {
-    async fetchCognitoTokens(authType: AuthTypes, cognitoAuthDomain, cognitoClientId): Promise<void> {
+    async fetchCognitoTokens(authType: AuthTypes, cognitoAuthDomain: string, cognitoClientId: string): Promise<void> {

Also applies to: 99-99

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webroot/src/pages/AuthCallback/_mixins/handler.mixin.ts` at line 84, Add
explicit string type annotations to the cognitoAuthDomain and cognitoClientId
parameters in both getTokens and fetchCognitoTokens, preserving the existing
appMode and authType types.

99-117: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add a timeout and error logging to the Cognito token exchange.

The axios.post() call has no explicit timeout — if Cognito is slow or unresponsive, the user is stuck on a blank callback page until the browser's default timeout fires. Additionally, the .catch() on line 88 silently swallows the error with no logging, making production debugging difficult.

⏱️ Proposed fix: add timeout and error logging
         params.append('code_verifier', consumePkceCodeVerifier() || '');

-        const { data } = await axios.post(`${cognitoAuthDomain}/oauth2/token`, params);
+        const { data } = await axios.post(`${cognitoAuthDomain}/oauth2/token`, params, {
+            timeout: 15000,
+        });

And in getTokens():

-        await this.fetchCognitoTokens(authType, cognitoAuthDomain, cognitoClientId).catch(() => {
+        await this.fetchCognitoTokens(authType, cognitoAuthDomain, cognitoClientId).catch((err) => {
+            console.error('Auth callback token exchange failed:', err);
             this.isError = true;
         });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webroot/src/pages/AuthCallback/_mixins/handler.mixin.ts` around lines 99 -
117, Update fetchCognitoTokens to pass an explicit timeout option to the
axios.post token request, using the project’s standard request timeout if
available. In getTokens, replace the silent catch with error logging that
includes the caught error and relevant Cognito token-exchange context before
preserving the existing failure handling.
webroot/src/pages/AuthCallback/StaffSocialWork/StaffSocialWork.spec.ts (1)

12-19: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Smoke test only covers mounting; consider testing auth callback behavior.

The test verifies the component mounts but doesn't exercise the CSRF state verification, token exchange, or error display paths that are the core purpose of this callback page. The mixin's created() will set isError = true when verifyCsrfState() fails (no stored state in test), which is untested.

💡 Suggested additional test cases
it('should display error when CSRF state verification fails', async () => {
    const wrapper = await mountShallow(StaffSocialWork);
    await wrapper.vm.$nextTick();
    expect(wrapper.findComponent(StaffSocialWork).exists()).to.equal(true);
    // Assert isError is true and error UI is rendered
});

it('should verify CSRF state and exchange tokens on valid callback', async () => {
    // Set up sessionStorage with matching state, route query with code+state
    // Assert token exchange was called and redirect occurred
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webroot/src/pages/AuthCallback/StaffSocialWork/StaffSocialWork.spec.ts`
around lines 12 - 19, Expand the StaffSocialWork tests beyond mounting by
covering the auth callback behavior in the component’s created lifecycle and
mixin. Add a test for failed verifyCsrfState() that waits for $nextTick and
asserts isError plus the rendered error UI, and a valid callback test that
configures matching sessionStorage state and route code/state, then verifies
token exchange and redirect behavior using the relevant mocked methods.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@webroot/src/pages/AuthCallback/_mixins/mixins.spec.ts`:
- Around line 43-50: Update the “should successfully get tokens” test to stub
the axios.post token exchange with a successful response and spy on the router
navigation method used by getTokens, then assert it is called with the expected
route instead of checking history.state.replaced. Ensure no real request to
http://localhost/oauth2/token can occur.

In `@webroot/src/pages/MfaResetConfirmLicensee/MfaResetConfirmLicensee.ts`:
- Around line 51-61: Handle failures from createPkceChallenge() within
MfaResetConfirmLicensee.created() using try/catch, preserve the existing CSRF
initialization, and surface a clear user-facing error or disable login when PKCE
generation fails so navigation cannot occur with an empty code_challenge.
- Line 92: Update the getHostedLoginUri call in MfaResetConfirmLicensee to pass
AppModes.JCC instead of this.appMode, ensuring the licensee callback path always
targets the supported JCC route.

In `@webroot/src/utils/auth.ts`:
- Around line 133-218: Resolve the LICENSEE callback mismatch in
getHostedLoginUri: either add the missing LicenseeCosmo and LicenseeSocialWork
callback pages and router entries for /auth/callback/licensee/cosmo and
/auth/callback/licensee/socialwork, or restrict callback-path generation so
LICENSEE only produces supported routes such as /auth/callback/licensee/jcc.

---

Nitpick comments:
In `@backend/social-work-app/docs/internal/postman/postman-collection.json`:
- Around line 164-167: Update the Postman authorization-flow requests and shared
pre-request setup to generate and send a CSRF state and PKCE code challenge with
code_challenge_method, then persist the matching code_verifier for the flow. Add
the stored state, challenge parameters, and verifier to the corresponding token
exchange requests, including the sections identified by the repeated
authorize/token request blocks.

In `@webroot/src/pages/AuthCallback/_mixins/handler.mixin.ts`:
- Line 138: Remove the commented-out toNative export near
MixinAuthCallbackHandler, keeping the active direct class export unchanged for
mixins() consumers.
- Line 84: Add explicit string type annotations to the cognitoAuthDomain and
cognitoClientId parameters in both getTokens and fetchCognitoTokens, preserving
the existing appMode and authType types.
- Around line 99-117: Update fetchCognitoTokens to pass an explicit timeout
option to the axios.post token request, using the project’s standard request
timeout if available. In getTokens, replace the silent catch with error logging
that includes the caught error and relevant Cognito token-exchange context
before preserving the existing failure handling.

In `@webroot/src/pages/AuthCallback/StaffCosmo/StaffCosmo.vue`:
- Around line 8-20: Extract the duplicated error card markup from
StaffCosmo.vue, StaffJcc.vue, StaffSocialWork.vue, and LicenseeJcc.vue into a
shared AuthCallbackError component or common wrapper. Preserve the existing
isError conditional, translations, styling classes, and DashboardPublic router
link, then replace each page’s inline markup with the shared component.

In `@webroot/src/pages/AuthCallback/StaffSocialWork/StaffSocialWork.spec.ts`:
- Around line 12-19: Expand the StaffSocialWork tests beyond mounting by
covering the auth callback behavior in the component’s created lifecycle and
mixin. Add a test for failed verifyCsrfState() that waits for $nextTick and
asserts isError plus the rendered error UI, and a valid callback test that
configures matching sessionStorage state and route code/state, then verifies
token exchange and redirect behavior using the relevant mocked methods.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 85950346-9adc-4ed8-8df5-ef52b99643b1

📥 Commits

Reviewing files that changed from the base of the PR and between a8102ac and 3a1a9d2.

📒 Files selected for processing (80)
  • backend/common-cdk/common_constructs/user_pool.py
  • backend/common-cdk/tests/test_user_pool.py
  • backend/compact-connect/docs/internal/postman/postman-collection.json
  • backend/compact-connect/docs/postman/postman-collection.json
  • backend/compact-connect/stacks/persistent_stack/staff_users.py
  • backend/compact-connect/stacks/provider_users/provider_users.py
  • backend/compact-connect/tests/app/base.py
  • backend/cosmetology-app/docs/internal/postman/postman-collection.json
  • backend/cosmetology-app/docs/postman/postman-collection.json
  • backend/cosmetology-app/docs/search-internal/postman/postman-collection.json
  • backend/cosmetology-app/stacks/persistent_stack/staff_users.py
  • backend/cosmetology-app/tests/app/base.py
  • backend/social-work-app/docs/internal/postman/postman-collection.json
  • backend/social-work-app/docs/postman/postman-collection.json
  • backend/social-work-app/docs/search-internal/postman/postman-collection.json
  • backend/social-work-app/stacks/persistent_stack/staff_users.py
  • backend/social-work-app/tests/app/base.py
  • webroot/src/app.config.ts
  • webroot/src/components/App/App.spec.ts
  • webroot/src/components/App/App.ts
  • webroot/src/components/AutoLogout/AutoLogout.ts
  • webroot/src/components/ChangePassword/ChangePassword.ts
  • webroot/src/components/MilitaryAffiliationInfoBlock/MilitaryAffiliationInfoBlock.ts
  • webroot/src/components/Page/PageMainNav/PageMainNav.ts
  • webroot/src/components/StateSettingsList/StateSettingsList.ts
  • webroot/src/components/UserAccount/UserAccount.ts
  • webroot/src/models/LicenseeUser/LicenseeUser.model.spec.ts
  • webroot/src/models/LicenseeUser/LicenseeUser.model.ts
  • webroot/src/models/StaffUser/StaffUser.model.spec.ts
  • webroot/src/models/StaffUser/StaffUser.model.ts
  • webroot/src/models/User/User.model.ts
  • webroot/src/network/licenseApi/data.api.ts
  • webroot/src/network/licenseApi/interceptors.ts
  • webroot/src/network/searchApi/interceptors.ts
  • webroot/src/network/stateApi/interceptors.ts
  • webroot/src/network/userApi/interceptors.ts
  • webroot/src/pages/AuthCallback/AuthCallback.spec.ts
  • webroot/src/pages/AuthCallback/AuthCallback.ts
  • webroot/src/pages/AuthCallback/LicenseeJcc/LicenseeJcc.less
  • webroot/src/pages/AuthCallback/LicenseeJcc/LicenseeJcc.spec.ts
  • webroot/src/pages/AuthCallback/LicenseeJcc/LicenseeJcc.ts
  • webroot/src/pages/AuthCallback/LicenseeJcc/LicenseeJcc.vue
  • webroot/src/pages/AuthCallback/StaffCosmo/StaffCosmo.less
  • webroot/src/pages/AuthCallback/StaffCosmo/StaffCosmo.spec.ts
  • webroot/src/pages/AuthCallback/StaffCosmo/StaffCosmo.ts
  • webroot/src/pages/AuthCallback/StaffCosmo/StaffCosmo.vue
  • webroot/src/pages/AuthCallback/StaffJcc/StaffJcc.less
  • webroot/src/pages/AuthCallback/StaffJcc/StaffJcc.spec.ts
  • webroot/src/pages/AuthCallback/StaffJcc/StaffJcc.ts
  • webroot/src/pages/AuthCallback/StaffJcc/StaffJcc.vue
  • webroot/src/pages/AuthCallback/StaffSocialWork/StaffSocialWork.less
  • webroot/src/pages/AuthCallback/StaffSocialWork/StaffSocialWork.spec.ts
  • webroot/src/pages/AuthCallback/StaffSocialWork/StaffSocialWork.ts
  • webroot/src/pages/AuthCallback/StaffSocialWork/StaffSocialWork.vue
  • webroot/src/pages/AuthCallback/_mixins/handler.mixin.ts
  • webroot/src/pages/AuthCallback/_mixins/mixins.spec.ts
  • webroot/src/pages/CompactSettings/CompactSettings.ts
  • webroot/src/pages/Home/Home.ts
  • webroot/src/pages/Logout/Logout.ts
  • webroot/src/pages/MfaResetConfirmLicensee/MfaResetConfirmLicensee.ts
  • webroot/src/pages/MfaResetStartLicensee/MfaResetStartLicensee.ts
  • webroot/src/pages/PrivilegeDetail/PrivilegeDetail.ts
  • webroot/src/pages/PublicDashboard/PublicDashboard.spec.ts
  • webroot/src/pages/PublicDashboard/PublicDashboard.ts
  • webroot/src/pages/StateSettings/StateSettings.ts
  • webroot/src/router/index.ts
  • webroot/src/router/routes.ts
  • webroot/src/store/global/global.mutations.ts
  • webroot/src/store/global/global.spec.ts
  • webroot/src/store/global/global.state.ts
  • webroot/src/store/user/user.actions.ts
  • webroot/src/store/user/user.mutations.ts
  • webroot/src/store/user/user.spec.ts
  • webroot/src/store/user/user.state.ts
  • webroot/src/styles.common/_mixins.less
  • webroot/src/styles.common/mixins/auth-error.less
  • webroot/src/utils/auth.ts
  • webroot/tests/helpers/setup.ts
  • webroot/tsconfig.json
  • webroot/vue.config.js
💤 Files with no reviewable changes (2)
  • webroot/src/pages/AuthCallback/AuthCallback.spec.ts
  • webroot/src/pages/AuthCallback/AuthCallback.ts

Comment thread webroot/src/pages/AuthCallback/_mixins/mixins.spec.ts
Comment thread webroot/src/utils/auth.ts
@jsandoval81
jsandoval81 requested a review from rmolinares July 13, 2026 20:01

@landonshumway-ia landonshumway-ia left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The backend changes for this look clean, the only thing missing is we need to keep the legacy callback URL in place to avoid downtime for JCC.

Comment thread backend/common-cdk/common_constructs/user_pool.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/common-cdk/common_constructs/user_pool.py (1)

259-281: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Avoid adding duplicate or unexpected callback URLs.

When callback_path uses its default /auth/callback, lines 274 and 276 append the same URL twice. For custom paths, the implementation adds a third legacy URL, while backend/common-cdk/tests/test_user_pool.py expects only the custom domain and localhost URLs. This will break the supplied callback URL assertions and may complicate Cognito client configuration.

Only append the legacy URL when callback_path != '/auth/callback', or update the contract and tests deliberately.

Proposed fix
         if ui_domain_name is not None:
             callback_urls.append(f'https://{ui_domain_name}{callback_path}')
-            # TODO - remove after cutover to custom callback paths is deployed `#noqa`: FIX002
-            callback_urls.append(f'https://{ui_domain_name}/auth/callback')
+            if callback_path != '/auth/callback':
+                # TODO - remove after cutover to custom callback paths is deployed `#noqa`: FIX002
+                callback_urls.append(f'https://{ui_domain_name}/auth/callback')
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/common-cdk/common_constructs/user_pool.py` around lines 259 - 281,
The callback URL construction in the app-client creation method duplicates the
default URL and adds an unexpected legacy URL for custom paths. Update the logic
around callback_path so the legacy /auth/callback URL is appended only when
callback_path differs from /auth/callback, preserving the custom-domain and
localhost URLs expected by the existing contract.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@backend/common-cdk/common_constructs/user_pool.py`:
- Around line 259-281: The callback URL construction in the app-client creation
method duplicates the default URL and adds an unexpected legacy URL for custom
paths. Update the logic around callback_path so the legacy /auth/callback URL is
appended only when callback_path differs from /auth/callback, preserving the
custom-domain and localhost URLs expected by the existing contract.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5efa48dd-8038-4063-b301-8fd64ea9b5b8

📥 Commits

Reviewing files that changed from the base of the PR and between 18286cc and 3336cca.

📒 Files selected for processing (1)
  • backend/common-cdk/common_constructs/user_pool.py

@jsandoval81
jsandoval81 requested a review from jlkravitz July 14, 2026 15:58
@jsandoval81

Copy link
Copy Markdown
Collaborator Author

@jlkravitz This is ready for your review.

@jlkravitz jlkravitz left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple of small comments! This is great stuff. Thank you.

Comment thread webroot/src/pages/MfaResetConfirmLicensee/MfaResetConfirmLicensee.ts Outdated
Comment thread webroot/src/pages/MfaResetStartLicensee/MfaResetStartLicensee.ts
Comment thread webroot/src/pages/AuthCallback/_mixins/mixins.spec.ts
Comment thread webroot/src/utils/auth.ts
Comment thread backend/common-cdk/common_constructs/user_pool.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
webroot/src/pages/MfaResetConfirmLicensee/MfaResetConfirmLicensee.ts (1)

51-61: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Unhandled rejection in async created() if createPkceChallenge() fails.

If the Web Crypto API throws (e.g., crypto.subtle.digest is unavailable), the rejection is unhandled and this.pkceChallenge remains ''. The user could then click "go to login" and get a URI with an empty code_challenge, causing a confusing token-exchange failure rather than a clear error message.

🛡️ Proposed fix to add a try/catch in created()
     async created(): Promise<void> {
         this.csrfState = createAuthCsrfState();
-        this.pkceChallenge = await createPkceChallenge();
+        try {
+            this.pkceChallenge = await createPkceChallenge();
+        } catch {
+            this.serverMessage = this.$t('serverErrors.networkError');
+            this.isLoading = false;
+        }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webroot/src/pages/MfaResetConfirmLicensee/MfaResetConfirmLicensee.ts` around
lines 51 - 61, Update the async created() lifecycle method to catch failures
from createPkceChallenge(), prevent an unhandled rejection, and present a clear
user-facing error state instead of allowing login with an empty pkceChallenge.
Preserve successful CSRF and PKCE initialization.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@webroot/src/pages/MfaResetConfirmLicensee/MfaResetConfirmLicensee.ts`:
- Around line 51-61: Update the async created() lifecycle method to catch
failures from createPkceChallenge(), prevent an unhandled rejection, and present
a clear user-facing error state instead of allowing login with an empty
pkceChallenge. Preserve successful CSRF and PKCE initialization.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 81144063-f91a-4314-a385-02178bc687cd

📥 Commits

Reviewing files that changed from the base of the PR and between 3336cca and 4ff3941.

📒 Files selected for processing (7)
  • backend/common-cdk/common_constructs/user_pool.py
  • backend/common-cdk/tests/test_user_pool.py
  • backend/compact-connect/tests/app/base.py
  • backend/cosmetology-app/tests/app/base.py
  • backend/social-work-app/tests/app/base.py
  • webroot/src/pages/AuthCallback/_mixins/mixins.spec.ts
  • webroot/src/pages/MfaResetConfirmLicensee/MfaResetConfirmLicensee.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • webroot/src/pages/AuthCallback/_mixins/mixins.spec.ts
  • backend/cosmetology-app/tests/app/base.py
  • backend/social-work-app/tests/app/base.py

@jsandoval81
jsandoval81 requested a review from jlkravitz July 20, 2026 21:28
@jsandoval81

Copy link
Copy Markdown
Collaborator Author

@jlkravitz This is ready for your re-review.

@jlkravitz jlkravitz left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@isabeleliassen Good to merge!

@isabeleliassen

Copy link
Copy Markdown
Collaborator

@jlkravitz looks like two conversations need to be resolved

@jlkravitz

Copy link
Copy Markdown
Collaborator

@isabeleliassen Whoops, done!

@isabeleliassen
isabeleliassen merged commit e0e3f8a into csg-org:main Jul 21, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Security/Medium] Cognito Hosted UI OAuth flow lacks PKCE and state CSRF validation

5 participants