sftpgo icon indicating copy to clipboard operation
sftpgo copied to clipboard

[OIDC] - Redirect to login page if state not supplied

Open stephen-cox-nzx opened this issue 1 month ago • 1 comments

Is your feature request related to a problem? Please describe.

If Microsoft Entra External Id with OIDC auth is employed, and users are logging in for the first time they typically will need to change their password.

Entra External Id appears to drop the "state" parameter in this situation resulting in an error message in SFTPGo after they have completed authentication.

This is not a great user experience for a user's first login to SFTPGo.

Describe the solution you'd like

Would it be possible to have an option for SFTPGo to simply redirect back to the login page if the state parameter is missing (vs. an invalid state value - this probably deserves the error message).

From experience the second time the user clicks on the OIDC auth button the login flow is successful, so this change would result in a cleaner user experience.

So the flow (for first time users) would be

  • User navigates to SFTPGo user site (unauthenticated)
  • User is redirected to the login page
  • User clicks to login via OIDC
  • Complete Entra External ID "onboarding" (or any other OIDC auth provider)
  • User is redirected back to SFTPGo (without a state parameter)
  • SFTPGO logs the error, then redirects the user to the Login Page
  • User clicks to login via OIDC
  • User is successfully authenticated to SFTPGo and redirected to the home page

Describe alternatives you've considered

sftpgo/internal/httpd/oidc.go

