Skip to content

feat: oidc authorize post#823

Draft
steveiliop56 wants to merge 2 commits intomainfrom
feat/oidc-authorize-post
Draft

feat: oidc authorize post#823
steveiliop56 wants to merge 2 commits intomainfrom
feat/oidc-authorize-post

Conversation

@steveiliop56
Copy link
Copy Markdown
Member

@steveiliop56 steveiliop56 commented Apr 27, 2026

image

Summary by CodeRabbit

  • New Features

    • Added support for POST requests to the /authorize endpoint with form-encoded parameter handling and HTTP redirects
    • Configured frontend development server proxy for /authorize requests to backend
  • Tests

    • Added test coverage for POST /authorize endpoint with form-encoded parameters and redirect validation

@dosubot dosubot Bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Apr 27, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 27, 2026

📝 Walkthrough

Walkthrough

This PR implements support for POST-based OAuth2/OIDC authorization requests. It adds a new POST /authorize endpoint that accepts form-encoded parameters, extracts them from the request body, and redirects to the standard GET-based authorize flow. The middleware is updated to pass through POST requests to the authorize path, and the frontend Vite proxy is configured to handle the new endpoint.

Changes

Cohort / File(s) Summary
OIDC Controller Logic
internal/controller/oidc_controller.go
Injects *gin.Engine, stores it on controller, registers POST /authorize endpoint with AuthorizePseudoPost handler that reads request body and redirects to issuer/authorize with query parameters or logs error.
Authorization Service
internal/service/oidc_service.go
Adds url struct tags to all existing AuthorizeRequest fields for URL/query parsing; introduces new Request field for request-object parameter support.
Bootstrap & Middleware
internal/bootstrap/router_bootstrap.go, internal/middleware/ui_middleware.go
Updates NewOIDCController invocation to pass gin engine; modifies middleware to intercept and forward POST requests to /authorize paths via c.Next().
Tests & Frontend Config
internal/controller/oidc_controller_test.go, frontend/vite.config.ts
Adds test for POST /authorize with form-encoded parameters and validates 302 redirect; configures Vite dev proxy for /authorize with GET bypass to serve SPA.

Sequence Diagram

sequenceDiagram
    participant Client as Browser/Client
    participant Middleware as UI Middleware
    participant Controller as OIDC Controller
    participant Backend as Authorization Backend
    
    Client->>Middleware: POST /authorize<br/>(form-encoded params)
    Middleware->>Middleware: Detect POST to /authorize
    Middleware->>Controller: c.Next() - Forward to handler
    Controller->>Controller: AuthorizePseudoPost:<br/>Read request body
    Controller->>Controller: Extract params from body
    Controller->>Client: 302 Redirect to GET /authorize<br/>with query parameters
    Client->>Backend: GET /authorize?scope=...&client_id=...
    Backend-->>Client: Authorization response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Suggested labels

size:M

Suggested reviewers

  • scottmckendry
  • Rycochet

Poem

🐰 A POST request hops along the way,
The controller reads it (hip hooray!),
Then redirects with a gentle bound,
To GET the auth on solid ground,
Query strings dance in the URL's play! 🎭

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding POST support for OIDC authorize endpoint, which is the primary focus of all changes across the codebase.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/oidc-authorize-post

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

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: 2

🧹 Nitpick comments (1)
internal/controller/oidc_controller_test.go (1)

872-874: Avoid asserting the full redirect URL string.

This couples the test to query ordering and exact escaping. Parsing Location and asserting the individual parameters would make the test less brittle.

Refactor sketch
 				location := recorder.Header().Get("Location")
 				assert.NotEmpty(t, location)
