Skip to content

Adjust login url handling for authenticated users#3079

Merged
samuelwei merged 11 commits into
developfrom
3078-auth-redirect-doesnt-work-if-already-logged-in
Apr 28, 2026
Merged

Adjust login url handling for authenticated users#3079
samuelwei merged 11 commits into
developfrom
3078-auth-redirect-doesnt-work-if-already-logged-in

Conversation

@samuelwei
Copy link
Copy Markdown
Collaborator

@samuelwei samuelwei commented Apr 23, 2026

Fixes #3078

Type

  • Bugfix
  • Feature
  • Documentation
  • Refactoring (e.g. Style updates, Test implementation, etc.)
  • Other (please describe):

Checklist

  • Code updated to current develop branch head
  • Passes CI checks
  • Is a part of an issue
  • Tests added for the bugfix or newly implemented feature, describe below why if not
  • Changelog is updated
  • Documentation of code and features exists

Changes

  • Direct OIDC / Shibboleth redirect calls with a redirect query parameter will now redirect authenticated users to the requested url
  • Allow frontend login url calls, user is redirected to requested redirect route, fallback to room overview

Other information

Summary by CodeRabbit

  • Improvements

    • Skip login/external-login UI when already authenticated; redirect to rooms or provided redirect. Respect a no_message flag to suppress post-login toast. Normalized redirect handling and updated route props.
  • Validation

    • New request validation for OIDC/Shibboleth redirect & callback endpoints that redirect to external_login?error=invalid_request and surface a localized "invalid request" message.
  • Tests

    • Expanded backend and frontend tests for authenticated redirects, invalid parameters, and end-to-end external-login flows.

Users are no longer redirected to a fixed location via the guest middleware, instead they are redirected to the external login route. We don't redirect directly to prevent an open redirect. The redirect via the external_login route is happening via vue router and therefore only allows valid SPA route redirects
@samuelwei samuelwei linked an issue Apr 23, 2026 that may be closed by this pull request
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 23, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds typed FormRequest classes and a shared REDIRECT_URL constant; redirect endpoints short‑circuit authenticated users to /external_login?no_message=1 (preserving an optional redirect), callbacks use the constant/session redirect, frontend normalizes redirect and no_message, and tests and translations updated accordingly.

Changes

Cohort / File(s) Summary
Auth Controllers & Client
app/Auth/OIDC/OIDCController.php, app/Auth/Shibboleth/ShibbolethController.php, app/Auth/OIDC/OIDCProvider.php, app/Auth/OIDC/OpenIDConnectClient.php
Add protected const string REDIRECT_URL = '/external_login'; exclude redirect route from guest middleware; redirect short‑circuits authenticated users to REDIRECT_URL?no_message=1 (optionally appending encoded redirect); callback redirects to REDIRECT_URL using session redirect_url; OpenIDConnectClient::authenticate() now returns void and throws on missing code.
FormRequest Classes
app/Auth/OIDC/OIDCRedirectRequest.php, app/Auth/OIDC/OIDCCallbackRequest.php, app/Auth/Shibboleth/ShibbolethRedirectRequest.php, app/Auth/Shibboleth/ShibbolethCallbackRequest.php
Add typed Laravel FormRequest classes validating inputs (e.g., redirect, code, state, error, error_description); on validation failure increment Prometheus login_failed_total with provider label, log invalid keys, and redirect to /external_login?error=invalid_request.
Frontend Router & Views
resources/js/router.js, resources/js/views/ExternalLogin.vue, resources/js/views/Login.vue
Remove guestsOnly meta from /login; map /external_login query no_message → boolean noMessage; add noMessage prop, normalize redirect query into scalar normalizedRedirect, and auto‑redirect authenticated users to normalized target or rooms index.
Backend Feature Tests
tests/Backend/Feature/api/v1/OIDCTest.php, tests/Backend/Feature/api/v1/ShibbolethTest.php
Add tests asserting logged‑in short‑circuit to /external_login?no_message=1 (with encoded redirect) and invalid‑parameter flows redirect to /external_login?error=invalid_request while emitting error logs; add Shibboleth callback end‑to‑end style test.
Frontend E2E Tests
tests/Frontend/e2e/Login.cy.js
Update logged‑in behavior tests to expect immediate redirects (e.g., /login/rooms or /admin), assert admin page and toast behavior, add external‑login error tests and no_message suppression checks.
Localization & Changelog
lang/en/auth.php, CHANGELOG.md
Add error.invalid_request translation and update Unreleased changelog entries referencing related issue/PRs.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