func (s *httpdServer) handleOIDCRedirect(w http.ResponseWriter, r *http.Request) {
        state := r.URL.Query().Get("state")
        if state == nil {
            logger.Debug(logSender, "", "state query parameter not present")
	        // this won't work as it relies on the state to determine the authReq.
	        // Could the admin vs user site have different OIDC redirect URLs, 
	        // so the context of the page itself determines where to redirect back to?    
	        if authReq.Audience == tokenAudienceWebAdmin {
		        http.Redirect(w, r, webAdminLoginPath, http.StatusFound)
        		return
	        }  else {
		        http.Redirect(w, r, webClientLoginPath, http.StatusFound)
		        return
                }
        }
...

What are you using SFTPGo for?

Medium business

Additional context

No response

stephen-cox-nzx avatar Oct 28 '25 21:10 stephen-cox-nzx

According to the OIDC specification, the code and state parameter must be returned by the Identity Provider to the client after authentication. In our tests with Keycloak, even when forcing a password change, the state parameter is preserved and authentication completes successfully.

The behavior described with Microsoft Entra ID, where state is dropped after a first-time password change, does not appear to conform to the OIDC spec. This does not looks like an issue in the SFTPGo implementation.

drakkan avatar Oct 29 '25 17:10 drakkan

@Drakken - I have spent a bit more time investigating this (happy to create this as a new issue as it appears the state is being provided by entra correctly).

This is the client HTTP log with the following changes

  • SFTP Site host name replaced
  • Entra Tenant Id and Client Id replaced
Image

The <STATE_REDACTED> value is the same in both the initial request to Entra and the resultant redirect back to SFTPGo.

start Time Http Result Request
0 s 302 https://example-sftpgo.corp.com
97 ms 200 https://example-sftpgo.corp.com/web/client/login
47.5 s 302 https://example-sftpgo.corp.com/web/client/oidclogin
47.5 s 200 https://da497719-80a2-4d52-b38d-5e8a1ec16a82.ciamlogin.com/da497719-80a2-4d52-b38d-5e8a1ec16a82/oauth2/v2.0/authorize?client_id=a92263e1-edb4-40a6-8e00-634caa9a370f&nonce=<NONCE_REDACTED> &redirect_uri=https%3A%2F%2Fexample-sftpgo.corp.com%2Fweb%2Foidc%2Fredirect&response_type=code&scope=openid+profile+email&state=<STATE_REDACTED>
1.9 m 200 https://da497719-80a2-4d52-b38d-5e8a1ec16a82.ciamlogin.com/da497719-80a2-4d52-b38d-5e8a1ec16a82/login
3 m 200 https://da497719-80a2-4d52-b38d-5e8a1ec16a82.ciamlogin.com/common/resume
3 m 200 https://fpt.dfp.microsoft.com/?session_id=28d73ae6-563c-46e4-a77d-ac1225f8bd05&instanceid=743bf5c2-d00c-46c3-96e1-6b2e72c5d2b7&tenantid=da497719-80a2-4d52-b38d-5e8a1ec16a82
3.1 m 200 https://fpt.dfp.microsoft.com/Clear.HTML?ctx=Ls1.0&wl=True&session_id=28d73ae6-563c-46e4-a77d-ac1225f8bd05&id=a9c6ae6b-3b22-40c4-9b31-3b938525110c&w=8DE38EEC3589E04&tkt=taBcrIH61PuCVH7eNCyH0MJojnuUODHcZ6x9WoxhgCn3NliiFwbCV7BnMPq8I2jdM9zyfbSwdjvOIqSwUHB8Qb0zrFxi87ShyyjPhnCc7Yk5tKzrnKVawHmu7rKMGv3PZMT%252f8omlpBHBYsADyORVObwMoIftgV93acfM7bKaCSSbL7yB99egklQ1Q2zR78VBeTJdpQbXYkwWroNcEItzw06WHBavytzWp7YW07jKtsy6vkd1cvps4xTz%252fH9GE4gW9Nb4P%252fIyXH3I8mnTqlRnUxPyiwtQSOvc2wZ4ZYe4Cfi6Bv%252bXcy59998bWzLybhdytxtctE8R5fjSrLUXKyDADYhTZjF3adiyxeCnwi9sViQ%253d&CustomerId=743bf5c2-d00c-46c3-96e1-6b2e72c5d2b7
3.8 m 200 https://da497719-80a2-4d52-b38d-5e8a1ec16a82.ciamlogin.com/da497719-80a2-4d52-b38d-5e8a1ec16a82/webresume
4 m 302 https://da497719-80a2-4d52-b38d-5e8a1ec16a82.ciamlogin.com/kmsi
4 m 400 https://example-sftpgo.corp.com/web/oidc/redirect?code=<CODE_REDACTED>&state=<STATE_REDACTED>&session_state=00b8d099-252e-6ad8-3817-125ac9f1b2d9

The relevant SFTPGo Log entries:

{"level":"debug","time":"2025-12-11T19:53:04.263","sender":"httpd","message":"oidc authentication state did not match"}
{"level":"warn","time":"2025-12-11T19:53:04.264","sender":"httpd","cipher_suite":"TLS_AES_128_GCM_SHA256","local_addr":"10.89.0.2:8080","method":"GET","proto":"HTTP/1.1","remote_addr":"10.89.0.2:42314","request
_id":"<REDACTED_HOST_NAME>/n7SF2RM6MU-001414","uri":"https://example-sftpgo.corp.com/web/oidc/redirect?code=<CODE_REDACTED>
&state=<STATE_REDACTED>&session_state=00b8d099-252e-
6ad8-3817-125ac9f1b2d9","user_agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36 Edg/143.0.0.0","resp_status":400,"resp_size":36368,"elapsed_
ms":0}

Looking at the code, as far as I can tell, the only way to get oidc authentication state did not match is for authReq, err := oidcMgr.getPendingAuth(state) to return an error, however the initial state sent to entra and returned to the oidc/redirect appear to be identical.

stephen-cox-nzx avatar Dec 11 '25 20:12 stephen-cox-nzx

@stephen-cox-nzx I agree getPendingAuth is returning an error. Are you running a multi-node cluster? Also, how much time passes between the initial authentication request and the redirect? Just a few seconds, or several minutes?

drakkan avatar Dec 12 '25 08:12 drakkan

Just a few seconds, or several minutes?

You are right - if the Authentication takes > 1 minute (approx) the auth fails.

I have repeated the login twice, both using new in-private sessions, no cached cookies etc. The configuration requires Entra MFA, so there is some mandatory latency introduced waiting for the email confirmation code. The first test was successful, and completed in < 1 minute The second test reproduced the error, as I added a 1 minute delay into the auth flow. It is a multi-node cluster behind an F5 using a PostgreSQL backend, but there is server affinity so not swapping between nodes. is_shared has not been set - I will try that now as well.

Successful

Http code Timing Request
302 0 https://example-sftpgo.corp.com/
200 0.1 s https://example-sftpgo.corp.com/web/client/login
302 3.01 s https://example-sftpgo.corp.com/web/client/oidclogin
200 3.03 s https://da497719-80a2-4d52-b38d-5e8a1ec16a82.ciamlogin.com/da497719-80a2-4d52-b38d-5e8a1ec16a82/oauth2/v2.0/authorize?client_id=a92263e1-edb4-40a6-8e00-634caa9a370f&nonce=<NONCE_REDACTED>&redirect_uri=https%3A%2F%2Fexample-sftpgo.corp.com%2Fweb%2Foidc%2Fredirect&response_type=code&scope=openid+profile+email&state=<STATE_REDACTED>
200 17.60 s https://da497719-80a2-4d52-b38d-5e8a1ec16a82.ciamlogin.com/da497719-80a2-4d52-b38d-5e8a1ec16a82/login
200 19.39 s https://fpt.dfp.microsoft.com/?session_id=6f2e7f12-1edd-412c-9af5-8becc2e54213&instanceid=743bf5c2-d00c-46c3-96e1-6b2e72c5d2b7&tenantid=da497719-80a2-4d52-b38d-5e8a1ec16a82
200 21.43 s https://fpt.dfp.microsoft.com/Clear.HTML?ctx=Ls1.0&wl=True&session_id=6f2e7f12-1edd-412c-9af5-8becc2e54213&id=a2491924-2d6d-4317-a03e-5ff7d3ae28d8&w=8DE3B49FEAF5E3C&tkt=<TICKET_REDACTED>&CustomerId=743bf5c2-d00c-46c3-96e1-6b2e72c5d2b7
200 37.98 s https://da497719-80a2-4d52-b38d-5e8a1ec16a82.ciamlogin.com/common/SAS/ProcessAuth
302 41.40 s https://da497719-80a2-4d52-b38d-5e8a1ec16a82.ciamlogin.com/kmsi
302 41.93 s https://example-sftpgo.corp.com/web/oidc/redirect?code=<CODE_REDACTED>&state=<STATE_REDACTED>&session_state=00b945c9-a27f-e35d-813b-e1ee51f68999
200 47.23 s https://example-sftpgo.corp.com/web/client/files

Unsuccessful (>1m delay completing MFA)

Http code Timing Request
302 0 https://example-sftpgo.corp.com/
200 0.09 s https://example-sftpgo.corp.com/web/client/login
302 2.92 s https://example-sftpgo.corp.com/web/client/oidclogin
200 2.94 s https://da497719-80a2-4d52-b38d-5e8a1ec16a82.ciamlogin.com/da497719-80a2-4d52-b38d-5e8a1ec16a82/oauth2/v2.0/authorize?client_id=a92263e1-edb4-40a6-8e00-634caa9a370f&nonce=<NONCE_REDACTED>&redirect_uri=https%3A%2F%2Fexample-sftpgo.corp.com%2Fweb%2Foidc%2Fredirect&response_type=code&scope=openid+profile+email&state=<STATE_REDACTED>
200 31.57 s https://da497719-80a2-4d52-b38d-5e8a1ec16a82.ciamlogin.com/da497719-80a2-4d52-b38d-5e8a1ec16a82/login
200 32.78 s https://fpt.dfp.microsoft.com/?session_id=c9a35514-97c9-4e2a-8e0d-e34e56c7b21b&instanceid=743bf5c2-d00c-46c3-96e1-6b2e72c5d2b7&tenantid=da497719-80a2-4d52-b38d-5e8a1ec16a82
200 34.84 s https://fpt.dfp.microsoft.com/Clear.HTML?ctx=Ls1.0&wl=True&session_id=c9a35514-97c9-4e2a-8e0d-e34e56c7b21b&id=58d2f07a-4c46-43b4-ad86-a45fa6015774&w=8DE3B4B3685D59F&tkt=<TICKET_REDACTED>&CustomerId=743bf5c2-d00c-46c3-96e1-6b2e72c5d2b7
200 1.9 m https://da497719-80a2-4d52-b38d-5e8a1ec16a82.ciamlogin.com/common/SAS/ProcessAuth
302 2.0 m https://da497719-80a2-4d52-b38d-5e8a1ec16a82.ciamlogin.com/kmsi
400 2.0 m https://example-sftpgo.corp.com/web/oidc/redirect?code=<CODE_REDACTED>&state=<STATE_REDACTED>&session_state=00b945c9-14a3-67eb-fde5-acd64630709a

stephen-cox-nzx avatar Dec 14 '25 20:12 stephen-cox-nzx