Skip to content

Commit

Permalink
Check if claims expiry longer than session (#1849)
Browse files Browse the repository at this point in the history
Fixes #1805 

At the moment only does this for when JWT is paired to policy.

In case of directly being tied to internal token i.e. with kid header the internal token expiry takes precedence
  • Loading branch information
joshblakeley authored and buger committed Aug 11, 2018
1 parent df9cd39 commit f9a2000
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 3 deletions.
16 changes: 15 additions & 1 deletion mw_jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,12 @@ func (k *JWTMiddleware) processCentralisedJWT(r *http.Request, token *jwt.Token)
log.Error("Could not find a valid policy to apply to this token!")
return errors.New("Key not authorized: no matching policy"), http.StatusForbidden
}
//override session expiry with JWT if longer lived
if f, ok := claims["exp"].(float64); ok {
if int64(f)-newSession.Expires > 0 {
newSession.Expires = int64(f)
}
}

session = newSession
session.MetaData = map[string]interface{}{"TykJWTSessionID": sessionID}
Expand Down Expand Up @@ -336,6 +342,13 @@ func (k *JWTMiddleware) processCentralisedJWT(r *http.Request, token *jwt.Token)
return errors.New("Key not authorized: could not apply new policy"), http.StatusForbidden
}

//override session expiry with JWT if longer lived
if f, ok := claims["exp"].(float64); ok {
if int64(f)-session.Expires > 0 {
session.Expires = int64(f)
}
}

go SessionCache.Set(session.KeyHash(), session, cache.DefaultExpiration)
}
}
Expand Down Expand Up @@ -486,7 +499,8 @@ func (k *JWTMiddleware) ProcessRequest(w http.ResponseWriter, r *http.Request, _
func (k *JWTMiddleware) timeValidateJWTClaims(c jwt.MapClaims) *jwt.ValidationError {
vErr := new(jwt.ValidationError)
now := time.Now().Unix()

// The claims below are optional, by default, so if they are set to the
// default value in Go, let's not fail the verification for them.
if !c.VerifyExpiresAt(now-int64(k.Spec.JWTExpiresAtValidationSkew), false) {
vErr.Inner = errors.New("token has expired")
vErr.Errors |= jwt.ValidationErrorExpired
Expand Down
50 changes: 48 additions & 2 deletions mw_jwt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -616,7 +616,7 @@ func TestJWTSessionRSAWithRawSourceInvalidPolicyID(t *testing.T) {
ts.Run(t, test.TestCase{
Headers: authHeaders,
Code: http.StatusForbidden,
BodyMatch: "Key not authorized: no matching policy",
BodyMatch: "key not authorized: no matching policy",
})
})
}
Expand Down Expand Up @@ -899,8 +899,8 @@ func TestJWTExistingSessionRSAWithRawSourceInvalidPolicyID(t *testing.T) {
t.Run("Request with invalid policy in JWT", func(t *testing.T) {
ts.Run(t, test.TestCase{
Headers: authHeaders,
BodyMatch: "key not authorized: no matching policy",
Code: http.StatusForbidden,
BodyMatch: "Key not authorized: no matching policy",
})
})
}
Expand Down Expand Up @@ -1339,3 +1339,49 @@ func TestJWTRSAInvalidPublickKey(t *testing.T) {
})
})
}

func createExpiringPolicy(pGen ...func(p *user.Policy)) string {
pID := keyGen.GenerateAuthKey("")
pol := createStandardPolicy()
pol.ID = pID
pol.KeyExpiresIn = 1

if len(pGen) > 0 {
pGen[0](pol)
}

policiesMu.Lock()
policiesByID[pID] = *pol
policiesMu.Unlock()

return pID
}

func TestJWTExpOverridesToken(t *testing.T) {
ts := newTykTestServer()
defer ts.Close()
//create policy which sets keys to have expiry in one second
pID := createExpiringPolicy()

buildAndLoadAPI(func(spec *APISpec) {
spec.UseKeylessAccess = false
spec.EnableJWT = true
spec.JWTSigningMethod = RSASign
spec.JWTSource = base64.StdEncoding.EncodeToString([]byte(jwtRSAPubKey))
spec.JWTPolicyFieldName = "policy_id"
spec.Proxy.ListenPath = "/"
})

jwtToken := createJWKToken(func(t *jwt.Token) {
t.Claims.(jwt.MapClaims)["foo"] = "bar"
t.Claims.(jwt.MapClaims)["sub"] = "user123@test.com" //is ignored
t.Claims.(jwt.MapClaims)["policy_id"] = pID
t.Claims.(jwt.MapClaims)["exp"] = time.Now().Add(time.Second * 72).Unix()
})
authHeaders := map[string]string{"authorization": jwtToken}
//JWT expiry overrides internal token which gets expiry from policy so second request will pass
ts.Run(t, []test.TestCase{
{Headers: authHeaders, Code: http.StatusOK, Delay: 1100 * time.Millisecond},
{Headers: authHeaders, Code: http.StatusOK},
}...)
}

0 comments on commit f9a2000

Please sign in to comment.