-				assert.Equal(t, "https://tinyauth.example.com/authorize?client_id=some-client-id&code_challenge=some-challenge&code_challenge_method=plain&nonce=some-nonce&redirect_uri=https%3A%2F%2Ftest.example.com%2Fcallback&response_type=code&scope=openid&state=some-state", location)
+
+				redirectURL, err := url.Parse(location)
+				require.NoError(t, err)
+				assert.Equal(t, "https://tinyauth.example.com/authorize", redirectURL.Scheme+"://"+redirectURL.Host+redirectURL.Path)
+
+				params := redirectURL.Query()
+				assert.Equal(t, "some-client-id", params.Get("client_id"))
+				assert.Equal(t, "some-challenge", params.Get("code_challenge"))
+				assert.Equal(t, "plain", params.Get("code_challenge_method"))
+				assert.Equal(t, "some-nonce", params.Get("nonce"))
+				assert.Equal(t, "https://test.example.com/callback", params.Get("redirect_uri"))
+				assert.Equal(t, "code", params.Get("response_type"))
+				assert.Equal(t, "openid", params.Get("scope"))
+				assert.Equal(t, "some-state", params.Get("state"))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/controller/oidc_controller_test.go` around lines 872 - 874, The test
currently asserts the entire Location string (the lines using
recorder.Header().Get("Location") and assert.Equal(... full URL ...)), which is
brittle; change it to parse the Location with net/url (url.Parse), assert the
scheme/host/path or base URL separately, then parse query parameters
(url.Query()) and assert individual keys like client_id, code_challenge,
code_challenge_method, nonce, redirect_uri, response_type, scope, and state have
the expected values; this removes coupling to query ordering and exact escaping
while keeping the same expectations.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/controller/oidc_controller.go`:
- Around line 202-211: AuthorizePseudoPost currently appends the raw POST body
into the redirect URL (in AuthorizePseudoPost), exposing large/signed request
payloads and breaking POST semantics; instead, persist the POST parameters
server-side and redirect with a short opaque reference (e.g., a generated state
ID) only. Change AuthorizePseudoPost to read and validate the body, generate a
secure random key (state), store the full form/payload in a server-side store or
session keyed by that state (or set a secure, HttpOnly cookie referencing it),
then redirect to fmt.Sprintf("%s/authorize?state=%s",
controller.oidc.GetIssuer(), state). Ensure you use the same storage lookup
later when handling the GET authorize flow and add error handling for storage
failures.

In `@internal/middleware/ui_middleware.go`:
- Around line 56-60: The current middleware forwards any POST whose path starts
with "authorize" (strings.HasPrefix(path, "authorize")), which erroneously
matches "/authorized" and "/authorize-anything"; change the condition to match
only the exact "/authorize" route used by the OIDC controller (e.g., replace the
HasPrefix check with an equality check for the full path string such as path ==
"/authorize" or path == "authorize" consistent with how path is set), keeping
the same behavior of calling c.Next() and return when matched so only POST
/authorize is forwarded to the downstream handler defined in the OIDC
controller.

---

Nitpick comments:
In `@internal/controller/oidc_controller_test.go`:
- Around line 872-874: The test currently asserts the entire Location string
(the lines using recorder.Header().Get("Location") and assert.Equal(... full URL
...)), which is brittle; change it to parse the Location with net/url
(url.Parse), assert the scheme/host/path or base URL separately, then parse
query parameters (url.Query()) and assert individual keys like client_id,
code_challenge, code_challenge_method, nonce, redirect_uri, response_type,
scope, and state have the expected values; this removes coupling to query
ordering and exact escaping while keeping the same expectations.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 2e11543d-bce0-487f-8172-3e1be8253af7

📥 Commits

Reviewing files that changed from the base of the PR and between 5d95123 and a736c4a.

📒 Files selected for processing (6)
  • frontend/vite.config.ts
  • internal/bootstrap/router_bootstrap.go
  • internal/controller/oidc_controller.go
  • internal/controller/oidc_controller_test.go
  • internal/middleware/ui_middleware.go
  • internal/service/oidc_service.go

