mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-11-03 07:44:13 +00:00
perf(logout): parse the ID token once for the invalidation check
With backchannel or frontchannel logout on, every authenticated request parsed the ID token twice to read sid, sub and iat. One shared parse now returns the same values. A test pins equality with the two separate calls for valid, partial and malformed tokens. The gain is one parseJWT call fewer per request. I did not measure it under yaegi.
This commit is contained in:
@@ -639,6 +639,22 @@ func (t *TraefikOidc) sessionCreatedAtForInvalidation(idToken string, session *S
|
||||
return time.Time{} // zero: treated as invalidated -> safe re-auth
|
||||
}
|
||||
|
||||
// invalidationInputs returns sid, sub and the creation time for the IdP-logout
|
||||
// check from one parseJWT call, with the same results as extractSessionInfo
|
||||
// and sessionCreatedAtForInvalidation called separately.
|
||||
func (t *TraefikOidc) invalidationInputs(idToken string, session *SessionData) (sid, sub string, createdAt time.Time) {
|
||||
jwt, err := parseJWT(idToken)
|
||||
if err != nil {
|
||||
return "", "", t.sessionCreatedAtForInvalidation("", session)
|
||||
}
|
||||
sid, _ = jwt.Claims["sid"].(string)
|
||||
sub, _ = jwt.Claims["sub"].(string)
|
||||
if iat, ok := jwt.Claims["iat"].(float64); ok {
|
||||
return sid, sub, time.Unix(int64(iat), 0)
|
||||
}
|
||||
return sid, sub, t.sessionCreatedAtForInvalidation("", session)
|
||||
}
|
||||
|
||||
// extractSessionInfo extracts sid and sub from an ID token for session tracking
|
||||
func (t *TraefikOidc) extractSessionInfo(idToken string) (sid, sub string, createdAt time.Time) {
|
||||
if idToken == "" {
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package traefikoidc
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/sessions"
|
||||
)
|
||||
|
||||
// invalidationInputs must return exactly what extractSessionInfo and
|
||||
// sessionCreatedAtForInvalidation return when called separately.
|
||||
func TestInvalidationInputsMatchSeparateCalls(t *testing.T) {
|
||||
oidc := &TraefikOidc{logger: newNoOpLogger()}
|
||||
sess := &SessionData{mainSession: &sessions.Session{Values: map[interface{}]interface{}{"created_at": int64(1700000000)}}}
|
||||
|
||||
tokens := map[string]string{
|
||||
"all claims": makeTestJWT(t, map[string]interface{}{"sid": "s1", "sub": "u1", "iat": float64(1700000100)}),
|
||||
"no iat": makeTestJWT(t, map[string]interface{}{"sid": "s1", "sub": "u1"}),
|
||||
"sid only": makeTestJWT(t, map[string]interface{}{"sid": "s1", "iat": float64(1700000200)}),
|
||||
"sub only": makeTestJWT(t, map[string]interface{}{"sub": "u1"}),
|
||||
"wrong types": makeTestJWT(t, map[string]interface{}{"sid": 5, "sub": true, "iat": "x"}),
|
||||
"malformed": "not-a-jwt",
|
||||
"empty": "",
|
||||
}
|
||||
for name, tok := range tokens {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
wantSID, wantSub, _ := oidc.extractSessionInfo(tok)
|
||||
wantAt := oidc.sessionCreatedAtForInvalidation(tok, sess)
|
||||
gotSID, gotSub, gotAt := oidc.invalidationInputs(tok, sess)
|
||||
if gotSID != wantSID || gotSub != wantSub || !gotAt.Equal(wantAt) {
|
||||
t.Fatalf("got (%q,%q,%v) want (%q,%q,%v)", gotSID, gotSub, gotAt, wantSID, wantSub, wantAt)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+2
-4
@@ -1054,8 +1054,7 @@ func (t *TraefikOidc) processAuthorizedRequestRS(rw http.ResponseWriter, req *ht
|
||||
idToken := rs.idToken
|
||||
if t.enableBackchannelLogout || t.enableFrontchannelLogout {
|
||||
if idToken != "" {
|
||||
sid, sub, _ := t.extractSessionInfo(idToken)
|
||||
createdAt := t.sessionCreatedAtForInvalidation(idToken, session)
|
||||
sid, sub, createdAt := t.invalidationInputs(idToken, session)
|
||||
if t.isSessionInvalidated(sid, sub, createdAt) {
|
||||
t.logger.Infof("Session for user %s has been invalidated via IdP-initiated logout", userIdentifier)
|
||||
if err := session.Clear(req, rw); err != nil {
|
||||
@@ -1147,8 +1146,7 @@ func (t *TraefikOidc) processAuthorizedRequest(rw http.ResponseWriter, req *http
|
||||
if t.enableBackchannelLogout || t.enableFrontchannelLogout {
|
||||
idToken := session.GetIDToken()
|
||||
if idToken != "" {
|
||||
sid, sub, _ := t.extractSessionInfo(idToken)
|
||||
createdAt := t.sessionCreatedAtForInvalidation(idToken, session)
|
||||
sid, sub, createdAt := t.invalidationInputs(idToken, session)
|
||||
if t.isSessionInvalidated(sid, sub, createdAt) {
|
||||
t.logger.Infof("Session for user %s has been invalidated via IdP-initiated logout", userIdentifier)
|
||||
// Clear the session and redirect to login
|
||||
|
||||
Reference in New Issue
Block a user