{"action":"closed","issue":{"url":"https://api.github.com/repos/IdentityServer/Thinktecture.IdentityServer3/issues/946","labels_url":"https://api.github.com/repos/IdentityServer/Thinktecture.IdentityServer3/issues/946/labels{/name}","comments_url":"https://api.github.com/repos/IdentityServer/Thinktecture.IdentityServer3/issues/946/comments","events_url":"https://api.github.com/repos/IdentityServer/Thinktecture.IdentityServer3/issues/946/events","html_url":"https://github.com/IdentityServer/Thinktecture.IdentityServer3/issues/946","id":58124549,"number":946,"title":"Access Token Validation & Caching Problem","user":{"login":"jlegan","id":2712321,"avatar_url":"https://avatars.githubusercontent.com/u/2712321?v=3","gravatar_id":"","url":"https://api.github.com/users/jlegan","html_url":"https://github.com/jlegan","followers_url":"https://api.github.com/users/jlegan/followers","following_url":"https://api.github.com/users/jlegan/following{/other_user}","gists_url":"https://api.github.com/users/jlegan/gists{/gist_id}","starred_url":"https://api.github.com/users/jlegan/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jlegan/subscriptions","organizations_url":"https://api.github.com/users/jlegan/orgs","repos_url":"https://api.github.com/users/jlegan/repos","events_url":"https://api.github.com/users/jlegan/events{/privacy}","received_events_url":"https://api.github.com/users/jlegan/received_events","type":"User","site_admin":false},"labels":[],"state":"closed","locked":false,"assignee":null,"milestone":null,"comments":4,"created_at":"2015-02-18T20:38:00Z","updated_at":"2015-02-19T16:23:36Z","closed_at":"2015-02-19T16:23:36Z","body":"I implemented Redis caching last week using the UseIdentityServerBearerTokenAuthentication middleware. I did a quick test and confirmed that the cache was working (token and claims were successfully pushed into cache, confirmed with a redis CLI client. I then moved onto a different part of the project and started troubleshooting a 401 being thrown by an API controller on the second attempt to call it from the MVC client. I had verbose logging on and it was not making sense so I set some break points in my cache implementation and I once again confirmed that it was working as expected (cache is checked and is added). However, every subsequent attempt to call the API fails with a 401 and the cache is never touched (get or set). If I set EnableValidationResultCache to false in the middleware and make it go to the database every time it works successfully each and every time. Below is my implementation of both the middleware and cache. Can anyone see what might be tripping this up on a second request? To summarize:\r\n\r\n* Cache is populated successfully on the first attempt to use an access token (reference tokens are being used).\r\n* Cache has an expiry of 5 minutes (confirmed that it is being cleaned up properly).\r\n* Two calls made back to back (same controller, same reference token, same API) fails with a 401 after the first successful call that added it to the cache.\r\n* On the subsequent requests, the breakpoints in the cache are never hit and I receive an immediate 401, happening software in the Owin pipeline.\r\n* Turning off cache in the middleware allows everything to work successfully.\r\n\r\n**Update:** I converted the client over to using straight access tokens instead of reference tokens and the behavior persists.\r\n\r\n````\r\napp.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions\r\n            {\r\n                Authority = CloudConfigurationManager.GetSetting(\"TokenAuthority\"),\r\n                RequiredScopes = new[] {Scopes.Lockdown.Management},\r\n                RoleClaimType = Claims.Role,\r\n                AuthenticationMode = AuthenticationMode.Active,\r\n                ValidationMode = ValidationMode.ValidationEndpoint,\r\n                EnableValidationResultCache = true, \r\n                ValidationResultCacheDuration = TimeSpan.FromMinutes(Convert.ToInt32(CloudConfigurationManager.GetSetting(\"TokenCacheDuration\"))),\r\n                ValidationResultCache =\r\n                    new RedisValidationResultCache(Convert.ToInt32(CloudConfigurationManager.GetSetting(\"TokenCacheDuration\")),\r\n                        ConnectionMultiplexer.Connect(CloudConfigurationManager.GetSetting(\"TokenCacheConnection\")))\r\n            });\r\n````\r\n````\r\npublic class RedisValidationResultCache : IValidationResultCache\r\n    {\r\n        private readonly ICache _cache;\r\n\t    private readonly IClock _clock;\r\n\t    private readonly int _cacheDuration;\r\n\r\n        /// <summary>\r\n        /// Initializes a new instance of the <see cref=\"InMemoryValidationResultCache\"/> class.\r\n        /// </summary>\r\n        /// <param name=\"cacheDuration\"></param>\r\n        /// <param name=\"connection\"></param>\r\n        public RedisValidationResultCache(int cacheDuration, ConnectionMultiplexer connection)\r\n            : this(cacheDuration, new TokenCacheClock(), new RedisTokenCache(connection))\r\n\t    { }\r\n\r\n        /// <summary>\r\n        /// Initializes a new instance of the <see cref=\"InMemoryValidationResultCache\"/> class.\r\n        /// </summary>\r\n        /// <param name=\"cacheDuration\"></param>\r\n        /// <param name=\"clock\">The clock.</param>\r\n        /// <param name=\"cache\">The cache.</param>\r\n        /// <exception cref=\"System.ArgumentNullException\">\r\n        /// clock\r\n        /// or\r\n        /// options\r\n        /// or\r\n        /// cache\r\n        /// </exception>\r\n        private RedisValidationResultCache(int cacheDuration, IClock clock, ICache cache) \r\n\t\t{\r\n\t\t    if (clock == null) { throw new ArgumentNullException(\"clock\"); }\r\n\t\t    if (cache == null) { throw new ArgumentNullException(\"cache\"); }\r\n            if (cacheDuration <= 0) { throw new ArgumentNullException(\"cacheDuration\"); }\r\n\r\n            _cacheDuration = cacheDuration;\r\n\t\t    _cache = cache;\r\n\t\t    _clock = clock;\r\n\t    }\r\n\r\n        /// <summary>\r\n        /// Add a validation result\r\n        /// </summary>\r\n        /// <param name=\"token\">The token.</param>\r\n        /// <param name=\"claims\">The claims.</param>\r\n        /// <returns></returns>\r\n\t    public Task AddAsync(string token, IEnumerable<Claim> claims) \r\n        {\r\n\t\t    var expiryClaim = claims.FirstOrDefault(c => c.Type == ClaimTypes.Expiration);\r\n\t\t    var cacheExpirySetting = _clock.UtcNow.Add(TimeSpan.FromMinutes(_cacheDuration));\r\n\t\t    \r\n            if (expiryClaim != null) {\r\n\t\t\t    long epoch;\r\n\t\t\t    if (long.TryParse(expiryClaim.Value, out epoch)) \r\n                {\r\n\t\t\t\t    var tokenExpiresAt = epoch.ToDateTimeOffsetFromEpoch();\r\n\t\t\t\t    \r\n                    if (tokenExpiresAt < cacheExpirySetting) \r\n                    {\r\n\t\t\t            _cache.Add(token, claims, tokenExpiresAt);\r\n\t\t\t            return Task.FromResult<object>(null);\r\n\t\t\t\t    }\r\n\t\t\t    }\r\n\t\t    }\r\n\t\t\t\r\n            _cache.Add(token, claims, cacheExpirySetting);\r\n\r\n            return Task.FromResult<object>(null);\r\n        }\r\n\r\n        /// <summary>\r\n        /// Retrieves a validation result\r\n        /// </summary>\r\n        /// <param name=\"token\">The token.</param>\r\n        /// <returns></returns>\r\n        public Task<IEnumerable<Claim>> GetAsync(string token)\r\n        {\r\n            var result = _cache.Get(token);\r\n\r\n            if (result != null)\r\n            {\r\n                return Task.FromResult(result as IEnumerable<Claim>);\r\n            }\r\n\r\n            return Task.FromResult<IEnumerable<Claim>>(null);\r\n        }\r\n    }\r\n````\r\n"}}