protected override async Task <AuthenticateResult> HandleAuthenticateAsync() { string token = Options.TokenRetriever(Context.Request); if (token.IsMissing()) { return(AuthenticateResult.Skip()); } if (token.Contains('.') && Options.SkipTokensWithDots) { _logger.LogTrace("Token contains a dot - skipped because SkipTokensWithDots is set."); return(AuthenticateResult.Skip()); } // Resolve request generator _requestGenerator = Context.RequestServices.GetService <IIntrospectionRequestGenerator>() ?? new DefaultIntrospectionRequestGenerator(); if (Options.EnableCaching) { var claims = await _cache.GetClaimsAsync(token).ConfigureAwait(false); if (claims != null && await _requestGenerator.UseCacheAsync(Context.Request, claims)) { var ticket = CreateTicket(claims); _logger.LogTrace("Token found in cache."); if (Options.SaveToken) { ticket.Properties.StoreTokens(new[] { new AuthenticationToken { Name = "access_token", Value = token } }); } return(AuthenticateResult.Success(ticket)); } _logger.LogTrace("Token is not cached, or the introspection response generator rejected the currently cached claims."); } // Use a LazyAsync to ensure only one thread is requesting introspection for a token - the rest will wait for the result var lazyIntrospection = _lazyTokenIntrospections.GetOrAdd(token, CreateLazyIntrospection); try { var response = await lazyIntrospection.Value.ConfigureAwait(false); if (response.IsError) { _logger.LogError("Error returned from introspection endpoint: " + response.Error); return(AuthenticateResult.Fail("Error returned from introspection endpoint: " + response.Error)); } if (response.IsActive) { var ticket = CreateTicket(response.Claims); if (Options.SaveToken) { ticket.Properties.StoreTokens(new[] { new AuthenticationToken { Name = "access_token", Value = token } }); } if (Options.EnableCaching) { await _cache.SetClaimsAsync(token, response.Claims, Options.CacheDuration, _logger).ConfigureAwait(false); } return(AuthenticateResult.Success(ticket)); } else { return(AuthenticateResult.Fail("Token is not active.")); } } finally { // If caching is on and it succeeded, the claims are now in the cache. // If caching is off and it succeeded, the claims will be discarded. // Either way, we want to remove the temporary store of claims for this token because it is only intended for de-duping fetch requests AsyncLazy <IntrospectionResponse> removed; _lazyTokenIntrospections.TryRemove(token, out removed); } }
/// <summary> /// Tries to authenticate a reference token on the current request /// </summary> /// <returns></returns> protected override async Task <AuthenticateResult> HandleAuthenticateAsync() { string token = Options.TokenRetriever(Context.Request); if (token.IsMissing()) { return(AuthenticateResult.NoResult()); } if (token.Contains('.') && Options.SkipTokensWithDots) { _logger.LogTrace("Token contains a dot - skipped because SkipTokensWithDots is set."); return(AuthenticateResult.NoResult()); } if (Options.EnableCaching) { var claims = await _cache.GetClaimsAsync(token).ConfigureAwait(false); if (claims != null) { var ticket = CreateTicket(claims); _logger.LogTrace("Token found in cache."); if (Options.SaveToken) { ticket.Properties.StoreTokens(new[] { new AuthenticationToken { Name = "access_token", Value = token } }); } return(AuthenticateResult.Success(ticket)); } _logger.LogTrace("Token is not cached."); } // Use a LazyAsync to ensure only one thread is requesting introspection for a token - the rest will wait for the result var lazyIntrospection = Options.LazyIntrospections.GetOrAdd(token, CreateLazyIntrospection); var lazyUserinfo = Options.LazyUserinfos.GetOrAdd(token, CreateLazyUserinfos); try { var introspectionResponse = await lazyIntrospection.Value.ConfigureAwait(false); if (introspectionResponse.IsError) { _logger.LogError("Error returned from introspection endpoint: " + introspectionResponse.Error); return(AuthenticateResult.Fail("Error returned from introspection endpoint: " + introspectionResponse.Error)); } if (introspectionResponse.IsActive) { var claims = introspectionResponse.Claims; if (Options.GetClaimsFromUserinfoEndpoint) { var userinfoResponse = await lazyUserinfo.Value.ConfigureAwait(false); if (userinfoResponse.IsError) { _logger.LogError("Error returned from userinfo endpoint: " + userinfoResponse.Error); return(AuthenticateResult.Fail("Error returned from userinfo endpoint: " + userinfoResponse.Error)); } claims = claims.Union(userinfoResponse.Claims); } if (Options.RoleClaimValueSplitting) { var roleClaimQuery = from c in claims where c.Type == Options.RoleClaimType select c; if (roleClaimQuery.Count() == 1) { var roleClaim = roleClaimQuery.Single(); var newRoleClaims = SplitClaim(roleClaim, Options.RoleClaimValueSplittingChar); claims = claims.Union(newRoleClaims); } } var ticket = CreateTicket(claims); if (Options.SaveToken) { ticket.Properties.StoreTokens(new[] { new AuthenticationToken { Name = "access_token", Value = token } }); } if (Options.EnableCaching) { await _cache.SetClaimsAsync(token, claims, Options.CacheDuration, _logger).ConfigureAwait(false); } return(AuthenticateResult.Success(ticket)); } else { return(AuthenticateResult.Fail("Token is not active.")); } } finally { // If caching is on and it succeeded, the claims are now in the cache. // If caching is off and it succeeded, the claims will be discarded. // Either way, we want to remove the temporary store of claims for this token because it is only intended for de-duping fetch requests Options.LazyIntrospections.TryRemove(token, out _); Options.LazyUserinfos.TryRemove(token, out _); } }
/// <summary> /// Tries to authenticate a reference token on the current request /// </summary> /// <returns></returns> protected override async Task <AuthenticateResult> HandleAuthenticateAsync() { string token = Options.TokenRetriever(Context.Request); if (token.IsMissing()) { return(AuthenticateResult.NoResult()); } if (token.Contains('.') && Options.SkipTokensWithDots) { _logger.LogTrace("Token contains a dot - skipped because SkipTokensWithDots is set."); return(AuthenticateResult.NoResult()); } if (Options.EnableCaching) { var key = $"{Options.CacheKeyPrefix}{token}"; var claims = await _cache.GetClaimsAsync(key).ConfigureAwait(false); if (claims != null) { // find out if it is a cached inactive token var isInActive = claims.FirstOrDefault(c => string.Equals(c.Type, "active", StringComparison.OrdinalIgnoreCase) && string.Equals(c.Value, "false", StringComparison.OrdinalIgnoreCase)); if (isInActive != null) { return(await ReportNonSuccessAndReturn("Cached token is not active.")); } return(await CreateTicket(claims, token)); } _logger.LogTrace("Token is not cached."); } // Use a LazyAsync to ensure only one thread is requesting introspection for a token - the rest will wait for the result var lazyIntrospection = Options.LazyIntrospections.GetOrAdd(token, CreateLazyIntrospection); try { var response = await lazyIntrospection.Value.ConfigureAwait(false); if (response.IsError) { _logger.LogError("Error returned from introspection endpoint: " + response.Error); return(await ReportNonSuccessAndReturn("Error returned from introspection endpoint: " + response.Error)); } if (response.IsActive) { if (Options.EnableCaching) { var key = $"{Options.CacheKeyPrefix}{token}"; await _cache.SetClaimsAsync(key, response.Claims, Options.CacheDuration, _logger).ConfigureAwait(false); } return(await CreateTicket(response.Claims, token)); } else { if (Options.EnableCaching) { var key = $"{Options.CacheKeyPrefix}{token}"; // add an exp claim - otherwise caching will not work var claimsWithExp = response.Claims.ToList(); claimsWithExp.Add(new Claim("exp", DateTimeOffset.UtcNow.Add(Options.CacheDuration).ToUnixTimeSeconds().ToString())); await _cache.SetClaimsAsync(key, claimsWithExp, Options.CacheDuration, _logger).ConfigureAwait(false); } return(await ReportNonSuccessAndReturn("Token is not active.")); } } finally { // If caching is on and it succeeded, the claims are now in the cache. // If caching is off and it succeeded, the claims will be discarded. // Either way, we want to remove the temporary store of claims for this token because it is only intended for de-duping fetch requests Options.LazyIntrospections.TryRemove(token, out _); } }
/// <summary> /// Tries to authenticate a reference token on the current request /// </summary> /// <returns></returns> protected override async Task <AuthenticateResult> HandleAuthenticateAsync() { string token = Options.TokenRetriever(Context.Request); if (token.IsMissing()) { return(ReportNonSuccessAndReturn(AuthenticateResult.NoResult())); } if (token.Contains(".") && Options.SkipTokensWithDots) { _logger.LogTrace("Token contains a dot - skipped because SkipTokensWithDots is set."); return(ReportNonSuccessAndReturn(AuthenticateResult.NoResult())); } if (Options.EnableCaching) { var key = $"{Options.CacheKeyPrefix}{token}"; var claims = await _cache.GetClaimsAsync(key).ConfigureAwait(false); if (claims.Any()) { var ticket = await CreateTicket(claims); _logger.LogTrace("Token found in cache."); if (Options.SaveToken) { ticket.Properties.StoreTokens(new[] { new AuthenticationToken { Name = "access_token", Value = token } }); } return(AuthenticateResult.Success(ticket)); } _logger.LogTrace("Token is not cached."); } // Use a LazyAsync to ensure only one thread is requesting introspection for a token - the rest will wait for the result var lazyIntrospection = Options.LazyIntrospections.GetOrAdd(token, CreateLazyIntrospection); try { var response = await lazyIntrospection.Value.ConfigureAwait(false); if (response.IsError) { _logger.LogError("Error returned from introspection endpoint: " + response.Error); return(ReportNonSuccessAndReturn(AuthenticateResult.Fail("Error returned from introspection endpoint: " + response.Error))); } if (response.IsActive) { var ticket = await CreateTicket(response.Claims); if (Options.SaveToken) { ticket.Properties.StoreTokens(new[] { new AuthenticationToken { Name = "access_token", Value = token } }); } if (Options.EnableCaching) { var key = $"{Options.CacheKeyPrefix}{token}"; await _cache.SetClaimsAsync(key, response.Claims, Options.CacheDuration, _logger).ConfigureAwait(false); } return(AuthenticateResult.Success(ticket)); } else { return(ReportNonSuccessAndReturn(AuthenticateResult.Fail("Token is not active."))); } } finally { // If caching is on and it succeeded, the claims are now in the cache. // If caching is off and it succeeded, the claims will be discarded. // Either way, we want to remove the temporary store of claims for this token because it is only intended for de-duping fetch requests Options.LazyIntrospections.TryRemove(token, out _); } }
/// <summary> /// Tries to authenticate a reference token on the current request /// </summary> /// <returns></returns> protected override async Task <AuthenticateResult> HandleAuthenticateAsync() { string token = Options.TokenRetriever(Context.Request); // no token - nothing to do here if (token.IsMissing()) { return(AuthenticateResult.NoResult()); } // if token contains a dot - it might be a JWT and we are skipping // this is configurable if (token.Contains('.') && Options.SkipTokensWithDots) { _logger.LogTrace("Token contains a dot - skipped because SkipTokensWithDots is set."); return(AuthenticateResult.NoResult()); } // if caching is enable - let's check if we have a cached introspection if (Options.EnableCaching) { var key = $"{Options.CacheKeyPrefix}{token}"; var claims = await _cache.GetClaimsAsync(key).ConfigureAwait(false); if (claims != null) { // find out if it is a cached inactive token var isInActive = claims.FirstOrDefault(c => string.Equals(c.Type, "active", StringComparison.OrdinalIgnoreCase) && string.Equals(c.Value, "false", StringComparison.OrdinalIgnoreCase)); if (isInActive != null) { return(await ReportNonSuccessAndReturn("Cached token is not active.")); } return(await CreateTicket(claims, token)); } _logger.LogTrace("Token is not cached."); } // no cached result - let's make a network roundtrip to the introspection endpoint // this code block tries to make sure that we only do a single roundtrip, even when multiple requests // with the same token come in at the same time try { return(await IntrospectionDictionary.GetOrAdd(token, _ => { return new Lazy <Task <AuthenticateResult> >(async() => { var response = await LoadClaimsForToken(token); if (response.IsError) { _logger.LogError("Error returned from introspection endpoint: " + response.Error); return await ReportNonSuccessAndReturn("Error returned from introspection endpoint: " + response.Error); } if (response.IsActive) { if (Options.EnableCaching) { var key = $"{Options.CacheKeyPrefix}{token}"; await _cache.SetClaimsAsync(key, response.Claims, Options.CacheDuration, _logger).ConfigureAwait(false); } return await CreateTicket(response.Claims, token); } else { if (Options.EnableCaching) { var key = $"{Options.CacheKeyPrefix}{token}"; // add an exp claim - otherwise caching will not work var claimsWithExp = response.Claims.ToList(); claimsWithExp.Add(new Claim("exp", DateTimeOffset.UtcNow.Add(Options.CacheDuration).ToUnixTimeSeconds().ToString())); await _cache.SetClaimsAsync(key, claimsWithExp, Options.CacheDuration, _logger) .ConfigureAwait(false); } return await ReportNonSuccessAndReturn("Token is not active."); } }); }).Value); } finally { IntrospectionDictionary.TryRemove(token, out _); } }