Complete - Waiting for review, refactor

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.48% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Adjust login url handling for authenticated users' clearly and concisely describes the main change: modifying login URL behavior when users are already authenticated.
Description check ✅ Passed The PR description is mostly complete, with type identified, checklist items marked, and changes listed. However, it references only #3078 while the PR objectives indicate #3079 is also involved, and lacks specific implementation details.
Linked Issues check ✅ Passed The PR successfully addresses issue #3078 by ensuring OIDC/Shibboleth redirect endpoints and frontend login pages respect the redirect parameter for authenticated users, with comprehensive test coverage and URL normalization logic across all affected components.
Out of Scope Changes check ✅ Passed All changes are directly related to the linked issue objectives: form request validation, URI handling, authenticated-user redirect logic, test coverage, and changelog updates. No out-of-scope modifications were found.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 3078-auth-redirect-doesnt-work-if-already-logged-in

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 and usage tips.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 23, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.77%. Comparing base (9ae6aaa) to head (68a226f).
⚠️ Report is 1 commits behind head on develop.

Additional details and impacted files
@@            Coverage Diff             @@
##             develop    #3079   +/-   ##
==========================================
  Coverage      96.77%   96.77%           
- Complexity      1924     1935   +11     
==========================================
  Files            457      461    +4     
  Lines          13132    13147   +15     
  Branches        2133     2143   +10     
==========================================
+ Hits           12708    12723   +15     
  Misses           424      424           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
resources/js/views/ExternalLogin.vue (1)

84-88: ⚠️ Potential issue | 🟡 Minor

Consider validating route.query.redirect before router.push (pre-existing, but exposure broadened by this PR).

