Improve handling of expired sessions

This commit is contained in:
2024-10-03 14:00:43 +01:00
parent dca2b5214c
commit 38433dfff8
3 changed files with 39 additions and 11 deletions
+2
View File
@@ -129,6 +129,8 @@ func (t *TraefikOidc) handleExpiredToken(rw http.ResponseWriter, req *http.Reque
if err != nil { if err != nil {
t.logger.Errorf("Failed to clear session: %v", err) t.logger.Errorf("Failed to clear session: %v", err)
} }
// Initiate a new authentication flow
t.initiateAuthentication(rw, req, session, t.redirectURL) t.initiateAuthentication(rw, req, session, t.redirectURL)
} }
+29 -10
View File
@@ -273,7 +273,26 @@ func (t *TraefikOidc) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
return return
} }
authenticated, needsRefresh := t.isUserAuthenticated(session) authenticated, needsRefresh, expired := t.isUserAuthenticated(session)
if expired {
t.handleExpiredToken(rw, req, session)
return
}
if !authenticated {
t.initiateAuthentication(rw, req, session, t.redirectURL)
return
}
if needsRefresh {
refreshed := t.refreshToken(rw, req, session)
if !refreshed {
t.handleExpiredToken(rw, req, session)
return
}
}
if authenticated { if authenticated {
if needsRefresh { if needsRefresh {
// Attempt to refresh the token silently // Attempt to refresh the token silently
@@ -338,48 +357,48 @@ func (t *TraefikOidc) determineHost(req *http.Request) string {
return req.Host return req.Host
} }
func (t *TraefikOidc) isUserAuthenticated(session *sessions.Session) (bool, bool) { func (t *TraefikOidc) isUserAuthenticated(session *sessions.Session) (bool, bool, bool) {
authenticated, _ := session.Values["authenticated"].(bool) authenticated, _ := session.Values["authenticated"].(bool)
if !authenticated { if !authenticated {
return false, false return false, false, false
} }
idToken, ok := session.Values["id_token"].(string) idToken, ok := session.Values["id_token"].(string)
if !ok || idToken == "" { if !ok || idToken == "" {
return false, false return false, false, true // Session is invalid, consider it expired
} }
// Verify the token // Verify the token
if err := t.verifyToken(idToken); err != nil { if err := t.verifyToken(idToken); err != nil {
t.logger.Errorf("Token verification failed: %v", err) t.logger.Errorf("Token verification failed: %v", err)
return false, false return false, false, true // Token is invalid, consider it expired
} }
claims, err := extractClaims(idToken) claims, err := extractClaims(idToken)
if err != nil { if err != nil {
t.logger.Errorf("Failed to extract claims: %v", err) t.logger.Errorf("Failed to extract claims: %v", err)
return false, false return false, false, true // Can't read claims, consider it expired
} }
exp, ok := claims["exp"].(float64) exp, ok := claims["exp"].(float64)
if !ok { if !ok {
t.logger.Errorf("Failed to get expiration time from claims") t.logger.Errorf("Failed to get expiration time from claims")
return false, false return false, false, true // No expiration, consider it expired
} }
now := time.Now().Unix() now := time.Now().Unix()
expTime := int64(exp) expTime := int64(exp)
if now > expTime { if now > expTime {
return false, false // Token has expired return false, false, true // Token has expired
} }
gracePeriod := time.Minute * 5 gracePeriod := time.Minute * 5
if time.Now().Add(gracePeriod).Unix() > expTime { if time.Now().Add(gracePeriod).Unix() > expTime {
return true, true // Token will expire soon, needs refresh return true, true, false // Token will expire soon, needs refresh
} }
return true, false return true, false, false // Token is valid and not expiring soon
} }
func (t *TraefikOidc) initiateAuthentication(rw http.ResponseWriter, req *http.Request, session *sessions.Session, redirectURL string) { func (t *TraefikOidc) initiateAuthentication(rw http.ResponseWriter, req *http.Request, session *sessions.Session, redirectURL string) {
+8 -1
View File
@@ -410,6 +410,7 @@ func (suite *TraefikOidcTestSuite) TestIsUserAuthenticated() {
setupSession func() *sessions.Session setupSession func() *sessions.Session
expectedAuth bool expectedAuth bool
expectedRefresh bool expectedRefresh bool
expectedExpired bool
}{ }{
{ {
name: "Valid Token", name: "Valid Token",
@@ -421,6 +422,7 @@ func (suite *TraefikOidcTestSuite) TestIsUserAuthenticated() {
}, },
expectedAuth: true, expectedAuth: true,
expectedRefresh: false, expectedRefresh: false,
expectedExpired: false,
}, },
{ {
name: "Expired Token", name: "Expired Token",
@@ -432,6 +434,7 @@ func (suite *TraefikOidcTestSuite) TestIsUserAuthenticated() {
}, },
expectedAuth: false, expectedAuth: false,
expectedRefresh: false, expectedRefresh: false,
expectedExpired: true,
}, },
{ {
name: "Token Needs Refresh", name: "Token Needs Refresh",
@@ -446,6 +449,7 @@ func (suite *TraefikOidcTestSuite) TestIsUserAuthenticated() {
}, },
expectedAuth: true, expectedAuth: true,
expectedRefresh: true, expectedRefresh: true,
expectedExpired: false,
}, },
{ {
name: "Not Authenticated", name: "Not Authenticated",
@@ -456,6 +460,7 @@ func (suite *TraefikOidcTestSuite) TestIsUserAuthenticated() {
}, },
expectedAuth: false, expectedAuth: false,
expectedRefresh: false, expectedRefresh: false,
expectedExpired: false,
}, },
} }
@@ -463,12 +468,14 @@ func (suite *TraefikOidcTestSuite) TestIsUserAuthenticated() {
suite.Run(tc.name, func() { suite.Run(tc.name, func() {
session := tc.setupSession() session := tc.setupSession()
suite.mockTokenVerifier.On("VerifyToken", mock.AnythingOfType("string")).Return(nil).Maybe() suite.mockTokenVerifier.On("VerifyToken", mock.AnythingOfType("string")).Return(nil).Maybe()
authenticated, needsRefresh := suite.oidc.isUserAuthenticated(session) authenticated, needsRefresh, expired := suite.oidc.isUserAuthenticated(session)
suite.Equal(tc.expectedAuth, authenticated) suite.Equal(tc.expectedAuth, authenticated)
suite.Equal(tc.expectedRefresh, needsRefresh) suite.Equal(tc.expectedRefresh, needsRefresh)
suite.Equal(tc.expectedExpired, expired)
}) })
} }
} }
func (suite *TraefikOidcTestSuite) TestInitiateAuthentication() { func (suite *TraefikOidcTestSuite) TestInitiateAuthentication() {
req := httptest.NewRequest("GET", "http://example.com", nil) req := httptest.NewRequest("GET", "http://example.com", nil)
rw := httptest.NewRecorder() rw := httptest.NewRecorder()