Comment on lines +202 to +211
// Pseudo handler that will just redirect to get in frontend then back to backend
func (controller *OIDCController) AuthorizePseudoPost(c *gin.Context) {
body, err := io.ReadAll(c.Request.Body)
if err != nil {
tlog.App.Error().Err(err).Msg("Failed to read request body")
c.Redirect(http.StatusFound, fmt.Sprintf("%s/authorize", controller.oidc.GetIssuer()))
return
}
redirectUrl := fmt.Sprintf("%s/authorize?%s", controller.oidc.GetIssuer(), body)
c.Redirect(http.StatusFound, redirectUrl)
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Apr 27, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

This turns authorize POST back into a GET.

Line 210 serializes the entire form body into the Location header, so large or signed request payloads still hit URL-length limits and end up in browser history/logs. That defeats the main reason clients use POST on the authorization endpoint. Keep the authorize parameters server-side across the login redirect instead of rebuilding them into the URL.

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

In `@internal/controller/oidc_controller.go` around lines 202 - 211,
AuthorizePseudoPost currently appends the raw POST body into the redirect URL
(in AuthorizePseudoPost), exposing large/signed request payloads and breaking
POST semantics; instead, persist the POST parameters server-side and redirect
with a short opaque reference (e.g., a generated state ID) only. Change
AuthorizePseudoPost to read and validate the body, generate a secure random key
(state), store the full form/payload in a server-side store or session keyed by
that state (or set a secure, HttpOnly cookie referencing it), then redirect to
fmt.Sprintf("%s/authorize?state=%s", controller.oidc.GetIssuer(), state). Ensure
you use the same storage lookup later when handling the GET authorize flow and
add error handling for storage failures.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

To be honest I don't believe that storing on the backend makes sense, it's way too much code for something that will be used extremely rarely. Even when it's used I am not familiar with any URL limit.

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.

@steveiliop56, that's a fair point — for standard authorization parameters the URL stays well within practical browser/server limits (~8 KB), and the certification test PASSED with 43/43 successes, which is the real validation here.

The one narrow edge case would be a large request JWT (RFC 9101 / JARM), since those can be several KB on their own. But since you've already added the Request field to AuthorizeRequest and the conformance suite accepts the current approach, the complexity of server-side storage clearly isn't justified. I'll drop the suggestion.


✏️ Learnings added
Learnt from: steveiliop56
Repo: tinyauthapp/tinyauth PR: 823
File: internal/controller/oidc_controller.go:202-211
Timestamp: 2026-04-28T12:09:48.523Z
Learning: In `internal/controller/oidc_controller.go` (tinyauth project, Go/Gin), `AuthorizePseudoPost` intentionally converts a POST body into redirect URL query parameters. The maintainer has decided this is acceptable: server-side parameter storage is considered too complex for this rarely-used code path, and the OIDCC certification test `oidcc-ensure-post-request-succeeds` passes with this approach.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment on lines +56 to +60
// For OIDC post authentication, we need to redirect the POST to /authorize to the backend
if method == http.MethodPost && strings.HasPrefix(path, "authorize") {
c.Next()
return
}
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.

⚠️ Potential issue | 🟡 Minor

Match only /authorize here.

strings.HasPrefix(path, "authorize") also forwards paths like /authorized and /authorize-anything, but the only downstream route added is POST /authorize in internal/controller/oidc_controller.go Line 78. Those requests now bypass the UI/static handler and fall into a 404 instead.

Suggested fix
-			if method == http.MethodPost && strings.HasPrefix(path, "authorize") {
+			if method == http.MethodPost && path == "authorize" {
 				c.Next()
 				return
 			}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// For OIDC post authentication, we need to redirect the POST to /authorize to the backend
if method == http.MethodPost && strings.HasPrefix(path, "authorize") {
c.Next()
return
}
// For OIDC post authentication, we need to redirect the POST to /authorize to the backend
if method == http.MethodPost && path == "authorize" {
c.Next()
return
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/middleware/ui_middleware.go` around lines 56 - 60, The current
middleware forwards any POST whose path starts with "authorize"
(strings.HasPrefix(path, "authorize")), which erroneously matches "/authorized"
and "/authorize-anything"; change the condition to match only the exact
"/authorize" route used by the OIDC controller (e.g., replace the HasPrefix
check with an equality check for the full path string such as path ==
"/authorize" or path == "authorize" consistent with how path is set), keeping
the same behavior of calling c.Next() and return when matched so only POST
/authorize is forwarded to the downstream handler defined in the OIDC
controller.

@steveiliop56
Copy link
Copy Markdown
Member Author

Never mind this is a mess.

@steveiliop56 steveiliop56 marked this pull request as draft April 27, 2026 17:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant