Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,19 @@ func WithHttpClient(client *http.Client) LogtoClientOption {
}
}

// WithTrustForwardedHeader sets whether to trust X-Forwarded-* headers for checking the request's origin, useful when behind a reverse proxy.
func WithTrustForwardedHeader(b bool) LogtoClientOption {
return func(c *LogtoClient) {
c.trustForwardedHeader = b
}
}
Comment on lines +44 to +49
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

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

Security concern: When trustForwardedHeader is enabled, the forwarded headers are trusted without any validation. This could be exploited if the application is not actually behind a properly configured reverse proxy, or if the proxy doesn't strip client-provided X-Forwarded-* headers.

Consider adding:

  1. Documentation warning that this option should only be enabled when the application is behind a trusted reverse proxy that properly sets and strips forwarded headers
  2. Optional validation that the forwarded values are well-formed URLs/hosts
  3. A comment in the code explaining the security implications

Copilot uses AI. Check for mistakes.

type LogtoClient struct {
httpClient *http.Client
logtoConfig *LogtoConfig
storage Storage
accessTokenMap map[string]AccessToken
httpClient *http.Client
logtoConfig *LogtoConfig
storage Storage
accessTokenMap map[string]AccessToken
trustForwardedHeader bool
}

func NewLogtoClient(config *LogtoConfig, storage Storage, opts ...LogtoClientOption) *LogtoClient {
Expand Down
3 changes: 3 additions & 0 deletions client/handle_sign_in_callback.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ func (logtoClient *LogtoClient) HandleSignInCallback(request *http.Request) erro
}

callbackUri := GetOriginRequestUrl(request)
if logtoClient.trustForwardedHeader {
callbackUri = getForwaredRequestUrl(request)
}
Comment on lines 19 to +22
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

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

Missing test coverage: There are no test cases that verify the HandleSignInCallback function correctly uses forwarded headers when trustForwardedHeader is enabled. The existing tests only cover the unit-level utility functions.

Consider adding an integration test that:

  1. Creates a LogtoClient with WithTrustForwardedHeader(true)
  2. Calls HandleSignInCallback with a request containing X-Forwarded-Host, X-Forwarded-Url, and X-Forwarded-Proto headers
  3. Verifies that the callback URI passed to core.VerifyAndParseCodeFromCallbackUri contains the forwarded values rather than the original request values

Copilot uses AI. Check for mistakes.
code, retrieveCodeErr := core.VerifyAndParseCodeFromCallbackUri(callbackUri, signInSession.RedirectUri, signInSession.State)
if retrieveCodeErr != nil {
return retrieveCodeErr
Expand Down
20 changes: 20 additions & 0 deletions client/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,26 @@ func GetOriginRequestUrl(request *http.Request) string {
return getRequestProtocol(request) + "://" + request.Host + request.RequestURI
}

func getForwaredRequestUrl(request *http.Request) string {
proto := getRequestProtocol(request)
host := getForwaredRequestHost(request)
uri := getForwaredRequestRequestUri(request)
return proto + "://" + host + uri
}
Comment on lines +15 to +20
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

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

The getForwaredRequestUrl function calls getRequestProtocol, which unconditionally trusts the X-Forwarded-Proto header. This means the protocol portion is determined by forwarded headers regardless of the trustForwardedHeader setting.

For consistency and security, when constructing URLs for forwarded requests, the protocol should also respect the forwarded headers setting. Consider creating a separate protocol detection function that accepts a flag to control whether to trust forwarded headers.

Copilot uses AI. Check for mistakes.
func getForwaredRequestHost(request *http.Request) string {
host := request.Header.Get("X-Forwarded-Host")
if host != "" {
return host
}
return request.Host
}
func getForwaredRequestRequestUri(request *http.Request) string {
uri := request.Header.Get("X-Forwarded-Url")
if uri != "" {
return uri
}
Comment on lines +29 to +32
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

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

Non-standard header usage: The implementation uses X-Forwarded-Url header, which is not a standard forwarded header. The standard headers typically used by reverse proxies are:

  • X-Forwarded-Proto for protocol
  • X-Forwarded-Host for host
  • X-Forwarded-Path or X-Original-URI for the path/URI

X-Forwarded-Url is not widely supported by standard reverse proxies like nginx, Apache, or load balancers. Consider using X-Forwarded-Path or X-Original-URI instead, or document clearly that this requires custom proxy configuration. Alternatively, if the path should remain unchanged, consider using just the Host and Proto headers.

Suggested change
uri := request.Header.Get("X-Forwarded-Url")
if uri != "" {
return uri
}
uri := request.Header.Get("X-Original-URI")
if uri != "" {
return uri
}
uri = request.Header.Get("X-Forwarded-Path")
if uri != "" {
return uri
}

Copilot uses AI. Check for mistakes.
return request.RequestURI
}
Comment on lines +15 to +34
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

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

There's a spelling error in the function names throughout the code. "Forwared" should be spelled "Forwarded" (with two 'd's). This affects:

  • getForwaredRequestUrl
  • getForwaredRequestHost
  • getForwaredRequestRequestUri
  • Test function names: TestGetForwaredRequestUrlShouldReturnXForwardedIfPresent, TestGetForwaredRequestHostShouldFallbackToRequestHost, and TestGetForwaredRequestRequestUriShouldFallbackToRequestURI

Copilot uses AI. Check for mistakes.
func getRequestProtocol(request *http.Request) string {
if request.TLS != nil {
return "https"
Expand Down
40 changes: 40 additions & 0 deletions client/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,43 @@ func createTestToken(resource string) (string, error) {

return token, nil
}

func TestGetForwaredRequestUrlShouldReturnXForwardedIfPresent(t *testing.T) {
req, err := http.NewRequest("GET", "http://example.com/path?query=1", nil)
assert.Nil(t, err)
// Ensure RequestURI is set like in real servers
req.RequestURI = "/path?query=1"

req.Header.Add("X-Forwarded-Host", "forwarded.example.com")
req.Header.Add("X-Forwarded-Url", "/forwarded-path?query=2")
req.Header.Add("X-Forwarded-Proto", "https")

url := getForwaredRequestUrl(req)

assert.Equal(t, "https://forwarded.example.com/forwarded-path?query=2", url)
}

func TestGetForwaredRequestHostShouldFallbackToRequestHost(t *testing.T) {
req, _ := http.NewRequest("GET", "http://example.com/", nil)
req.RequestURI = "/"

host := getForwaredRequestHost(req)

assert.Equal(t, "example.com", host)

req.Header.Add("X-Forwarded-Host", "proxied.example.com")
host = getForwaredRequestHost(req)
assert.Equal(t, "proxied.example.com", host)
}

func TestGetForwaredRequestRequestUriShouldFallbackToRequestURI(t *testing.T) {
req, _ := http.NewRequest("GET", "http://example.com/api", nil)
req.RequestURI = "/api"

uri := getForwaredRequestRequestUri(req)
assert.Equal(t, "/api", uri)

req.Header.Add("X-Forwarded-Url", "/proxied/api?x=1")
uri = getForwaredRequestRequestUri(req)
assert.Equal(t, "/proxied/api?x=1", uri)
}
Comment on lines +134 to +172
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

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

There's a spelling error in the test function names. "Forwared" should be spelled "Forwarded" (with two 'd's). This affects:

  • TestGetForwaredRequestUrlShouldReturnXForwardedIfPresent
  • TestGetForwaredRequestHostShouldFallbackToRequestHost
  • TestGetForwaredRequestRequestUriShouldFallbackToRequestURI

Copilot uses AI. Check for mistakes.
Loading