This line is not changed, but the PR widens the set of flows that land here with an attacker-influenceable redirect query (authenticated users can now reach /external_login?redirect=… directly). If route.query.redirect is something like //evil.com/x, http://evil.com, or an array, behavior may be unsafe or buggy:

  • Protocol-relative (//host) values may be accepted by some history implementations as external navigations.
  • Repeated ?redirect=a&redirect=b becomes an array — router.push(['…','…']) is not a valid argument.

A simple guard would make the contract explicit:

🛡️ Suggested guard
-    if (route.query.redirect !== undefined) {
-      router.push(route.query.redirect);
+    const redirect = route.query.redirect;
+    if (typeof redirect === "string" && /^\/(?![/\\])/.test(redirect)) {
+      router.push(redirect);
     } else {
       router.push({ name: "rooms.index" });
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@resources/js/views/ExternalLogin.vue` around lines 84 - 88, The redirect
handling in ExternalLogin.vue currently pushes route.query.redirect without
validation; ensure route.query.redirect is a safe single relative path before
calling router.push. Update the logic around route.query.redirect: verify it's a
string (if it's an array, pick the first element or ignore), reject values that
start with '//' or contain a scheme like 'http:' or 'https:', and only allow
paths that begin with a single '/' (or otherwise match an explicit
internal-route whitelist); if the value fails validation, fall back to
router.push({ name: "rooms.index" }). Use the existing route.query.redirect and
router.push symbols when locating and changing the code.
🧹 Nitpick comments (2)
app/Auth/Shibboleth/ShibbolethController.php (1)

28-37: Minor: use lowercase auth() helper and consider simplifying query construction.

Two small points:

  1. Auth() with an uppercase A is unconventional — Laravel's global helper is lowercased auth(). PHP function names are case-insensitive so it works, but it's inconsistent with the rest of the codebase and with the OIDC controller (which uses the same casing here). Prefer auth()->check() for consistency.
  2. The two-branch construction can be collapsed by building the query array upfront:
♻️ Suggested simplification
-        if (Auth()->check()) {
-            $uri = Uri::of(self::REDIRECT_URL)->withQuery(['no_message' => true]);
-            if ($request->query('redirect')) {
-                return redirect($uri
-                    ->withQuery(['redirect' => $request->query('redirect')])
-                    ->value());
-            }
-
-            return redirect($uri->value());
-        }
+        if (auth()->check()) {
+            $query = ['no_message' => true];
+            if ($request->query('redirect')) {
+                $query['redirect'] = $request->query('redirect');
+            }
+
+            return redirect(Uri::of(self::REDIRECT_URL)->withQuery($query)->value());
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/Auth/Shibboleth/ShibbolethController.php` around lines 28 - 37, Replace
the unconventional Auth() call with the lowercase helper auth() in the
conditional (use auth()->check()) and simplify the redirect logic in
ShibbolethController by building a single query array first (include
'no_message' => true and conditionally add 'redirect' =>
$request->query('redirect')), then apply that query to
Uri::of(self::REDIRECT_URL) and return a single redirect(...) using the
uri->value(); this collapses the two-branch return into one and keeps URI
construction centralized.
app/Auth/OIDC/OIDCController.php (1)

30-40: Duplicated short-circuit logic with ShibbolethController.

This authenticated-user short-circuit is byte-for-byte identical to the new block in app/Auth/Shibboleth/ShibbolethController.php (lines 28–37). Consider extracting it into a small trait, a shared helper, or a base controller method to avoid drift (e.g., if the no_message contract, URL, or validation rules change later, both places must be updated in lockstep).

Same Auth() casing nit as noted on the Shibboleth side — prefer auth()->check().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/Auth/OIDC/OIDCController.php` around lines 30 - 40, Extract the
duplicated authenticated-user short-circuit into a shared helper (trait or base
controller method) and replace the inline block in both OIDCController and
ShibbolethController with a call to that helper; specifically, move the logic
that checks auth()->check(), constructs
Uri::of(self::REDIRECT_URL)->withQuery(['no_message' => true]), handles optional
$request->query('redirect') and returns the redirect into a single method (e.g.,
handleAuthenticatedRedirect(Request $request) in a trait or base class), update
both controllers to call that method, and also change Auth()->check() to
auth()->check() to fix the casing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/Auth/Shibboleth/ShibbolethController.php`:
- Around line 28-39: The redirect query parameter is forwarded unvalidated from
ShibbolethController::redirect (and similarly OIDCController::redirect) to
/external_login and then used by the frontend (ExternalLogin.vue and Login.vue)
in router.push(), creating an open-redirect risk; fix this by validating and
normalizing the redirect before forwarding and before calling router.push(): in
ShibbolethController::redirect (and OIDCController::redirect) reject or remove
any redirect that contains '//' or '/\' or that is not a single('/')-prefixed
path or a recognized SPA route name, and only forward whitelisted values to
/external_login; in ExternalLogin.vue and Login.vue, enforce the same
whitelist/validation on route.query.redirect (or require a route name/object)
and refuse/purge malformed values before calling router.push() to ensure only
known SPA routes or safe single-segment paths are allowed.

---

Outside diff comments:
In `@resources/js/views/ExternalLogin.vue`:
- Around line 84-88: The redirect handling in ExternalLogin.vue currently pushes
route.query.redirect without validation; ensure route.query.redirect is a safe
single relative path before calling router.push. Update the logic around
route.query.redirect: verify it's a string (if it's an array, pick the first
element or ignore), reject values that start with '//' or contain a scheme like
'http:' or 'https:', and only allow paths that begin with a single '/' (or
otherwise match an explicit internal-route whitelist); if the value fails
validation, fall back to router.push({ name: "rooms.index" }). Use the existing
route.query.redirect and router.push symbols when locating and changing the
code.

---

Nitpick comments:
In `@app/Auth/OIDC/OIDCController.php`:
- Around line 30-40: Extract the duplicated authenticated-user short-circuit
into a shared helper (trait or base controller method) and replace the inline
block in both OIDCController and ShibbolethController with a call to that
helper; specifically, move the logic that checks auth()->check(), constructs
Uri::of(self::REDIRECT_URL)->withQuery(['no_message' => true]), handles optional
$request->query('redirect') and returns the redirect into a single method (e.g.,
handleAuthenticatedRedirect(Request $request) in a trait or base class), update
both controllers to call that method, and also change Auth()->check() to
auth()->check() to fix the casing.

In `@app/Auth/Shibboleth/ShibbolethController.php`:
- Around line 28-37: Replace the unconventional Auth() call with the lowercase
helper auth() in the conditional (use auth()->check()) and simplify the redirect
logic in ShibbolethController by building a single query array first (include
'no_message' => true and conditionally add 'redirect' =>
$request->query('redirect')), then apply that query to
Uri::of(self::REDIRECT_URL) and return a single redirect(...) using the
uri->value(); this collapses the two-branch return into one and keeps URI
construction centralized.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: e78846f0-7717-4c03-99b3-a04e2b7ecec3

📥 Commits

Reviewing files that changed from the base of the PR and between 9ae6aaa and be9fb0e.

📒 Files selected for processing (7)
  • app/Auth/OIDC/OIDCController.php
  • app/Auth/Shibboleth/ShibbolethController.php
  • resources/js/router.js
  • resources/js/views/ExternalLogin.vue
  • tests/Backend/Feature/api/v1/OIDCTest.php
  • tests/Backend/Feature/api/v1/ShibbolethTest.php
  • tests/Frontend/e2e/Login.cy.js

Comment thread app/Auth/Shibboleth/ShibbolethController.php
@cypress
Copy link
Copy Markdown

cypress Bot commented Apr 23, 2026

PILOS    Run #2997

Run Properties:  status check passed Passed #2997  •  git commit 68a226feee: Adjust login url handling for authenticated users
Project PILOS
Branch Review 3078-auth-redirect-doesnt-work-if-already-logged-in
Run status status check passed Passed #2997
Run duration 07m 50s
Commit git commit 68a226feee: Adjust login url handling for authenticated users
Committer Samuel Weirich
View all properties for this run ↗︎

Test results
Tests that failed  Failures 0
Tests that were flaky  Flaky 0
Tests that did not run due to a developer annotating a test with .skip  Pending 0
Tests that did not run due to a failure in a mocha hook  Skipped 0
Tests that passed  Passing 631
View all changes introduced in this branch ↗︎

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
resources/js/views/Login.vue (1)

123-145: Handle array and empty-string cases for route.query.redirect across all usages.

route.query.redirect is typed as LocationQueryValue | LocationQueryValue[] by Vue Router, so URLs with duplicate keys (e.g., ?redirect=/a&redirect=/b) make it an array. This breaks three locations:

  1. Line 128 (onMounted): router.push(route.query.redirect) with array input will fail/warn
  2. Line 149–150 (oidcRedirectUrl): encodeURIComponent(route.query.redirect) converts array to comma-separated string ("a,b")
  3. Lines 156–157 (shibbolethRedirectUrl): same issue as above
  4. Line 174 (handleLogin): same pattern as line 128

Additionally, empty-string redirects (?redirect=) pass the !== undefined check and push an empty path.

Extract route.query.redirect once as a normalized value and reuse it:

♻️ Suggested refactoring
+const normalizedRedirect = computed(() => {
+  const value = route.query.redirect;
+  const redirectValue = Array.isArray(value) ? value[0] : value;
+  return redirectValue || undefined;
+});

 const oidcRedirectUrl = computed(() => {
   const url = "/auth/oidc/redirect";
-  return route.query.redirect
-    ? url + "?redirect=" + encodeURIComponent(route.query.redirect)
+  return normalizedRedirect.value
+    ? url + "?redirect=" + encodeURIComponent(normalizedRedirect.value)
     : url;
 });

 const shibbolethRedirectUrl = computed(() => {
   const url = "/auth/shibboleth/redirect";
-  return route.query.redirect
-    ? url + "?redirect=" + encodeURIComponent(route.query.redirect)
+  return normalizedRedirect.value
+    ? url + "?redirect=" + encodeURIComponent(normalizedRedirect.value)
     : url;
 });

 onMounted(() => {
   if (authStore.isAuthenticated) {
-    if (route.query.redirect !== undefined) {
-      router.push(route.query.redirect);
+    if (normalizedRedirect.value) {
+      router.push(normalizedRedirect.value);
     } else {
       router.push({ name: "rooms.index" });
     }
     return;
   }
   // ...
 });

 async function handleLogin({ data, id }) {
   // ...
   if (normalizedRedirect.value) {
     await router.push(normalizedRedirect.value);
   } else {
     await router.push({ name: "rooms.index" });
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@resources/js/views/Login.vue` around lines 123 - 145, Normalize
route.query.redirect once (e.g., getNormalizedRedirect) and use that everywhere:
in the onMounted redirect logic, in oidcRedirectUrl and shibbolethRedirectUrl
construction, and in handleLogin. Specifically, read route.query.redirect, if
it's an array take the first non-empty element, if it's a string treat empty
string as undefined, and otherwise treat undefined as no redirect; then use the
normalized string for router.push and for encodeURIComponent calls so arrays and
empty values no longer cause incorrect pushes or comma-joined encodings. Ensure
you reference the existing symbols route.query.redirect, onMounted redirect
block, oidcRedirectUrl, shibbolethRedirectUrl, and handleLogin when applying the
change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@resources/js/views/Login.vue`:
- Around line 123-145: Normalize route.query.redirect once (e.g.,
getNormalizedRedirect) and use that everywhere: in the onMounted redirect logic,
in oidcRedirectUrl and shibbolethRedirectUrl construction, and in handleLogin.
Specifically, read route.query.redirect, if it's an array take the first
non-empty element, if it's a string treat empty string as undefined, and
otherwise treat undefined as no redirect; then use the normalized string for
router.push and for encodeURIComponent calls so arrays and empty values no
longer cause incorrect pushes or comma-joined encodings. Ensure you reference
the existing symbols route.query.redirect, onMounted redirect block,
oidcRedirectUrl, shibbolethRedirectUrl, and handleLogin when applying the
change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: fbd445ee-57c9-406a-bca6-1c96a3298847

📥 Commits

Reviewing files that changed from the base of the PR and between be9fb0e and e7e08e8.

📒 Files selected for processing (5)
  • resources/js/router.js
  • resources/js/views/Login.vue
  • tests/Backend/Feature/api/v1/OIDCTest.php
  • tests/Backend/Feature/api/v1/ShibbolethTest.php
  • tests/Frontend/e2e/Login.cy.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/Backend/Feature/api/v1/ShibbolethTest.php
  • tests/Backend/Feature/api/v1/OIDCTest.php

@samuelwei samuelwei changed the title Adjust redirect handling for OIDC/Shibboleth Adjust login url handling for authenticated users Apr 24, 2026
@samuelwei
Copy link
Copy Markdown
Collaborator Author

@tibroc @lkiesow: Could you please verify that this PR fixes your issue

@samuelwei samuelwei requested a review from Sabr1n4W April 24, 2026 13:30
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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)
app/Auth/OIDC/OpenIDConnectClient.php (1)

181-199: ⚠️ Potential issue | 🟡 Minor

Fix stale PHPDoc return contract for authenticate().

The method now returns void, but the docblock still describes a boolean return contract. Please update it to avoid misleading IDE/static-analysis hints.

✏️ Proposed docblock fix
-     * `@return` bool Returns true if authentication is successful, false if the code is missing
+     * `@return` void
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/Auth/OIDC/OpenIDConnectClient.php` around lines 181 - 199, Update the
PHPDoc for the OpenIDConnectClient::authenticate(Request $request) method to
reflect its actual signature: change the `@return` line from "bool Returns true if
authentication is successful, false if the code is missing" to "@return void"
and adjust the short description accordingly (e.g., remove the boolean return
description); also tidy any duplicated `@throws` entries if present so the
docblock accurately matches the method signature and exceptions declared in the
code.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@app/Auth/OIDC/OpenIDConnectClient.php`:
- Around line 181-199: Update the PHPDoc for the
OpenIDConnectClient::authenticate(Request $request) method to reflect its actual
signature: change the `@return` line from "bool Returns true if authentication is
successful, false if the code is missing" to "@return void" and adjust the short
description accordingly (e.g., remove the boolean return description); also tidy
any duplicated `@throws` entries if present so the docblock accurately matches the
method signature and exceptions declared in the code.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 14498866-054e-45a1-938e-ee95bd881d38

📥 Commits

Reviewing files that changed from the base of the PR and between e7e08e8 and 6ce7053.

📒 Files selected for processing (11)
  • CHANGELOG.md
  • app/Auth/OIDC/OIDCCallbackRequest.php
  • app/Auth/OIDC/OIDCController.php
  • app/Auth/OIDC/OIDCProvider.php
  • app/Auth/OIDC/OIDCRedirectRequest.php
  • app/Auth/OIDC/OpenIDConnectClient.php
  • app/Auth/Shibboleth/ShibbolethCallbackRequest.php
  • app/Auth/Shibboleth/ShibbolethController.php
  • app/Auth/Shibboleth/ShibbolethRedirectRequest.php
  • resources/js/views/ExternalLogin.vue
  • resources/js/views/Login.vue
✅ Files skipped from review due to trivial changes (1)
  • CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • resources/js/views/ExternalLogin.vue
  • resources/js/views/Login.vue

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
app/Auth/OIDC/OIDCCallbackRequest.php (1)

26-35: Consider extracting shared failedValidation logic to a base class.

Both OIDCCallbackRequest and ShibbolethRedirectRequest have nearly identical failedValidation implementations, differing only in the provider label ('oidc' vs 'shibboleth') and log message prefix.

♻️ Proposed refactor using a base class

Create a shared base class:

<?php

declare(strict_types=1);

namespace App\Auth;

use App\Prometheus\Counter;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Log;

abstract class ExternalAuthRequest extends FormRequest
{
    protected string $providerName;
    protected string $logPrefix;

    protected function failedValidation(Validator $validator): void
    {
        $keys = $validator->errors()->keys();
        $message = 'invalid request parameter(s): '.implode(',', $keys);

        Counter::get('login_failed_total')->inc($this->providerName);
        Log::error($this->logPrefix.$message);

        parent::failedValidation($validator);
    }
}

Then simplify the subclasses:

class OIDCCallbackRequest extends ExternalAuthRequest
{
    protected string $providerName = 'oidc';
    protected string $logPrefix = 'OIDC login callback failed: ';
    protected $redirect = '/external_login?error=invalid_request';

    public function rules(): array
    {
        return [
            'code' => ['string'],
            'state' => ['string'],
            'error' => ['string'],
            'error_description' => ['string'],
        ];
    }
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/Auth/OIDC/OIDCCallbackRequest.php` around lines 26 - 35, Extract the
duplicate failedValidation logic into a new base class (e.g.,
App\Auth\ExternalAuthRequest) that extends FormRequest and implements
failedValidation(Validator $validator) using
Counter::get('login_failed_total')->inc($this->providerName) and
Log::error($this->logPrefix.$message); add protected properties string
$providerName and string $logPrefix that subclasses set; then have
OIDCCallbackRequest and ShibbolethRedirectRequest extend ExternalAuthRequest,
set their providerName ('oidc' / 'shibboleth') and logPrefix ('OIDC login
callback failed: ' / 'Shibboleth login callback failed: '), and keep their
existing rules and $redirect overrides.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@app/Auth/OIDC/OIDCCallbackRequest.php`:
- Around line 26-35: Extract the duplicate failedValidation logic into a new
base class (e.g., App\Auth\ExternalAuthRequest) that extends FormRequest and
implements failedValidation(Validator $validator) using
Counter::get('login_failed_total')->inc($this->providerName) and
Log::error($this->logPrefix.$message); add protected properties string
$providerName and string $logPrefix that subclasses set; then have
OIDCCallbackRequest and ShibbolethRedirectRequest extend ExternalAuthRequest,
set their providerName ('oidc' / 'shibboleth') and logPrefix ('OIDC login
callback failed: ' / 'Shibboleth login callback failed: '), and keep their
existing rules and $redirect overrides.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 9b67af27-da9e-4957-8b6a-057e49181b00

📥 Commits

Reviewing files that changed from the base of the PR and between 6ce7053 and 859082d.

📒 Files selected for processing (10)
  • app/Auth/OIDC/OIDCCallbackRequest.php
  • app/Auth/OIDC/OIDCRedirectRequest.php
  • app/Auth/OIDC/OpenIDConnectClient.php
  • app/Auth/Shibboleth/ShibbolethCallbackRequest.php
  • app/Auth/Shibboleth/ShibbolethRedirectRequest.php
  • lang/en/auth.php
  • resources/js/views/ExternalLogin.vue
  • tests/Backend/Feature/api/v1/OIDCTest.php
  • tests/Backend/Feature/api/v1/ShibbolethTest.php
  • tests/Frontend/e2e/Login.cy.js
✅ Files skipped from review due to trivial changes (1)
  • lang/en/auth.php
🚧 Files skipped from review as they are similar to previous changes (5)
  • app/Auth/OIDC/OIDCRedirectRequest.php
  • app/Auth/Shibboleth/ShibbolethCallbackRequest.php
  • resources/js/views/ExternalLogin.vue
  • tests/Backend/Feature/api/v1/OIDCTest.php
  • tests/Backend/Feature/api/v1/ShibbolethTest.php

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/Frontend/e2e/Login.cy.js (1)

743-788: Consider table-driven tests for repeated external-login error cases.

The five nearly identical error tests are good coverage, but a small data-driven loop would cut duplication and ease future additions.

Refactor sketch
+  [
+    "invalid_request",
+    "openid_connect_network_exception",
+    "openid_connect_exception",
+    "missing_attributes",
+    "shibboleth_session_duplicate_exception",
+  ].forEach((errorKey) => {
+    it(`external login callback ${errorKey}`, function () {
+      cy.visit(`/external_login?error=${errorKey}`);
+      cy.contains("auth.error.login_failed").should("be.visible");
+      cy.contains(`auth.error.${errorKey}`).should("be.visible");
+      cy.get('a[data-test="login-button"]')
+        .should("be.visible")
+        .should("have.text", "auth.login")
+        .should("have.attr", "href", "/login");
+    });
+  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/Frontend/e2e/Login.cy.js` around lines 743 - 788, Replace the five
near-identical it(...) blocks (e.g., "external login callback invalid request",
"external login callback openid connect network exception", "external login
callback openid connect exception") with a single table-driven loop: define an
array of cases where each entry maps the error query param (e.g.,
invalid_request, openid_connect_network_exception, openid_connect_exception) to
its expected translation key (e.g., auth.error.invalid_request,
auth.error.openid_connect_network_exception,
auth.error.openid_connect_exception), then forEach over that array and create an
it(...) for each case that calls cy.visit("/external_login?error="+error),
asserts cy.contains("auth.error.login_failed").should("be.visible"), asserts
cy.contains(expectedKey).should("be.visible"), and asserts
cy.get('a[data-test="login-button"]').should("be.visible").should("have.text","auth.login").should("have.attr","href","/login");
this removes duplication while preserving the existing cy.visit, cy.contains and
cy.get checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@resources/js/views/ExternalLogin.vue`:
- Line 2: Move the v-if attribute before the class attribute on the root div in
ExternalLogin.vue to satisfy the frontend style rule: locate the element "<div
class="container" v-if="!authStore.isAuthenticated">" and reorder the attributes
so it reads with v-if first (e.g., <div v-if="!authStore.isAuthenticated"
class="container">).

---

Nitpick comments:
In `@tests/Frontend/e2e/Login.cy.js`:
- Around line 743-788: Replace the five near-identical it(...) blocks (e.g.,
"external login callback invalid request", "external login callback openid
connect network exception", "external login callback openid connect exception")
with a single table-driven loop: define an array of cases where each entry maps
the error query param (e.g., invalid_request, openid_connect_network_exception,
openid_connect_exception) to its expected translation key (e.g.,
auth.error.invalid_request, auth.error.openid_connect_network_exception,
auth.error.openid_connect_exception), then forEach over that array and create an
it(...) for each case that calls cy.visit("/external_login?error="+error),
asserts cy.contains("auth.error.login_failed").should("be.visible"), asserts
cy.contains(expectedKey).should("be.visible"), and asserts
cy.get('a[data-test="login-button"]').should("be.visible").should("have.text","auth.login").should("have.attr","href","/login");
this removes duplication while preserving the existing cy.visit, cy.contains and
cy.get checks.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: e07e4fd3-2841-46b8-8844-c3feac4f5197

📥 Commits

Reviewing files that changed from the base of the PR and between 859082d and 7ff9e84.

📒 Files selected for processing (2)
  • resources/js/views/ExternalLogin.vue
  • tests/Frontend/e2e/Login.cy.js

Comment thread resources/js/views/ExternalLogin.vue Outdated
@samuelwei samuelwei merged commit ab6bb7e into develop Apr 28, 2026
23 checks passed
@samuelwei samuelwei deleted the 3078-auth-redirect-doesnt-work-if-already-logged-in branch April 28, 2026 14:39
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.

Auth redirect doesn't work if already logged in

2 participants